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()