Hi,
I just read this amazing blog on a technique called NMM (Non Maximum Merging). I want to know how can I apply this for oriented bounding boxes. If anyone can help me it would be amazing.
Hi,
Thanks a lot for the kind words. Regarding non-max merging (NMM) for oriented bounding boxes (OBB), supervision currently doesn’t support such an operation. As far as I know, there is no established convention for performing this operation, but I spent an hour drafting a rough solution.
Here is the code defining the input OBBs that we would like to merge.
import numpy as np
import supervision as sv
xyxyxyxy = np.array([
[
[200, 200],
[300, 210],
[280, 320],
[180, 310]
],
[
[260, 240],
[360, 250],
[340, 360],
[240, 350]
],
[
[220, 300],
[320, 310],
[310, 400],
[210, 390]
]
], dtype=int)
image = np.zeros((600, 600, 3), dtype=np.uint8)
def xyxyxyxy_to_xyxy(obbs):
x_min = obbs[:, :, 0].min(axis=1)
x_max = obbs[:, :, 0].max(axis=1)
y_min = obbs[:, :, 1].min(axis=1)
y_max = obbs[:, :, 1].max(axis=1)
return np.stack([x_min, y_min, x_max, y_max], axis=1)
detections = sv.Detections(
xyxy=xyxyxyxy_to_xyxy(xyxyxyxy),
data={'xyxyxyxy': xyxyxyxy}
)
- We rely on shapely to treat each oriented bounding box (OBB) as a
Polygon
and then unify them withunary_union
. - If those polygons overlap, Shapely merges them into one or more larger shapes.
- Using
minimum_rotated_rectangle
on each merged shape gives the smallest oriented box that covers it. - Extracting the rectangle’s corners provides the final merged OBBs.
from shapely.geometry import Polygon
from shapely.ops import unary_union
def merge_xyxyxyxy(xyxyxyxy):
# Convert each OBB's 4 corners into a Shapely polygon
polygons = [Polygon(box) for box in xyxyxyxy]
# Merge all polygons into one geometry (could be a single Polygon or MultiPolygon)
merged = unary_union(polygons)
# If it's a single Polygon, wrap it in a list; if MultiPolygon, gather individual polygons
if merged.geom_type == 'Polygon':
polygons_merged = [merged]
else:
polygons_merged = list(merged.geoms)
# For each merged region (Polygon), compute its minimum-area bounding rectangle
results = []
for poly in polygons_merged:
# Shapely's minimum_rotated_rectangle returns a Polygon
# with 5 points, the last one repeating the first
mabr = poly.minimum_rotated_rectangle
coords = list(mabr.exterior.coords)[:-1] # drop repeated last corner
# Append the 4 corners as (4, 2) into the results
results.append(coords)
# Convert to an (M, 4, 2) NumPy array (M can be 1 or more if there are disjoint merges)
return np.array(results, dtype=np.float32)
xyxyxyxy2 = merge_xyxyxyxy(xyxyxyxy)
detections2 = sv.Detections(
xyxy=xyxyxyxy_to_xyxy(xyxyxyxy2),
data={'xyxyxyxy': xyxyxyxy2}
)