Intersection Over Union (IOU)#
Intersection Over Union (IOU) is a measure based on Jaccard Index that evaluates the overlap between two bounding boxes. It requires a ground truth bounding box \(B_{\text{y_true}}\) and a predicted bounding box \(B_{\text{y_pred}}\). By applying the IOU we can tell if a detection is valid (True Positive) or not (False Positive).
IOU is given by the overlapping area between the predicted bounding box and the ground truth bounding box divided by the area of union between them:
The image below illustrates the IOU between a ground truth bounding box (in green) and a detected bounding box (in red).

Confidence score is the probability that an anchor box contains an object. It is usually predicted by a classifier. In YOLO it is the objectness score.
IOU: This is the normal definition we know of which measures the overlap of the ground truth boxes and the predicted boxes.
threshold = 0.3
for each unique image amongst many images:
for each detection (bounding box predictions) that has a confidence score > threshold:
amongst the ground-truths bounding boxes in this current image, we choose the one ground-truth bounding box that has the highest IOU with this current detection bounding box, and this ground-truth bounding box has the same class as the predicted bounding box.
if no ground-truth can be chosen or IoU < threshold (e.g., 0.5):
the detection is a false positive
else:
the detection is a true positive
refer BELOW for implementation
import cv2
import numpy as np
import random
def bbox_iou(b1, b2):
"""Calculate the Intersection of Unions (IoUs) between bounding boxes.
Args:
b1 (np.ndarray): An ndarray containing N(x4) bounding boxes of shape (N, 4) in [xmin, ymin, xmax, ymax] format.
b2 (np.ndarray): An ndarray containing M(x4) bounding boxes of shape (N, 4) in [xmin, ymin, xmax, ymax] format.
Returns:
np.ndarray: An ndarray containing the IoUs of shape (N, 1)
"""
# 0 = np.convert_to_tensor(0.0, b1.dtype)
# b1 = b1.astype(np.float32)
# b2 = b2.astype(np.float32)
b1_xmin, b1_ymin, b1_xmax, b1_ymax = np.split(b1, 4, axis=-1)
b2_xmin, b2_ymin, b2_xmax, b2_ymax = np.split(b2, 4, axis=-1)
b1_height = np.maximum(0, b1_ymax - b1_ymin)
b1_width = np.maximum(0, b1_xmax - b1_xmin)
b2_height = np.maximum(0, b2_ymax - b2_ymin)
b2_width = np.maximum(0, b2_xmax - b2_xmin)
b1_area = b1_height * b1_width
b2_area = b2_height * b2_width
intersect_xmin = np.maximum(b1_xmin, b2_xmin)
intersect_ymin = np.maximum(b1_ymin, b2_ymin)
intersect_xmax = np.minimum(b1_xmax, b2_xmax)
intersect_ymax = np.minimum(b1_ymax, b2_ymax)
intersect_height = np.maximum(0, intersect_ymax - intersect_ymin)
intersect_width = np.maximum(0, intersect_xmax - intersect_xmin)
intersect_area = intersect_height * intersect_width
union_area = b1_area + b2_area - intersect_area
iou = np.nan_to_num(intersect_area/union_area).squeeze()
return iou
import sys
import unittest
import torch
#sys.path.append("ML/Pytorch/object_detection/metrics/")
#from iou import intersection_over_union
class TestIntersectionOverUnion(unittest.TestCase):
def setUp(self):
# test cases we want to run
self.t1_box1 = torch.tensor([0.8, 0.1, 0.2, 0.2])
self.t1_box2 = torch.tensor([0.9, 0.2, 0.2, 0.2])
self.t1_correct_iou = 1 / 7
self.t2_box1 = torch.tensor([0.95, 0.6, 0.5, 0.2])
self.t2_box2 = torch.tensor([0.95, 0.7, 0.3, 0.2])
self.t2_correct_iou = 3 / 13
self.t3_box1 = torch.tensor([0.25, 0.15, 0.3, 0.1])
self.t3_box2 = torch.tensor([0.25, 0.35, 0.3, 0.1])
self.t3_correct_iou = 0
self.t4_box1 = torch.tensor([0.7, 0.95, 0.6, 0.1])
self.t4_box2 = torch.tensor([0.5, 1.15, 0.4, 0.7])
self.t4_correct_iou = 3 / 31
self.t5_box1 = torch.tensor([0.5, 0.5, 0.2, 0.2])
self.t5_box2 = torch.tensor([0.5, 0.5, 0.2, 0.2])
self.t5_correct_iou = 1
# (x1,y1,x2,y2) format
self.t6_box1 = torch.tensor([2, 2, 6, 6])
self.t6_box2 = torch.tensor([4, 4, 7, 8])
self.t6_correct_iou = 4 / 24
self.t7_box1 = torch.tensor([0, 0, 2, 2])
self.t7_box2 = torch.tensor([3, 0, 5, 2])
self.t7_correct_iou = 0
self.t8_box1 = torch.tensor([0, 0, 2, 2])
self.t8_box2 = torch.tensor([0, 3, 2, 5])
self.t8_correct_iou = 0
self.t9_box1 = torch.tensor([0, 0, 2, 2])
self.t9_box2 = torch.tensor([2, 0, 5, 2])
self.t9_correct_iou = 0
self.t10_box1 = torch.tensor([0, 0, 2, 2])
self.t10_box2 = torch.tensor([1, 1, 3, 3])
self.t10_correct_iou = 1 / 7
self.t11_box1 = torch.tensor([0, 0, 3, 2])
self.t11_box2 = torch.tensor([1, 1, 3, 3])
self.t11_correct_iou = 0.25
self.t12_bboxes1 = torch.tensor(
[
[0, 0, 2, 2],
[0, 0, 2, 2],
[0, 0, 2, 2],
[0, 0, 2, 2],
[0, 0, 2, 2],
[0, 0, 3, 2],
]
)
self.t12_bboxes2 = torch.tensor(
[
[3, 0, 5, 2],
[3, 0, 5, 2],
[0, 3, 2, 5],
[2, 0, 5, 2],
[1, 1, 3, 3],
[1, 1, 3, 3],
]
)
self.t12_correct_ious = torch.tensor([0, 0, 0, 0, 1 / 7, 0.25])
# Accept if the difference in iou is small
self.epsilon = 0.001
def test_both_inside_cell_shares_area(self):
iou = bbox_iou(self.t1_box1, self.t1_box2, box_format="midpoint")
self.assertTrue((torch.abs(iou - self.t1_correct_iou) < self.epsilon))
def test_partially_outside_cell_shares_area(self):
iou = bbox_iou(self.t2_box1, self.t2_box2, box_format="midpoint")
self.assertTrue((torch.abs(iou - self.t2_correct_iou) < self.epsilon))
def test_both_inside_cell_shares_no_area(self):
iou = bbox_iou(self.t3_box1, self.t3_box2, box_format="midpoint")
self.assertTrue((torch.abs(iou - self.t3_correct_iou) < self.epsilon))
def test_midpoint_outside_cell_shares_area(self):
iou = bbox_iou(self.t4_box1, self.t4_box2, box_format="midpoint")
self.assertTrue((torch.abs(iou - self.t4_correct_iou) < self.epsilon))
def test_both_inside_cell_shares_entire_area(self):
iou = bbox_iou(self.t5_box1, self.t5_box2, box_format="midpoint")
self.assertTrue((torch.abs(iou - self.t5_correct_iou) < self.epsilon))
def test_box_format_x1_y1_x2_y2(self):
iou = bbox_iou(self.t6_box1, self.t6_box2, box_format="corners")
self.assertTrue((torch.abs(iou - self.t6_correct_iou) < self.epsilon))
def test_additional_and_batch(self):
ious = bbox_iou(
self.t12_bboxes1, self.t12_bboxes2, box_format="corners"
)
all_true = torch.all(
torch.abs(self.t12_correct_ious - ious.squeeze(1)) < self.epsilon
)
self.assertTrue(all_true)
if __name__ == "__main__":
print("Running Intersection Over Union Tests:")
unittest.main(argv=['ignored', '-v'], exit=False)
test_additional_and_batch (__main__.TestIntersectionOverUnion.test_additional_and_batch) ...
ERROR
test_both_inside_cell_shares_area (__main__.TestIntersectionOverUnion.test_both_inside_cell_shares_area) ...
ERROR
test_both_inside_cell_shares_entire_area (__main__.TestIntersectionOverUnion.test_both_inside_cell_shares_entire_area) ...
ERROR
test_both_inside_cell_shares_no_area (__main__.TestIntersectionOverUnion.test_both_inside_cell_shares_no_area) ...
ERROR
test_box_format_x1_y1_x2_y2 (__main__.TestIntersectionOverUnion.test_box_format_x1_y1_x2_y2) ...
ERROR
test_midpoint_outside_cell_shares_area (__main__.TestIntersectionOverUnion.test_midpoint_outside_cell_shares_area) ...
ERROR
test_partially_outside_cell_shares_area (__main__.TestIntersectionOverUnion.test_partially_outside_cell_shares_area) ...
ERROR
======================================================================
ERROR: test_additional_and_batch (__main__.TestIntersectionOverUnion.test_additional_and_batch)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3028/1496942041.py", line 107, in test_additional_and_batch
ious = bbox_iou(
self.t12_bboxes1, self.t12_bboxes2, box_format="corners"
)
TypeError: bbox_iou() got an unexpected keyword argument 'box_format'
======================================================================
ERROR: test_both_inside_cell_shares_area (__main__.TestIntersectionOverUnion.test_both_inside_cell_shares_area)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3028/1496942041.py", line 83, in test_both_inside_cell_shares_area
iou = bbox_iou(self.t1_box1, self.t1_box2, box_format="midpoint")
TypeError: bbox_iou() got an unexpected keyword argument 'box_format'
======================================================================
ERROR: test_both_inside_cell_shares_entire_area (__main__.TestIntersectionOverUnion.test_both_inside_cell_shares_entire_area)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3028/1496942041.py", line 99, in test_both_inside_cell_shares_entire_area
iou = bbox_iou(self.t5_box1, self.t5_box2, box_format="midpoint")
TypeError: bbox_iou() got an unexpected keyword argument 'box_format'
======================================================================
ERROR: test_both_inside_cell_shares_no_area (__main__.TestIntersectionOverUnion.test_both_inside_cell_shares_no_area)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3028/1496942041.py", line 91, in test_both_inside_cell_shares_no_area
iou = bbox_iou(self.t3_box1, self.t3_box2, box_format="midpoint")
TypeError: bbox_iou() got an unexpected keyword argument 'box_format'
======================================================================
ERROR: test_box_format_x1_y1_x2_y2 (__main__.TestIntersectionOverUnion.test_box_format_x1_y1_x2_y2)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3028/1496942041.py", line 103, in test_box_format_x1_y1_x2_y2
iou = bbox_iou(self.t6_box1, self.t6_box2, box_format="corners")
TypeError: bbox_iou() got an unexpected keyword argument 'box_format'
======================================================================
ERROR: test_midpoint_outside_cell_shares_area (__main__.TestIntersectionOverUnion.test_midpoint_outside_cell_shares_area)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3028/1496942041.py", line 95, in test_midpoint_outside_cell_shares_area
iou = bbox_iou(self.t4_box1, self.t4_box2, box_format="midpoint")
TypeError: bbox_iou() got an unexpected keyword argument 'box_format'
======================================================================
ERROR: test_partially_outside_cell_shares_area (__main__.TestIntersectionOverUnion.test_partially_outside_cell_shares_area)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/ipykernel_3028/1496942041.py", line 87, in test_partially_outside_cell_shares_area
iou = bbox_iou(self.t2_box1, self.t2_box2, box_format="midpoint")
TypeError: bbox_iou() got an unexpected keyword argument 'box_format'
----------------------------------------------------------------------
Ran 7 tests in 0.010s
FAILED (errors=7)
Running Intersection Over Union Tests: