summaryrefslogtreecommitdiff
path: root/megapixels/app/models/bbox.py
diff options
context:
space:
mode:
authoradamhrv <adam@ahprojects.com>2018-11-04 21:44:20 +0100
committeradamhrv <adam@ahprojects.com>2018-11-04 21:44:20 +0100
commit156790b383101756e2324dcde63415f00ba94a86 (patch)
tree62761815f480d244fae3602c9189baf7aec02497 /megapixels/app/models/bbox.py
parent83507e26c00f79b7bac3d3b606da50cc4cd0db6b (diff)
.
Diffstat (limited to 'megapixels/app/models/bbox.py')
-rw-r--r--megapixels/app/models/bbox.py236
1 files changed, 236 insertions, 0 deletions
diff --git a/megapixels/app/models/bbox.py b/megapixels/app/models/bbox.py
new file mode 100644
index 00000000..41b67416
--- /dev/null
+++ b/megapixels/app/models/bbox.py
@@ -0,0 +1,236 @@
+from dlib import rectangle as dlib_rectangle
+import numpy as np
+
+class BBoxPoint:
+
+ def __init__(self, x, y):
+ self._x = x
+ self._y = y
+
+ @property
+ def x(self):
+ return self._x
+
+ @property
+ def y(self):
+ return self._y
+
+ def offset(self, x, y):
+ return (self._x + x, self._y + y)
+
+ def tuple(self):
+ return (self._x, self._y)
+
+
+class BBox:
+
+ def __init__(self, x1, y1, x2, y2):
+ """Represents a bounding box and provides methods for accessing and modifying
+ :param x1: normalized left coord
+ :param y1: normalized top coord
+ :param x2: normalized right coord
+ :param y2: normalized bottom coord
+ """
+ self._x1 = x1
+ self._y1 = y1
+ self._x2 = x2
+ self._y2 = y2
+ self._width = x2 - x1
+ self._height = y2 - y1
+ self._cx = x1 + (self._width // 2)
+ self._cy = y1 + (self._height // 2)
+ self._tl = (x1, y1)
+ self._br = (x2, y2)
+ self._rect = (self._x1, self._y1, self._x2, self._y2)
+
+
+ @property
+ def pt_tl(self):
+ return self._tl
+
+ @property
+ def pt_br(self):
+ return self._br
+
+ @property
+ def x(self):
+ return self._x1
+
+ @property
+ def y(self):
+ return self._y1
+
+ @property
+ def x1(self):
+ return self._x1
+
+ @property
+ def y1(self):
+ return self._y1
+
+
+ @property
+ def x2(self):
+ return self._x2
+
+ @property
+ def y2(self):
+ return self._y2
+
+ @property
+ def height(self):
+ return self._height
+
+ @property
+ def width(self):
+ return self._width
+
+ @property
+ def h(self):
+ return self._height
+
+ @property
+ def w(self):
+ return self._width
+
+ @property
+ def cx(self):
+ return self._cx
+
+ @property
+ def cy(self):
+ return self._cy
+
+ # # -----------------------------------------------------------------
+ # # Utils
+
+ # def constrain(self, dim):
+
+
+ # -----------------------------------------------------------------
+ # Modify
+
+ def expand_dim(self, amt, dim):
+ """Expands BBox within dim
+ :param box: (tuple) left, top, right, bottom
+ :param dim: (tuple) width, height
+ :returns (BBox) in pixel dimensions
+ """
+ # expand
+ rect_exp = list( (np.array(self._rect) + np.array([-amt, -amt, amt, amt])).astype('int'))
+ # outliers
+ oob = list(range(4))
+ oob[0] = min(rect_exp[0], 0)
+ oob[1] = min(rect_exp[1], 0)
+ oob[2] = dim[0] - max(rect_exp[2], 2)
+ oob[3] = dim[1] - max(rect_exp[3], 3)
+ oob = np.array(oob)
+ oob[oob > 0] = 0
+ # amount
+ oob = np.absolute(oob)
+ # threshold
+ rect_exp[0] = max(rect_exp[0], 0)
+ rect_exp[1] = max(rect_exp[1], 0)
+ rect_exp[2] = min(rect_exp[2], dim[0])
+ rect_exp[3] = min(rect_exp[3], dim[1])
+ # redistribute oob amounts
+ oob = np.array([-oob[2], -oob[3], oob[0], oob[1]])
+ rect_exp = np.add(np.array(rect_exp), oob)
+ return BBox(*rect_exp)
+
+
+ # -----------------------------------------------------------------
+ # Convert to
+
+ def to_dim(self, dim):
+ """scale is (w, h) is tuple of dimensions"""
+ w, h = dim
+ rect = list((np.array(self._rect) * np.array([w, h, w, h])).astype('int'))
+ return BBox(*rect)
+
+ def normalize(self, rect, dim):
+ w, h = dim
+ x1, y1, x2, y2 = rect
+ return (x1 / w, y1 / h, x2 / w, y2 / h)
+
+ # -----------------------------------------------------------------
+ # Format as
+
+ def as_xyxy(self):
+ """Converts BBox back to x1, y1, x2, y2 rect"""
+ return (self._x1, self._y1, self._x2, self._y2)
+
+ def as_xywh(self):
+ """Converts BBox back to haar type"""
+ return (self._x1, self._y1, self._width, self._height)
+
+ def as_trbl(self):
+ """Converts BBox to CSS (top, right, bottom, left)"""
+ return (self._y1, self._x2, self._y2, self._x1)
+
+ def as_dlib(self):
+ """Converts BBox to dlib rect type"""
+ return dlib.rectangle(self._x1, self._y1, self._x2, self._y2)
+
+ def as_yolo(self):
+ """Converts BBox to normalized center x, center y, w, h"""
+ return (self._cx, self._cy, self._width, self._height)
+
+
+ # -----------------------------------------------------------------
+ # Create from
+
+ @classmethod
+ def from_xyxy_dim(cls, x1, y1, x2, y2, dim):
+ """Converts x1, y1, w, h to BBox and normalizes
+ :returns BBox
+ """
+ rect = cls.normalize(cls, (x1, y1, x2, y2), dim)
+ return cls(*rect)
+
+ @classmethod
+ def from_xywh_dim(cls, x, y, w, h, dim):
+ """Converts x1, y1, w, h to BBox and normalizes
+ :param rect: (list) x1, y1, w, h
+ :param dim: (list) w, h
+ :returns BBox
+ """
+ rect = cls.normalize(cls, (x, y, x + w, y + h), dim)
+ return cls(*rect)
+
+ @classmethod
+ def from_xywh(cls, x, y, w, h):
+ """Converts x1, y1, w, h to BBox
+ :param rect: (list) x1, y1, w, h
+ :param dim: (list) w, h
+ :returns BBox
+ """
+ return cls(x, y, x+w, y+h)
+
+ @classmethod
+ def from_css(cls, rect, dim):
+ """Converts rect from CSS (top, right, bottom, left) to BBox
+ :param rect: (list) x1, y1, x2, y2
+ :param dim: (list) w, h
+ :returns BBox
+ """
+ rect = (rect[3], rect[0], rect[1], rect[2])
+ rect = cls.normalize(cls, rect, dim)
+ return cls(*rect)
+
+ @classmethod
+ def from_dlib_dim(cls, rect, dim):
+ """Converts dlib.rectangle to BBox
+ :param rect: (list) x1, y1, x2, y2
+ :param dim: (list) w, h
+ :returns dlib.rectangle
+ """
+ rect = (rect.left(), rect.top(), rect.right(), rect.bottom())
+ rect = cls.normalize(cls, rect, dim)
+ return cls(*rect)
+
+
+ def str(self):
+ """Return BBox as a string "x1, y1, x2, y2" """
+ return self.as_box()
+