| |
| |
| |
| import numpy as np |
|
|
|
|
| |
| BBOX_FORMATS = ("pascal_voc", "albumentations", "coco", "coco_normalized") |
|
|
|
|
| def to_pascal_voc(coords, fmt: str, im_w: int, im_h: int) -> dict: |
| """Convert a bounding box in any supported format to pascal_voc. |
| |
| All formats collapse to the pascal_voc convention — a dict of absolute |
| top-left/bottom-right pixels ``{x0, y0, x1, y1}`` (matching :func:`get_bbox_dict` |
| and ``pilbox``). Given four ordered values ``(c0, c1, c2, c3)``: |
| |
| - ``pascal_voc``: ``(x0, y0, x1, y1)`` absolute pixels. |
| - ``albumentations``: ``(x0, y0, x1, y1)`` normalized to ``[0, 1]``. |
| - ``coco``: ``(x0, y0, w, h)`` absolute pixels (top-left + size). |
| - ``coco_normalized``: ``(x0, y0, w, h)`` normalized to ``[0, 1]``. |
| |
| Args: |
| coords: The four box values ``(c0, c1, c2, c3)`` in the given ``fmt``. |
| fmt: One of :data:`BBOX_FORMATS`. |
| im_w: Reference image width in pixels (for the normalized formats). |
| im_h: Reference image height in pixels (for the normalized formats). |
| |
| Returns: |
| ``{"x0", "y0", "x1", "y1"}`` of absolute integer pixels. |
| |
| Raises: |
| ValueError: If ``fmt`` is not one of :data:`BBOX_FORMATS`. |
| |
| >>> to_pascal_voc((10, 20, 40, 60), "pascal_voc", 100, 100) |
| {'x0': 10, 'y0': 20, 'x1': 40, 'y1': 60} |
| >>> to_pascal_voc((0.1, 0.2, 0.4, 0.6), "albumentations", 100, 200) |
| {'x0': 10, 'y0': 40, 'x1': 40, 'y1': 120} |
| >>> to_pascal_voc((10, 20, 30, 40), "coco", 100, 100) |
| {'x0': 10, 'y0': 20, 'x1': 40, 'y1': 60} |
| >>> to_pascal_voc((0.1, 0.2, 0.3, 0.4), "coco_normalized", 100, 200) |
| {'x0': 10, 'y0': 40, 'x1': 40, 'y1': 120} |
| """ |
| c0, c1, c2, c3 = coords |
| if fmt == "pascal_voc": |
| return {"x0": int(c0), "y0": int(c1), "x1": int(c2), "y1": int(c3)} |
| if fmt == "albumentations": |
| return { |
| "x0": int(c0 * im_w), |
| "y0": int(c1 * im_h), |
| "x1": int(c2 * im_w), |
| "y1": int(c3 * im_h), |
| } |
| if fmt == "coco": |
| return get_bbox_dict(c0, c1, c2, c3) |
| if fmt == "coco_normalized": |
| return get_bbox_dict(c0, c1, c2, c3, im_wh=(im_w, im_h)) |
| raise ValueError(f"unknown bbox format {fmt!r}; expected one of {list(BBOX_FORMATS)}") |
|
|
|
|
| def get_bbox_dict(x, y, width, height, im_wh = None)-> dict: |
| '''given top-left (x,y), return a bbox dict with absolute coordinates x0, y0, x1, y1 |
| Args: |
| im_width: if provided, treats x,y,w,h as relative coordinates |
| im_height: if provided, treats x,y,w,h as relative coordinates |
| ''' |
| is_relative = type(im_wh) != type(None) |
| if is_relative: |
| w,h = im_wh |
| return {'x0': int(x * w) if is_relative else int(x), |
| 'y0': int(y * h) if is_relative else int(y), |
| 'x1': int((x+width) * w) if is_relative else int(x + width), |
| 'y1': int((y+height) * h) if is_relative else int(y + height)} |
|
|
| def bbox_rebase_xy(x,y,w,h, to_yolo_format = False): |
| '''convert bbox format frorm yolo (centroid) to standard (top-left) or vice versa |
| ''' |
| x = x+w/2 if to_yolo_format else x - w/2 |
| y = y+h/2 if to_yolo_format else y - h/2 |
| return {'x': x, 'y': y, 'w': w, 'h': h} |
|
|
| def bbox_get_xywh(x0,y0,x1,y1): |
| '''returns dict of x,y,w,h |
| ''' |
| return {'x': x0, 'y': y0, 'w': x1-x0 ,'h': y1-y0} |
|
|
| def bbox_convert(x0, y0, x1, y1, width, height): |
| '''convert bounding box from relative to absolute and vice versa |
| Args: |
| width: reference image's width |
| height: reference image's height |
| ''' |
| if all([i <= 1 for i in [x0,y0,x1,y1]]): |
| |
| return { |
| 'x0': int(x0 * width), 'x1': int(x1 * width), |
| 'y0': int(y0 * height), 'y1': int(y1 * height) |
| } |
| else: |
| |
| if x0 > width or x1> width: |
| raise ValueError(f'{x0} or {x1} is greater than width: {width}') |
| if y0 > height or y1> height: |
| raise ValueError(f'{y0} or {y1} is greater than height: {height}') |
|
|
| return { |
| 'x0': x0 / width, 'x1': x1 / width, |
| 'y0': y0 / height, 'y1': y1 / height |
| } |
|
|
| def bboxes_to_im_mask(l_bboxes, im_wh): |
| ''' return a binary mask given a list of bounding boxes |
| ''' |
| mask = np.zeros(shape = (im_wh[::-1]), dtype = np.uint8) |
| for bbox in l_bboxes: |
| mask[bbox['y0']: bbox['y1'], bbox['x0']: bbox['x1']] = 1 |
| return mask |
|
|
| def bbox_intersects(bbox_a, bbox_b): |
| '''return True if two pascal_voc boxes overlap. |
| |
| Uses the standard axis-aligned overlap test rather than corner-in-rect |
| checks: the latter miss "cross" overlaps where the boxes intersect but no |
| corner of either lies inside the other (e.g. a tall thin box crossing a |
| short wide one). |
| |
| >>> a = {'x0': 0, 'y0': 0, 'x1': 10, 'y1': 10} |
| >>> bbox_intersects(a, {'x0': 5, 'y0': 5, 'x1': 15, 'y1': 15}) |
| True |
| >>> bbox_intersects(a, {'x0': 20, 'y0': 20, 'x1': 30, 'y1': 30}) |
| False |
| >>> # cross overlap: no corner of either box is inside the other |
| >>> bbox_intersects({'x0': 4, 'y0': 0, 'x1': 6, 'y1': 10}, |
| ... {'x0': 0, 'y0': 4, 'x1': 10, 'y1': 6}) |
| True |
| ''' |
| return ( |
| bbox_a['x0'] <= bbox_b['x1'] and bbox_a['x1'] >= bbox_b['x0'] |
| and bbox_a['y0'] <= bbox_b['y1'] and bbox_a['y1'] >= bbox_b['y0'] |
| ) |
|
|
| def bbox_area(x0, y0, x1, y1): |
| return (x1-x0+1) * (y1-y0+1) |
|
|
| def get_bbox_iou(bbox_a, bbox_b): |
| if bbox_intersects(bbox_a, bbox_b): |
| x_left = max(bbox_a['x0'], bbox_b['x0']) |
| x_right = min(bbox_a['x1'], bbox_b['x1']) |
| y_top = max(bbox_a['y0'], bbox_b['y0']) |
| y_bottom = min(bbox_a['y1'], bbox_b['y1']) |
|
|
| inter_area = bbox_area(x0 = x_left, x1 = x_right, y0 = y_top , y1 = y_bottom) |
| bbox_a_area = bbox_area(**bbox_a) |
| bbox_b_area = bbox_area(**bbox_b) |
|
|
| return inter_area / float(bbox_a_area + bbox_b_area - inter_area) |
| else: |
| return 0 |
|
|
| def boxer(lsXY, pctBuffer = 0.3, lXBounds = None, lYBounds = None): |
| ''' |
| Create a minimum Bounding Box given a list of x,y coordinates |
| and a buffer given in percentages of the output image size |
| if pctBuffer is given as a tuple: |
| pctBuffer[0] will be the x buffer |
| pctBuffer[1] will be the y buffer |
| Optional: Provide XBounds and YBounds to ensure the returned values |
| fits within range. |
| ''' |
| minX = minY = float("inf") |
| maxX = maxY = float("-inf") |
|
|
| for x, y in lsXY: |
| |
| if x < minX: |
| minX = x |
| if y < minY: |
| minY = y |
|
|
| |
| if x > maxX: |
| maxX = x |
| if y > maxY: |
| maxY = y |
|
|
| width = maxX - minX |
| height = maxY - minY |
|
|
| if type(pctBuffer) == tuple: |
| xBuffer = pctBuffer[0] |
| yBuffer = pctBuffer[1] |
| else: |
| xBuffer = yBuffer = pctBuffer |
|
|
| coordsDict ={ |
| 'x1': int(minX - xBuffer * width), |
| 'x2': int(maxX + xBuffer * width), |
| 'y1': int(minY - yBuffer * height), |
| 'y2': int(maxY + yBuffer * height) |
| } |
|
|
| if lXBounds: |
| coordsDict['x1'] = max( lXBounds[0], coordsDict['x1']) |
| coordsDict['x2'] = min( lXBounds[1], coordsDict['x2']) |
| if lYBounds: |
| coordsDict['y1'] = max( lYBounds[0], coordsDict['y1']) |
| coordsDict['y2'] = min( lYBounds[1], coordsDict['y2']) |
|
|
| return coordsDict |
|
|