大きな画像でオブジェクト検出アルゴリズムをテストする際に、検出された境界ボックスをグラウンドトゥルースの長方形に指定された座標と照合します。
Pascal VOCの課題によると、これは次のとおりです。
予測されたバウンディングボックスは、グラウンドトゥルースのバウンディングボックスと50%以上オーバーラップする場合は正しいと見なされます。それ以外の場合は、バウンディングボックスは誤検出と見なされます。複数の検出は罰せられます。システムが1つのグラウンドトゥルースバウンディングボックスと重複する複数のバウンディングボックスを予測する場合、1つの予測のみが正しいと見なされ、他の予測は誤検知と見なされます。
つまり、オーバーラップの割合を計算する必要があります。これは、グラウンドトゥルースボックスが検出された境界ボックスで50%カバーされていることを意味しますか?または、境界ボックスの50%がグラウンドトゥルースボックスによって吸収されますか?
私は検索しましたが、これに対する標準的なアルゴリズムは見つかりませんでした。これは、これがコンピュータービジョンではかなり一般的なものだと思っていたので、驚くべきことです。 (私はそれが初めてです)。見逃しましたか?このタイプの問題の標準アルゴリズムが何か知っている人はいますか?
概念的な答えがここにあることがわかりました: http://pascallin.ecs.soton.ac.uk/challenges/VOC/voc2012/htmldoc/devkit_doc.html#SECTION000540000000000000
このスレッドから: 2つの境界ボックスを相互に比較するMatlab
これをpythonでコーディングできるはずです!
軸に沿った境界ボックスの場合は、比較的簡単です。 「軸揃え」とは、境界ボックスが回転しないことを意味します。または言い換えると、ボックスの線は軸に平行です。 2つの軸に位置合わせされた境界ボックスの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
画像は この回答からのものです
スクリーン(ピクセル)座標で作業している場合、 トップ投票の回答 に数学的なエラーがあります!私は数週間前に、すべての読者が数学を理解できるように長い説明を添えて 編集 を提出しました。しかし、その編集はレビュアーに理解されずに削除されたため、同じ編集を再度送信しましたが、今回はより簡単に要約しました。 (更新: 拒否2vs1 「大幅な変更」と見なされたためです).
だから私はこの別の答えでBIG問題とその数学を完全に説明します。
だから、はい、一般的に、トップ投票の答えは正しいですし、IoUを計算する良い方法です。しかし、(他の人も指摘したように)その計算はコンピュータ画面では完全に正しくありません。 _(x2 - x1) * (y2 - y1)
_を実行することはできません。これは、正しい面積計算がまったく行われないためです。画面のインデックス作成は、ピクセル_0,0
_で始まり、_width-1,height-1
_で終わります。画面座標の範囲は_inclusive:inclusive
_(両端を含む)であるため、_0
_から_10
_までのピクセル座標の範囲は、_0 1 2 3 4 5 6 7 8 9 10
_が含まれるため、実際には11ピクセル幅です。 (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)
_
シンプルな方法
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%
になります。
交差距離については、+ 1を追加して、
intersection_area = (x_right - x_left + 1) * (y_bottom - y_top + 1)
(AABBと同じ)
これが好き pyimage search post
私は同意します(x_right-x_left)x(y_bottom-y_top)は点座標を使った数学で機能しますが、ピクセルを扱うので、私は違うと思います。
1Dの例を考えます。
-2点:x1 = 1およびx2 = 3、距離は確かにx2-x1 = 2
-インデックスの2ピクセル:i1 = 1およびi2 = 3、ピクセルi1からi2へのセグメントには3つのピクセルが含まれますl = i2-i1 + 1
このアプローチはどうですか?任意の数の結合された形状に拡張できます
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)
以下のスニペットでは、最初のボックスのエッジに沿ってポリゴンを作成しています。次に、Matplotlibを使用して、ポリゴンを2番目のボックスにクリップします。結果のポリゴンには4つの頂点が含まれますが、左上隅と右下隅のみに関心があるため、座標の最大値と最小値を取得して、ユーザーに返される境界ボックスを取得します。
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)