Расчет процента перекрытия ограничивающей рамки для оценки детектора изображения

При тестировании алгоритма обнаружения объектов на больших изображениях мы сверяем обнаруженные нами ограничивающие рамки с координатами, заданными для прямоугольников наземной истинности.

Согласно задачам Pascal VOC, это:

Прогнозируемая ограничивающая рамка считается правильной, если она перекрывается более чем на 50% с ограничивающей рамкой наземной истины, в противном случае ограничивающая рамка считается ложноположительным обнаружением. Множественные обнаружения наказываются. Если система предсказывает несколько ограничивающих прямоугольников, которые перекрываются с одним наземным ограничивающим прямоугольником, только один прогноз считается правильным, остальные считаются ложноположительными.

Это означает, что нам нужно рассчитать процент перекрытия. Означает ли это, что наземный блок истины на 50% покрыт обнаруженным ограничивающим блоком? Или что 50% ограничивающей рамки поглощается наземной рамкой истины?

Я искал, но не нашел для этого стандартного алгоритма - что удивительно, потому что я мог подумать, что это довольно распространенное явление в компьютерном зрении. (Я новичок в этом). Я это пропустил? Кто-нибудь знает, какой стандартный алгоритм для такого типа проблем?


person user961627    schedule 17.08.2014    source источник


Ответы (8)


Для ограничивающих прямоугольников, выровненных по оси, это относительно просто. «Выровнено по оси» означает, что ограничивающая рамка не поворачивается; или, другими словами, линии прямоугольников параллельны осям. Вот как рассчитать 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

Объяснение

введите описание изображения здесь введите описание изображения здесь

Изображения взяты из этого ответа

person Martin Thoma    schedule 18.03.2017
comment
В этом коде есть ошибка - y_top = max(bb1['y1'], bb2['y1']) следует использовать min. Аналогично y_bottom следует использовать max. - person James Meakin; 14.03.2018
comment
@JamesMeakin: Код правильный. y=0 находится вверху и увеличивается вниз. - person Cris Luengo; 26.06.2018
comment
Что делать, если ограничивающая рамка не прямоугольник? - person markroxor; 01.10.2018
comment
Тогда копипаст не будет работать. До сих пор в обнаружении у меня были только выровненные по оси ограничительные рамки. Для семантической сегментации существуют произвольные сложные формы. Но концепция та же. - person Martin Thoma; 01.10.2018
comment
@Chaine Я не уверен, что мне писать. Строки документации не отвечают на ваш вопрос? - person Martin Thoma; 02.10.2018
comment
@MartinThoma будет ли это работать для прямоугольника внутри другого прямоугольника? - person prb; 27.11.2018
comment
В коде действительно была ошибка, но не та, которую предложил Джеймс Микинг. Ошибка была вместо этого в вычислении площади, ЕСЛИ вы работаете с ПИКСЕЛЬНЫМИ КООРДИНАТАМИ. На экранах компьютеров используются пиксели / прямоугольники, которые начинаются с 0,0 (для верхней левой точки) и заканчиваются w-1, h-1. И координаты inclusive:inclusive. Это не работает с математикой, использованной в исходной функции. Я отправил отдельный ответ с фиксированной математикой и подробным объяснением, почему это исправление необходимо. Спасибо Мартину за оригинальную функцию. С исправлениями я теперь использую его в своем коде анализа AI / пикселей! ‹3 - person Mitch McMabers; 28.09.2019

В ответе, получившем наибольшее количество голосов, есть математическая ошибка, если вы работаете с координатами экрана (пикселей)! Несколько недель назад я отправил правку с подробным объяснением для всех читателей, чтобы они поняли математику. Но это изменение не было понято рецензентами и было удалено, поэтому я снова отправил то же изменение, но на этот раз более кратко. (Обновление: Отклонено 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, см. В ответе Джиндиля: 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)
person Mitch McMabers    schedule 26.09.2019

Простой способ для любого типа многоугольника.

example (Изображение не масштабировано)

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 находится слева внизу, а источник в компьютерной графике - слева вверху. Это различие не влияет на расчет IoU, но эта информация может оказаться полезной при выполнении других типов расчетов.

person Uzzal Podder    schedule 29.07.2019
comment
Приятно использовать библиотеку, в которой уже есть функции. Но я почти на 100% уверен, что этот код неверен: iou = poly_1.intersection(poly_2).area / poly_1.union(poly_2).area. Вы вычисляете площадь пересечения двух прямоугольников. И разделив на площадь объединения двух ящиков. Что ж, посмотрите на формулу индекса Жаккара (IoU). Правильная формула индекса Жаккара: iou = intersection_area / (union_area - intersection_area). - person Mitch McMabers; 10.09.2019
comment
На самом деле, оказывается, что функция объединения в Shapely уже игнорирует пересечение. Итак, ваш код правильный. Доказательство: poly_1.area и poly_2.area оба 2310. poly_1.union(poly_2).area равно 4059. poly_1.intersection(poly_2).area равно 561. И все доказать: 4059+561 == 2310+2310. Сумма обоих равна 4620. Итак, да, ваш код правильный и следует формуле Жаккара, потому что Shapely вычисляет его объединение минус пересечение. Отлично. - person Mitch McMabers; 10.09.2019
comment
В красной рамке на рисунке неправильно обозначены координаты двух нижних точек. Их следует поменять местами. - person totjammykd; 08.11.2019

Вы можете рассчитать с 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]])
person Keiku    schedule 01.02.2021

Для расстояния пересечения не следует добавлять +1, чтобы иметь

intersection_area = (x_right - x_left + 1) * (y_bottom - y_top + 1)   

(то же самое для AABB)
Как на этом поисковое сообщение pyimage

Я согласен, что (x_right - x_left) x (y_bottom - y_top) работает в математике с координатами точки, но поскольку мы имеем дело с пикселями, я думаю, что это другое.

Рассмотрим одномерный пример:

  • 2 балла: x1 = 1 и x2 = 3, расстояние действительно x2-x1 = 2
  • 2 пикселя индекса: i1 = 1 и i2 = 3, сегмент от пикселя i1 до i2 содержит 3 пикселя, т.е. l = i2 - i1 + 1

РЕДАКТИРОВАТЬ: я недавно узнал, что это небольшой квадратный подход.
Если, однако, вы рассматриваете пиксели как точечные образцы (т.е. угол ограничивающего прямоугольника будет в центре пикселя, как, очевидно, в matplotlib), тогда вы не нужен +1.
См. это комментарий и эту иллюстрацию

person Jindil    schedule 07.08.2018
comment
Вы правы ... Экран 1920x1080 индексируется от 0 (первый пиксель) до 1919 (последний пиксель по горизонтали) и от 0 (первый пиксель) до 1079 (последний пиксель по вертикали). Итак, если у нас есть прямоугольник в пиксельном координатном пространстве, чтобы вычислить его площадь, мы должны добавить 1 в каждом направлении. В противном случае представьте, что наш экран с разрешением 1920x1080 имеет полноэкранный прямоугольник с left=0,top=0,right=1919,bottom=1079. Мы знаем, что 1920x1080 пикселей - это 2073600 пикселей. Но с неправильной area = (x_right - x_left) * (y_bottom - y_top) математикой мы получаем: (1919 - 0) * (1079 - 0) = 1919 * 1079 = 2070601 пикселей! - person Mitch McMabers; 10.09.2019
comment
Я провел кучу тестов для проверки и теперь отправил правку принятого ответа на основе вашего правильного наблюдения. Спасибо! Интересно, сколько кодовых баз скопировали исходную, ошибочную математику за все эти годы. ;-) - person Mitch McMabers; 10.09.2019
comment
Было много проблем с утверждением исправленного редактирования, поэтому я опубликовал отдельный ответ на этой странице. Короткий ответ: вы правы. Диапазоны пикселей равны inclusive:inclusive, поэтому мы должны добавить + 1 к каждой оси, если нам нужна истинная область диапазона пикселей. - person Mitch McMabers; 28.09.2019

В приведенном ниже фрагменте я строю многоугольник по краям первого блока. Затем я использую Matplotlib, чтобы закрепить многоугольник на втором поле. Результирующий многоугольник содержит четыре вершины, но нас интересуют только верхний левый и нижний правый углы, поэтому я беру максимальное и минимальное значения координат, чтобы получить ограничивающую рамку, которая возвращается пользователю.

import numpy as np
from matplotlib import path, transforms

def clip_boxes(box0, box1):
    path_coords = np.array([[box0[0, 0], box0[0, 1]],
                            [box0[1, 0], box0[0, 1]],
                            [box0[1, 0], box0[1, 1]],
                            [box0[0, 0], box0[1, 1]]])

    poly = path.Path(np.vstack((path_coords[:, 0],
                                path_coords[:, 1])).T, closed=True)
    clip_rect = transforms.Bbox(box1)

    poly_clipped = poly.clip_to_bbox(clip_rect).to_polygons()[0]

    return np.array([np.min(poly_clipped, axis=0),
                     np.max(poly_clipped, axis=0)])

box0 = np.array([[0, 0], [1, 1]])
box1 = np.array([[0, 0], [0.5, 0.5]])

print clip_boxes(box0, box1)
person Stefan van der Walt    schedule 15.10.2014
comment
С точки зрения координат возвращаемое значение представляет: [[ x1 y1 ] [ x2 y2 ]], я прав? - person user961627; 16.10.2014
comment
И поля ввода должны соответствовать тому же представлению координат, не так ли? - person user961627; 16.10.2014
comment
Спасибо - уже давно пользуюсь нормально! Но теперь иногда возникает ошибка, я не знаю почему: stackoverflow.com/questions/26712637/ - person user961627; 03.11.2014

import numpy as np

def box_area(arr):
    # arr: np.array([[x1, y1, x2, y2]])
    width = arr[:, 2] - arr[:, 0]
    height = arr[:, 3] - arr[:, 1]
    return width * height

def _box_inter_union(arr1, arr2):
    # arr1 of [N, 4]
    # arr2 of [N, 4]
    area1 = box_area(arr1)
    area2 = box_area(arr2)

    # Intersection
    top_left = np.maximum(arr1[:, :2], arr2[:, :2]) # [[x, y]]
    bottom_right = np.minimum(arr1[:, 2:], arr2[:, 2:]) # [[x, y]]
    wh = bottom_right - top_left
    # clip: if boxes not overlap then make it zero
    intersection = wh[:, 0].clip(0) * wh[:, 1].clip(0)

    #union 
    union = area1 + area2 - intersection
    return intersection, union

def box_iou(arr1, arr2):
    # arr1[N, 4]
    # arr2[N, 4]
    # N = number of bounding boxes
    assert(arr1[:, 2:] > arr[:, :2]).all()
    assert(arr2[:, 2:] > arr[:, :2]).all()
    inter, union = _box_inter_union(arr1, arr2)
    iou = inter / union
    print(iou)
box1 = np.array([[10, 10, 80, 80]])
box2 = np.array([[20, 20, 100, 100]])
box_iou(box1, box2)

ссылка: https://pytorch.org/vision/stable/_modules/torchvision/ops/boxes.html#nms

person Sri Ganesh Tallapaneni    schedule 07.07.2021
comment
Хотя этот код может ответить на вопрос, здесь есть что прочитать, и нет описания того, что делает код (внешние ссылки не в счет!). Не могли бы вы добавить комментарий, чтобы помочь другим читателям? - person Simas Joneliunas; 08.07.2021

как насчет этого подхода? Может быть расширен до любого количества объединенных фигур

surface = np.zeros([1024,1024])
surface[1:1+10, 1:1+10] += 1
surface[100:100+500, 100:100+100] += 1
unionArea = (surface==2).sum()
print(unionArea)
person Reno Fiedler    schedule 08.10.2018
comment
Создание такой матрицы фиксированного размера и заполнение ее числами по смещению каждой формы кажется немного безумным. Попробуйте использовать библиотеку Shapely для Python. Он имеет вспомогательные функции для расчета пересечений и объединений различной формы. Я не пробовал делать с ним произвольные (не прямоугольные) формы, но, вероятно, это возможно. - person Mitch McMabers; 10.09.2019
comment
Под безумием я подразумеваю: медлительность и чрезмерная память. Библиотека Shapely обрабатывает сложные пересечения / вычисления площадей с использованием гораздо более умной математики и ярлыков, когда объекты совсем не находятся рядом друг с другом и т. Д. И да, я только что убедился, что Shapely идеально обрабатывает сложные формы, многоугольники, повернутые формы и т. - person Mitch McMabers; 10.09.2019