web-dev-qa-db-fra.com

Calcul du pourcentage de chevauchement du cadre de délimitation, pour l'évaluation du détecteur d'image

En testant un algorithme de détection d'objets dans de grandes images, nous vérifions nos boîtes de délimitation détectées par rapport aux coordonnées données pour les rectangles de vérité au sol.

Selon les défis Pascal VOC, il y a ceci:

Une zone de délimitation prédite est considérée comme correcte si elle chevauche plus de 50% avec une zone de délimitation de la vérité du sol, sinon la zone de délimitation est considérée comme une détection de faux positifs. Les détections multiples sont pénalisées. Si un système prédit plusieurs boîtes englobantes qui se chevauchent avec une seule boîte englobante de la vérité au sol, une seule prédiction est considérée comme correcte, les autres sont considérées comme des faux positifs.

Cela signifie que nous devons calculer le pourcentage de chevauchement. Cela signifie-t-il que la boîte de vérité au sol est couverte à 50% par la boîte limite détectée? Ou que 50% de la boîte englobante est absorbée par la boîte de vérité au sol?

J'ai cherché mais je n'ai pas trouvé d'algorithme standard pour cela - ce qui est surprenant car j'aurais pensé que c'est quelque chose d'assez commun en vision par ordinateur. (Je suis nouveau dans ce domaine). L'ai-je manqué? Quelqu'un sait-il quel est l'algorithme standard pour ce type de problème?

14
user961627

J'ai trouvé que la réponse conceptuelle est ici: http://pascallin.ecs.soton.ac.uk/challenges/VOC/voc2012/htmldoc/devkit_doc.html#SECTION000540000000000000

à partir de ce fil: Comparez deux boîtes englobantes avec Matlab

Je devrais être capable de coder cela en python!

1
user961627

Pour les boîtes englobantes alignées sur l'axe, c'est relativement simple. "Axe aligné" signifie que le cadre de sélection n'est pas tourné; ou en d'autres termes que les lignes des cases sont parallèles aux axes. Voici comment calculer l'IoU de deux boîtes englobantes alignées sur l'axe.

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

Explication

enter image description hereenter image description here

Les images sont de cette réponse

40
Martin Thoma

réponse la plus votée a une erreur mathématique si vous travaillez avec des coordonnées d'écran (pixel)! J'ai soumis ne modification il y a quelques semaines avec une longue explication pour tous les lecteurs afin qu'ils comprennent les mathématiques. Mais cette modification n'a pas été comprise par les réviseurs et a été supprimée, j'ai donc soumis à nouveau la même modification, mais plus brièvement résumée cette fois. (Mise à jour: Rejeté 2vs1 car cela a été considéré comme un "changement substantiel", heh).

Je vais donc expliquer complètement le GRAND problème avec ses mathématiques ici dans cette réponse séparée.

Donc, oui, en général, la réponse la plus votée est correcte et constitue un bon moyen de calculer l'IoU. Mais (comme d'autres l'ont également souligné), ses calculs sont complètement incorrects pour les écrans d'ordinateur. Vous ne pouvez pas simplement faire (x2 - x1) * (y2 - y1), Car cela ne produira en aucun cas les calculs de zone corrects. L'indexation d'écran commence au pixel 0,0 Et se termine à width-1,height-1. La plage de coordonnées d'écran est inclusive:inclusive (Y compris aux deux extrémités), donc une plage de 0 À 10 En coordonnées pixel est en fait de 11 pixels de large, car elle comprend 0 1 2 3 4 5 6 7 8 9 10 (11 objets). Donc, pour calculer l'aire des coordonnées d'écran, vous DEVEZ donc ajouter +1 à chaque dimension, comme suit: (x2 - x1 + 1) * (y2 - y1 + 1).

Si vous travaillez dans un autre système de coordonnées où la plage n'est pas inclusive (comme un système inclusive:exclusive0 À 10 Signifie "éléments 0-9 mais pas 10 "), ces calculs supplémentaires ne seraient PAS nécessaires. Mais très probablement, vous traitez des boîtes englobantes basées sur les pixels. Eh bien, les coordonnées de l'écran commencent à 0,0 Et augmentent à partir de là.

Un écran 1920x1080 Est indexé de 0 (Premier pixel) à 1919 (Dernier pixel horizontalement) et de 0 (Premier pixel) à 1079 (dernier pixel verticalement).

Donc, si nous avons un rectangle dans "l'espace de coordonnées en pixels", pour calculer sa surface, nous devons ajouter 1 dans chaque direction. Sinon, nous obtenons la mauvaise réponse pour le calcul de l'aire.

Imaginez que notre écran 1920x1080 A un rectangle basé sur les coordonnées de pixels avec left=0,top=0,right=1919,bottom=1079 (Couvrant tous les pixels sur tout l'écran).

Eh bien, nous savons que 1920x1080 Pixels est 2073600 Pixels, ce qui est la zone correcte d'un écran 1080p.

Mais avec une mauvaise math area = (x_right - x_left) * (y_bottom - y_top), nous obtiendrions: (1919 - 0) * (1079 - 0) = 1919 * 1079 = 2070601 Pixels! C'est faux!

C'est pourquoi nous devons ajouter +1 À chaque calcul, ce qui nous donne le calcul corrigé suivant: area = (x_right - x_left + 1) * (y_bottom - y_top + 1), nous donnant: (1919 - 0 + 1) * (1079 - 0 + 1) = 1920 * 1080 = 2073600 Pixels! Et c'est bien la bonne réponse!

Le résumé le plus court possible est le suivant: les plages de coordonnées de pixels sont inclusive:inclusive, Nous devons donc ajouter + 1 À chaque axe si nous voulons la zone réelle d'une plage de coordonnées de pixels.

Pour plus de détails sur la raison pour laquelle +1 Est nécessaire, voir la réponse de Jindil: https://stackoverflow.com/a/51730512/8874388

Ainsi que cet article pyimagesearch: https://www.pyimagesearch.com/2016/11/07/intersection-over-union-iou-for-object-detection/

Et ce commentaire GitHub: https://github.com/AlexeyAB/darknet/issues/3995#issuecomment-535697357

Étant donné que le calcul fixe n'a pas été approuvé, toute personne qui copie le code de la réponse la plus votée voit, espérons-le, cette réponse et pourra la corriger elle-même, en copiant simplement les assertions corrigées et les lignes de calcul de zone ci-dessous, qui ont été corrigé pour les plages de coordonnées inclusive:inclusive (pixel):

    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)
10
Mitch McMabers

Une manière simple

example (L'image n'est pas dessinée à l'échelle)

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))

Le résultat sera 0.138211... ce qui signifie 13.82%.

5
Uzzal Podder

Pour la distance d'intersection, ne faut-il pas ajouter un +1 pour avoir

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

(idem pour l'AABB)
Comme sur ce poste de recherche pyimage

Je suis d'accord (x_right - x_left) x (y_bottom - y_top) fonctionne en mathématiques avec des coordonnées ponctuelles, mais comme nous traitons avec des pixels, je pense que c'est différent.

Prenons un exemple 1D:
- 2 points: x1 = 1 et x2 = 3 , la distance est en effet x2-x1 = 2
- 2 pixels d'index: i1 = 1 et i2 = 3 , le segment du pixel i1 à i2 contient 3 pixels soit l = i2 - i1 + 1

2
Jindil

que diriez-vous de cette approche? Peut être étendu à n'importe quel nombre de formes réunies

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)
0
Reno Fiedler

Dans l'extrait ci-dessous, je construis un polygone le long des bords de la première boîte. J'utilise ensuite Matplotlib pour découper le polygone dans la deuxième case. Le polygone résultant contient quatre sommets, mais nous ne nous intéressons qu'aux coins supérieur gauche et inférieur droit, donc je prends le max et le min des coordonnées pour obtenir un cadre de délimitation, qui est retourné à l'utilisateur.

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)
0