CoolFace
Apppublic

VerokeAI/Object_tracking_boxmot

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
iou.py349 linesDownload Raw Back to utils
1import numpy as np2import cv2 as cv3 4def iou_obb_pair(i, j, bboxes1, bboxes2):5    """6    Compute IoU for the rotated rectangles at index i and j in the batches `bboxes1`, `bboxes2` .7    """8    rect1 = bboxes1[int(i)]9    rect2 = bboxes2[int(j)]10    11    (cx1, cy1, w1, h1, angle1) = rect1[0:5]12    (cx2, cy2, w2, h2, angle2) = rect2[0:5]13    14    15    r1 = ((cx1, cy1), (w1, h1), angle1)16    r2 = ((cx2, cy2), (w2, h2), angle2)17    18    # Compute intersection19    ret, intersect = cv.rotatedRectangleIntersection(r1, r2)20    if ret == 0 or intersect is None:21        return 0.0  # No intersection22    23    # Calculate intersection area24    intersection_area = cv.contourArea(intersect)25    26    # Calculate union area27    area1 = w1 * h128    area2 = w2 * h229    union_area = area1 + area2 - intersection_area30    31    # Compute IoU32    return intersection_area / union_area if union_area > 0 else 0.033 34class AssociationFunction:35    def __init__(self, w, h, asso_mode="iou"):36        """37        Initializes the AssociationFunction class with the necessary parameters for bounding box operations.38        The association function is selected based on the `asso_mode` string provided during class creation.39        40        Parameters:41        w (int): The width of the frame, used for normalizing centroid distance.42        h (int): The height of the frame, used for normalizing centroid distance.43        asso_mode (str): The association function to use (e.g., "iou", "giou", "centroid", etc.).44        """45        self.w = w46        self.h = h47        self.asso_mode = asso_mode48        self.asso_func = self._get_asso_func(asso_mode)49 50    @staticmethod51    def iou_batch(bboxes1, bboxes2) -> np.ndarray:52        bboxes2 = np.expand_dims(bboxes2, 0)53        bboxes1 = np.expand_dims(bboxes1, 1)54 55        xx1 = np.maximum(bboxes1[..., 0], bboxes2[..., 0])56        yy1 = np.maximum(bboxes1[..., 1], bboxes2[..., 1])57        xx2 = np.minimum(bboxes1[..., 2], bboxes2[..., 2])58        yy2 = np.minimum(bboxes1[..., 3], bboxes2[..., 3])59        w = np.maximum(0.0, xx2 - xx1)60        h = np.maximum(0.0, yy2 - yy1)61        wh = w * h62        o = wh / (63            (bboxes1[..., 2] - bboxes1[..., 0]) * (bboxes1[..., 3] - bboxes1[..., 1]) +64            (bboxes2[..., 2] - bboxes2[..., 0]) * (bboxes2[..., 3] - bboxes2[..., 1]) -65            wh66        )67        return o68    69    @staticmethod70    def iou_batch_obb(bboxes1, bboxes2) -> np.ndarray:71 72        N, M = len(bboxes1), len(bboxes2)73 74        def wrapper(i, j):75            return iou_obb_pair(i, j, bboxes1, bboxes2)76        77        iou_matrix = np.fromfunction(np.vectorize(wrapper), shape=(N, M), dtype=int)78        return iou_matrix79 80    @staticmethod81    def hmiou_batch(bboxes1, bboxes2):82        """83        Compute a modified Intersection over Union (hIoU) between two batches of bounding boxes,84        incorporating a vertical overlap ratio.85 86        Parameters:87        - bboxes1: (N, 4) array of bounding boxes [x1, y1, x2, y2]88        - bboxes2: (M, 4) array of bounding boxes [x1, y1, x2, y2]89 90        Returns:91        - hmiou: (N, M) array where hmiou[i, j] is the modified IoU between bboxes1[i] and bboxes2[j]92        """93        # Expand dimensions for broadcasting94        bboxes1 = np.expand_dims(bboxes1, axis=1)  # Shape: (N, 1, 4)95        bboxes2 = np.expand_dims(bboxes2, axis=0)  # Shape: (1, M, 4)96 97        # Compute vertical overlap ratio 'o'98        intersect_y1 = np.maximum(bboxes1[..., 1], bboxes2[..., 1])99        intersect_y2 = np.minimum(bboxes1[..., 3], bboxes2[..., 3])100        intersection_height = np.maximum(0.0, intersect_y2 - intersect_y1)101 102        union_y1 = np.minimum(bboxes1[..., 1], bboxes2[..., 1])103        union_y2 = np.maximum(bboxes1[..., 3], bboxes2[..., 3])104        union_height = np.maximum(1e-10, union_y2 - union_y1)105 106        o = intersection_height / union_height107 108        # Compute standard IoU109        inter_x1 = np.maximum(bboxes1[..., 0], bboxes2[..., 0])110        inter_y1 = np.maximum(bboxes1[..., 1], bboxes2[..., 1])111        inter_x2 = np.minimum(bboxes1[..., 2], bboxes2[..., 2])112        inter_y2 = np.minimum(bboxes1[..., 3], bboxes2[..., 3])113 114        inter_w = np.maximum(0.0, inter_x2 - inter_x1)115        inter_h = np.maximum(0.0, inter_y2 - inter_y1)116        inter_area = inter_w * inter_h117 118        area1 = (bboxes1[..., 2] - bboxes1[..., 0]) * (bboxes1[..., 3] - bboxes1[..., 1])  # Shape: (N, 1)119        area2 = (bboxes2[..., 2] - bboxes2[..., 0]) * (bboxes2[..., 3] - bboxes2[..., 1])  # Shape: (1, M)120 121        union_area = area1 + area2 - inter_area122 123        iou = inter_area / (union_area + 1e-10)124 125        # Modify IoU with vertical overlap ratio126        hmiou = iou * o127 128        return hmiou129 130    @staticmethod131    def giou_batch(bboxes1, bboxes2) -> np.ndarray:132        """133        :param bboxes1: predict of bbox(N,4)(x1,y1,x2,y2)134        :param bboxes2: groundtruth of bbox(N,4)(x1,y1,x2,y2)135        :return:136        """137        # Ensure predict's bbox form138        bboxes2 = np.expand_dims(bboxes2, 0)139        bboxes1 = np.expand_dims(bboxes1, 1)140 141        xx1 = np.maximum(bboxes1[..., 0], bboxes2[..., 0])142        yy1 = np.maximum(bboxes1[..., 1], bboxes2[..., 1])143        xx2 = np.minimum(bboxes1[..., 2], bboxes2[..., 2])144        yy2 = np.minimum(bboxes1[..., 3], bboxes2[..., 3])145        w = np.maximum(0.0, xx2 - xx1)146        h = np.maximum(0.0, yy2 - yy1)147        wh = w * h  # Intersection area148 149        # Compute areas of individual boxes150        area1 = (bboxes1[..., 2] - bboxes1[..., 0]) * (bboxes1[..., 3] - bboxes1[..., 1])151        area2 = (bboxes2[..., 2] - bboxes2[..., 0]) * (bboxes2[..., 3] - bboxes2[..., 1])152 153        # Union area154        union_area = area1 + area2 - wh155 156        iou = wh / union_area157 158        xxc1 = np.minimum(bboxes1[..., 0], bboxes2[..., 0])159        yyc1 = np.minimum(bboxes1[..., 1], bboxes2[..., 1])160        xxc2 = np.maximum(bboxes1[..., 2], bboxes2[..., 2])161        yyc2 = np.maximum(bboxes1[..., 3], bboxes2[..., 3])162        wc = xxc2 - xxc1163        hc = yyc2 - yyc1164        assert (wc > 0).all() and (hc > 0).all()165        area_enclose = wc * hc  # Area of the smallest enclosing box166 167        # Corrected GIoU computation168        giou = iou - (area_enclose - union_area) / area_enclose169        giou = (giou + 1.0) / 2.0  # Resize from (-1,1) to (0,1)170        return giou171 172 173    def centroid_batch(self, bboxes1, bboxes2) -> np.ndarray:174        centroids1 = np.stack(((bboxes1[..., 0] + bboxes1[..., 2]) / 2,175                               (bboxes1[..., 1] + bboxes1[..., 3]) / 2), axis=-1)176        centroids2 = np.stack(((bboxes2[..., 0] + bboxes2[..., 2]) / 2,177                               (bboxes2[..., 1] + bboxes2[..., 3]) / 2), axis=-1)178 179        centroids1 = np.expand_dims(centroids1, 1)180        centroids2 = np.expand_dims(centroids2, 0)181 182        distances = np.sqrt(np.sum((centroids1 - centroids2) ** 2, axis=-1))183        norm_factor = np.sqrt(self.w ** 2 + self.h ** 2)184        normalized_distances = distances / norm_factor185 186        return 1 - normalized_distances187    188    def centroid_batch_obb(self, bboxes1, bboxes2) -> np.ndarray:189        centroids1 = np.stack((bboxes1[..., 0], bboxes1[..., 1]),axis=-1)190        centroids2 = np.stack((bboxes2[..., 0], bboxes2[..., 1]),axis=-1)191 192        centroids1 = np.expand_dims(centroids1, 1)193        centroids2 = np.expand_dims(centroids2, 0)194 195        distances = np.sqrt(np.sum((centroids1 - centroids2) ** 2, axis=-1))196        norm_factor = np.sqrt(self.w ** 2 + self.h ** 2)197        normalized_distances = distances / norm_factor198 199        return 1 - normalized_distances200    201    202    @staticmethod203    def ciou_batch(bboxes1, bboxes2) -> np.ndarray:204        """205        Calculate Complete Intersection over Union (CIoU) for batches of bounding boxes.206 207        :param bboxes1: Predicted bounding boxes of shape (N, 4) as (x1, y1, x2, y2)208        :param bboxes2: Ground truth bounding boxes of shape (N, 4) as (x1, y1, x2, y2)209        :return: CIoU scores scaled between 0 and 1210        """211        epsilon = 1e-7  # Small value to prevent division by zero212 213        # Expand dimensions for broadcasting214        bboxes2 = np.expand_dims(bboxes2, 0)  # Shape: (1, M, 4)215        bboxes1 = np.expand_dims(bboxes1, 1)  # Shape: (N, 1, 4)216 217        # Calculate the intersection box218        xx1 = np.maximum(bboxes1[..., 0], bboxes2[..., 0])219        yy1 = np.maximum(bboxes1[..., 1], bboxes2[..., 1])220        xx2 = np.minimum(bboxes1[..., 2], bboxes2[..., 2])221        yy2 = np.minimum(bboxes1[..., 3], bboxes2[..., 3])222        w = np.maximum(0.0, xx2 - xx1)223        h = np.maximum(0.0, yy2 - yy1)224        wh = w * h225 226        # Calculate IoU227        area1 = (bboxes1[..., 2] - bboxes1[..., 0]) * (bboxes1[..., 3] - bboxes1[..., 1])228        area2 = (bboxes2[..., 2] - bboxes2[..., 0]) * (bboxes2[..., 3] - bboxes2[..., 1])229        iou = wh / (area1 + area2 - wh + epsilon)230 231        # Calculate center points232        centerx1 = (bboxes1[..., 0] + bboxes1[..., 2]) / 2.0233        centery1 = (bboxes1[..., 1] + bboxes1[..., 3]) / 2.0234        centerx2 = (bboxes2[..., 0] + bboxes2[..., 2]) / 2.0235        centery2 = (bboxes2[..., 1] + bboxes2[..., 3]) / 2.0236 237        # Calculate squared center distance238        inner_diag = (centerx1 - centerx2) ** 2 + (centery1 - centery2) ** 2239 240        # Calculate smallest enclosing box diagonal241        xxc1 = np.minimum(bboxes1[..., 0], bboxes2[..., 0])242        yyc1 = np.minimum(bboxes1[..., 1], bboxes2[..., 1])243        xxc2 = np.maximum(bboxes1[..., 2], bboxes2[..., 2])244        yyc2 = np.maximum(bboxes1[..., 3], bboxes2[..., 3])245        outer_diag = (xxc2 - xxc1) ** 2 + (yyc2 - yyc1) ** 2 + epsilon246 247        # Calculate aspect ratio consistency248        w1 = bboxes1[..., 2] - bboxes1[..., 0]249        h1 = bboxes1[..., 3] - bboxes1[..., 1]250        w2 = bboxes2[..., 2] - bboxes2[..., 0]251        h2 = bboxes2[..., 3] - bboxes2[..., 1]252 253        # Prevent division by zero254        h2 = h2 + epsilon255        h1 = h1 + epsilon256        arctan_diff = np.arctan(w2 / h2) - np.arctan(w1 / h1)257        v = (4 / (np.pi ** 2)) * (arctan_diff ** 2)258 259        # Calculate alpha260        S = 1 - iou261        alpha = v / (S + v + epsilon)262 263        # Compute CIoU264        ciou = iou - (inner_diag / outer_diag) + (alpha * v)265 266        # Scale CIoU to [0, 1]267        return (ciou + 1) / 2.0268 269    270    def diou_batch(bboxes1, bboxes2) -> np.ndarray:271        """272        :param bbox_p: predict of bbox(N,4)(x1,y1,x2,y2)273        :param bbox_g: groundtruth of bbox(N,4)(x1,y1,x2,y2)274        :return:275        """276        # for details should go to https://arxiv.org/pdf/1902.09630.pdf277        # ensure predict's bbox form278        bboxes2 = np.expand_dims(bboxes2, 0)279        bboxes1 = np.expand_dims(bboxes1, 1)280 281        # calculate the intersection box282        xx1 = np.maximum(bboxes1[..., 0], bboxes2[..., 0])283        yy1 = np.maximum(bboxes1[..., 1], bboxes2[..., 1])284        xx2 = np.minimum(bboxes1[..., 2], bboxes2[..., 2])285        yy2 = np.minimum(bboxes1[..., 3], bboxes2[..., 3])286        w = np.maximum(0.0, xx2 - xx1)287        h = np.maximum(0.0, yy2 - yy1)288        wh = w * h289        iou = wh / (290            (bboxes1[..., 2] - bboxes1[..., 0]) * (bboxes1[..., 3] - bboxes1[..., 1]) +291            (bboxes2[..., 2] - bboxes2[..., 0]) * (bboxes2[..., 3] - bboxes2[..., 1]) -292            wh293        )294 295        centerx1 = (bboxes1[..., 0] + bboxes1[..., 2]) / 2.0296        centery1 = (bboxes1[..., 1] + bboxes1[..., 3]) / 2.0297        centerx2 = (bboxes2[..., 0] + bboxes2[..., 2]) / 2.0298        centery2 = (bboxes2[..., 1] + bboxes2[..., 3]) / 2.0299 300        inner_diag = (centerx1 - centerx2) ** 2 + (centery1 - centery2) ** 2301 302        xxc1 = np.minimum(bboxes1[..., 0], bboxes2[..., 0])303        yyc1 = np.minimum(bboxes1[..., 1], bboxes2[..., 1])304        xxc2 = np.maximum(bboxes1[..., 2], bboxes2[..., 2])305        yyc2 = np.maximum(bboxes1[..., 3], bboxes2[..., 3])306 307        outer_diag = (xxc2 - xxc1) ** 2 + (yyc2 - yyc1) ** 2308        diou = iou - inner_diag / outer_diag309 310        return (diou + 1) / 2.0 311    312 313    @staticmethod314    def run_asso_func(self, bboxes1, bboxes2):315        """316        Runs the selected association function (based on the initialization string) on the input bounding boxes.317        318        Parameters:319        bboxes1: First set of bounding boxes.320        bboxes2: Second set of bounding boxes.321        """322        return self.asso_func(bboxes1, bboxes2)323 324    def _get_asso_func(self, asso_mode):325        """326        Returns the corresponding association function based on the provided mode string.327        328        Parameters:329        asso_mode (str): The association function to use (e.g., "iou", "giou", "centroid", etc.).330        331        Returns:332        function: The appropriate function for the association calculation.333        """334        ASSO_FUNCS = {335            "iou": AssociationFunction.iou_batch,336            "iou_obb": AssociationFunction.iou_batch_obb,337            "hmiou": AssociationFunction.hmiou_batch,338            "giou": AssociationFunction.giou_batch,339            "ciou": AssociationFunction.ciou_batch,340            "diou": AssociationFunction.diou_batch,341            "centroid": self.centroid_batch,  # only not being staticmethod342            "centroid_obb": self.centroid_batch_obb343        }344 345        if self.asso_mode not in ASSO_FUNCS:346            raise ValueError(f"Invalid association mode: {self.asso_mode}. Choose from {list(ASSO_FUNCS.keys())}")347 348        return ASSO_FUNCS[self.asso_mode]349