计算图像检测器评估中边界框重叠的百分比

62 投票
9 回答
123430 浏览
提问于 2025-04-18 17:34

在测试一个物体检测算法时,我们会把算法检测到的边框和真实的边框坐标进行对比。

根据Pascal VOC的挑战规则,有以下规定:

如果一个预测的边框与真实边框重叠超过50%,那么这个预测就被认为是正确的;如果重叠不超过50%,那么这个预测就被认为是错误的检测(假阳性)。如果系统预测了多个边框与同一个真实边框重叠,那么只有一个预测会被认为是正确的,其他的都会被视为假阳性。

这就意味着我们需要计算重叠的百分比。那么,这是否意味着真实的边框被检测到的边框覆盖了50%?还是说检测到的边框有50%被真实边框覆盖了?

我查找过,但没有找到一个标准的算法来解决这个问题——这让我感到惊讶,因为我以为这是计算机视觉中很常见的事情。(我对这个领域还很陌生)。我是不是漏掉了什么?有没有人知道解决这类问题的标准算法是什么?

9 个回答

7

关于交集的距离,难道我们不应该加个 +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 = 1x2 = 3,它们之间的距离确实是 x2-x1 = 2
  • 两个像素的索引:i1 = 1i2 = 3,从像素 i1 到 i2 的区间包含了 3 个像素,也就是 l = i2 - i1 + 1

补充:我最近了解到这是一种“微小方块”的方法。
不过如果你把像素看作是点样本(也就是说边界框的角落位于像素的中心,就像在 matplotlib 中那样),那么就不需要加 +1 了。
可以参考 这个评论这个插图

35

你可以使用 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]])
37

这是一个简单的方法,适用于任何类型的多边形

示例

(图片没有按比例绘制)

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的计算,但如果你进行其他类型的计算,这些信息可能会有帮助。

51

这个点赞最高的回答在处理屏幕(像素)坐标时有个数学错误!几周前我提交了一个编辑,里面详细解释了这个数学问题,希望大家能理解。但审阅者没有理解我的编辑,所以把它删掉了。我这次又提交了一次,但这次简化了一些内容。(更新:被拒绝了2比1,因为被认为是“重大更改”,哈哈)。

所以,我将在这里详细解释这个数学问题。

总的来说,点赞最高的回答是正确的,确实是计算IoU的好方法。但(正如其他人也指出的)它的数学在计算机屏幕上完全不对。你不能简单地用(x2 - x1) * (y2 - y1),因为这样计算出的面积是错误的。屏幕的坐标从像素0,0开始,到width-1,height-1结束。屏幕坐标的范围是inclusive:inclusive(两端都包含),所以从010的像素坐标实际上是11个像素宽,因为它包含了0 1 2 3 4 5 6 7 8 9 10(总共11个项)。因此,要计算屏幕坐标的面积,你必须在每个维度上加上1,计算公式为:(x2 - x1 + 1) * (y2 - y1 + 1)

如果你在其他坐标系统中工作,范围不是包含的(比如inclusive:exclusive系统,其中010表示“元素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)
98

对于轴对齐的边界框来说,计算起来相对简单。“轴对齐”意味着这个边界框没有旋转;换句话说,边界框的边是和坐标轴平行的。下面是如何计算两个轴对齐边界框的交并比(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

解释

在这里输入图片描述 在这里输入图片描述

这些图片来自 这个回答

撰写回答