计算图像检测器评估中边界框重叠的百分比
在测试一个物体检测算法时,我们会把算法检测到的边框和真实的边框坐标进行对比。
根据Pascal VOC的挑战规则,有以下规定:
如果一个预测的边框与真实边框重叠超过50%,那么这个预测就被认为是正确的;如果重叠不超过50%,那么这个预测就被认为是错误的检测(假阳性)。如果系统预测了多个边框与同一个真实边框重叠,那么只有一个预测会被认为是正确的,其他的都会被视为假阳性。
这就意味着我们需要计算重叠的百分比。那么,这是否意味着真实的边框被检测到的边框覆盖了50%?还是说检测到的边框有50%被真实边框覆盖了?
我查找过,但没有找到一个标准的算法来解决这个问题——这让我感到惊讶,因为我以为这是计算机视觉中很常见的事情。(我对这个领域还很陌生)。我是不是漏掉了什么?有没有人知道解决这类问题的标准算法是什么?
9 个回答
关于交集的距离,难道我们不应该加个 +1 吗,这样就可以得到
intersection_area = (x_right - x_left + 1) * (y_bottom - y_top + 1)
(AABB也是一样)
就像在这个 pyimage search 的帖子中提到的那样。
我同意 (x_right - x_left) x (y_bottom - y_top) 在数学上是适用的,尤其是处理点坐标时,但因为我们处理的是像素,所以我觉得情况有所不同。
考虑一个一维的例子:
- 两个点:x1 = 1 和 x2 = 3,它们之间的距离确实是 x2-x1 = 2
- 两个像素的索引:i1 = 1 和 i2 = 3,从像素 i1 到 i2 的区间包含了 3 个像素,也就是 l = i2 - i1 + 1
补充:我最近了解到这是一种“微小方块”的方法。
不过如果你把像素看作是点样本(也就是说边界框的角落位于像素的中心,就像在 matplotlib 中那样),那么就不需要加 +1 了。
可以参考 这个评论和 这个插图
你可以使用 torchvision
来进行计算。这里的边界框(bbox)需要按照 [x1, y1, x2, y2]
这种格式来准备。
import torch
import torchvision.ops.boxes as bops
box1 = torch.tensor([[511, 41, 577, 76]], dtype=torch.float)
box2 = torch.tensor([[544, 59, 610, 94]], dtype=torch.float)
iou = bops.box_iou(box1, box2)
# tensor([[0.1382]])
这是一个简单的方法,适用于任何类型的多边形。
(图片没有按比例绘制)from shapely.geometry import Polygon
def calculate_iou(box_1, box_2):
poly_1 = Polygon(box_1)
poly_2 = Polygon(box_2)
iou = poly_1.intersection(poly_2).area / poly_1.union(poly_2).area
return iou
box_1 = [[511, 41], [577, 41], [577, 76], [511, 76]]
box_2 = [[544, 59], [610, 59], [610, 94], [544, 94]]
print(calculate_iou(box_1, box_2))
计算的结果是 0.138211...
,这意味着 13.82%
。
如果你的形状是矩形的,可以使用 shapely.geometry.box
,格式是 [最小x, 最小y, 最大x, 最大y]。
注意:在shapely库中,坐标系的原点在左下角,而计算机图形学中的原点在左上角。这种差异不会影响IoU的计算,但如果你进行其他类型的计算,这些信息可能会有帮助。
这个点赞最高的回答在处理屏幕(像素)坐标时有个数学错误!几周前我提交了一个编辑,里面详细解释了这个数学问题,希望大家能理解。但审阅者没有理解我的编辑,所以把它删掉了。我这次又提交了一次,但这次简化了一些内容。(更新:被拒绝了2比1,因为被认为是“重大更改”,哈哈)。
所以,我将在这里详细解释这个数学问题。
总的来说,点赞最高的回答是正确的,确实是计算IoU的好方法。但(正如其他人也指出的)它的数学在计算机屏幕上完全不对。你不能简单地用(x2 - x1) * (y2 - y1)
,因为这样计算出的面积是错误的。屏幕的坐标从像素0,0
开始,到width-1,height-1
结束。屏幕坐标的范围是inclusive:inclusive
(两端都包含),所以从0
到10
的像素坐标实际上是11个像素宽,因为它包含了0 1 2 3 4 5 6 7 8 9 10
(总共11个项)。因此,要计算屏幕坐标的面积,你必须在每个维度上加上1,计算公式为:(x2 - x1 + 1) * (y2 - y1 + 1)
。
如果你在其他坐标系统中工作,范围不是包含的(比如inclusive:exclusive
系统,其中0
到10
表示“元素0-9,但不包括10”),那么这个额外的计算就不需要了。但大多数情况下,你是在处理基于像素的边界框。屏幕坐标是从0,0
开始的。
一个1920x1080
的屏幕从0
(第一个像素)到1919
(最后一个水平像素),从0
(第一个像素)到1079
(最后一个垂直像素)。
所以如果我们在“像素坐标空间”中有一个矩形,要计算它的面积,我们必须在每个方向上加1。否则,我们会得到错误的面积计算结果。
想象一下,我们的1920x1080
屏幕上有一个基于像素坐标的矩形,left=0,top=0,right=1919,bottom=1079
(覆盖了整个屏幕的所有像素)。
我们知道1920x1080
像素是2073600
个像素,这是1080p屏幕的正确面积。
但如果用错误的计算方法area = (x_right - x_left) * (y_bottom - y_top)
,我们会得到:(1919 - 0) * (1079 - 0)
= 1919 * 1079
= 2070601
个像素!这是错误的!
这就是为什么我们必须在每个计算中加+1
,这样我们得到的正确计算是:area = (x_right - x_left + 1) * (y_bottom - y_top + 1)
,结果是:(1919 - 0 + 1) * (1079 - 0 + 1)
= 1920 * 1080
= 2073600
个像素!这确实是正确的答案!
最简短的总结是:像素坐标范围是inclusive:inclusive
,所以如果我们想要得到像素坐标范围的真实面积,就必须在每个轴上加+ 1
。
关于为什么需要+1
的更多细节,可以参考Jindil的回答:https://stackoverflow.com/a/51730512/8874388
还有这篇pyimagesearch的文章: https://www.pyimagesearch.com/2016/11/07/intersection-over-union-iou-for-object-detection/
以及这个GitHub评论: https://github.com/AlexeyAB/darknet/issues/3995#issuecomment-535697357
由于修正的数学没有被批准,希望从点赞最高的回答中复制代码的人能看到这个回答,并能自己修复这个问题,只需复制下面修正后的断言和面积计算行,这些都是针对inclusive:inclusive
(像素)坐标范围修正的:
assert bb1['x1'] <= bb1['x2']
assert bb1['y1'] <= bb1['y2']
assert bb2['x1'] <= bb2['x2']
assert bb2['y1'] <= bb2['y2']
................................................
# The intersection of two axis-aligned bounding boxes is always an
# axis-aligned bounding box.
# NOTE: We MUST ALWAYS add +1 to calculate area when working in
# screen coordinates, since 0,0 is the top left pixel, and w-1,h-1
# is the bottom right pixel. If we DON'T add +1, the result is wrong.
intersection_area = (x_right - x_left + 1) * (y_bottom - y_top + 1)
# compute the area of both AABBs
bb1_area = (bb1['x2'] - bb1['x1'] + 1) * (bb1['y2'] - bb1['y1'] + 1)
bb2_area = (bb2['x2'] - bb2['x1'] + 1) * (bb2['y2'] - bb2['y1'] + 1)
对于轴对齐的边界框来说,计算起来相对简单。“轴对齐”意味着这个边界框没有旋转;换句话说,边界框的边是和坐标轴平行的。下面是如何计算两个轴对齐边界框的交并比(IoU)。
def get_iou(bb1, bb2):
"""
Calculate the Intersection over Union (IoU) of two bounding boxes.
Parameters
----------
bb1 : dict
Keys: {'x1', 'x2', 'y1', 'y2'}
The (x1, y1) position is at the top left corner,
the (x2, y2) position is at the bottom right corner
bb2 : dict
Keys: {'x1', 'x2', 'y1', 'y2'}
The (x, y) position is at the top left corner,
the (x2, y2) position is at the bottom right corner
Returns
-------
float
in [0, 1]
"""
assert bb1['x1'] < bb1['x2']
assert bb1['y1'] < bb1['y2']
assert bb2['x1'] < bb2['x2']
assert bb2['y1'] < bb2['y2']
# determine the coordinates of the intersection rectangle
x_left = max(bb1['x1'], bb2['x1'])
y_top = max(bb1['y1'], bb2['y1'])
x_right = min(bb1['x2'], bb2['x2'])
y_bottom = min(bb1['y2'], bb2['y2'])
if x_right < x_left or y_bottom < y_top:
return 0.0
# The intersection of two axis-aligned bounding boxes is always an
# axis-aligned bounding box
intersection_area = (x_right - x_left) * (y_bottom - y_top)
# compute the area of both AABBs
bb1_area = (bb1['x2'] - bb1['x1']) * (bb1['y2'] - bb1['y1'])
bb2_area = (bb2['x2'] - bb2['x1']) * (bb2['y2'] - bb2['y1'])
# compute the intersection over union by taking the intersection
# area and dividing it by the sum of prediction + ground-truth
# areas - the interesection area
iou = intersection_area / float(bb1_area + bb2_area - intersection_area)
assert iou >= 0.0
assert iou <= 1.0
return iou
解释
这些图片来自 这个回答