pythonでGIoU (Generalized Intersection over Union)の計算方法を実装する方法を紹介します。GIoUは従来のIoU (Intersection over Union)を一般化した概念で、IoUは物体検出AIで出力される複数の矩形の重なり具合を表した指標です。
GIoUでは、2つの矩形の重なり具合だけでなく重なりが無かった場合の矩形間の距離も考慮した指標になっています。
この記事でできること
pythonでGIoU (Generalized Intersection over Union)を計算できる実装例を紹介します。
GIoUは従来のIoU (Intersection over Union)を一般化した概念です。GIoUはIoUと同様に2つの矩形(長方形)の重なりの大きさを表す指標であると同時に、重なりが無い場合に2つの矩形がどのくらい離れているかを表す指標にもなっています。
2つの矩形の重なり具合を表すIoUは、2つの矩形に重なりが無い場合にはいつも0となってしまいます。GIoUでは、2つの矩形に重なりが無い場合に、その重なりのなさ具合も定量化した指標です。
なお、従来のIoU (Intersection over Union)の実装例は次の記事で紹介しました。
GIoU(Generalized Intersection over Union)とは
まずはGIoUの定義について説明します。GIoUは次の論文で提案されました。
Rezatofighi, H.; Tsoi, N.; Gwak, J.; Sadeghian, A.; Reid, I.; and Savarese, S. 2019. Generalized intersection over union: A metric and a loss for bounding box regression. In Proceedings of the IEEE Conference on Computer Vision and Pattern Recognition, 658–666.
https://arxiv.org/abs/1902.09630
GIoUを使うと何が良いかというところですが、物体検出AIの損失(loss)関数として従来のIoUを使った場合、矩形の重なりが無い時の”重なりが無い具合”を定量化できませんでした。
IoUでは重なりが無い場合にはIoUは0となり、2つの矩形がどれだけ離れていても、その距離を表現できていないからです。GIoUでは、2つの矩形の”重なりが無い具合”を定量化することができます。
上記の提案論文では、YOLOv3、Faster R-CNN、Mask R-CNNといった物体検出AIの損失関数にGIoUを使用した実験結果が記載されており、IoUを用いるよりも検出精度 (mAP)が上回ることや、YOLOv3等で従来使用されている損失関数に対する優位性が主張されています。
また、より最近に提案されたアンカーフリー系の物体検出AIであるTTFNet (Training-Time-Friendly Network)でも使われています。
GIoUの定義
従来のIoUでは、2つの矩形A, Bの重なり部分Intersection (積集合: A∩B)と、2つの矩形A, Bを合わせた部分Union (和集合: A∪B)の面積で計算しました。GIoUでは、さらに矩形A, Bを囲む最小面積のconvex shape (凸形)の面積を使って計算します。
ですが、難しいことは考えず、矩形A, Bを囲む最小の四角形を考えれば十分のようです。提案論文記載の疑似コードではそのようになっていました(矩形A, Bを囲む面積最小の凸形(凸多角形等)は必ずしもそうではないとは思うのですが)。矩形A, Bを囲んでいる四角形のうちの、面積最小となる四角形を考えればOKです。
次の図を見れば一目瞭然だと思います。

この図で、矩形AとBを含めた点線で囲まれた部分がconvex shape (凸形)となります。
単純ですね。
さて、GIoUの計算式ですが、従来のIoUに対して、convex shapeの面積からUnionを引いた値とconvex shapeの面積の比率を引いた値がGIoUです。
\[
GIoU = IoU – \frac{Convex \ \ shape \ – \ Union}{Convex \ \ shape}
\]
念のためですが、IoUは次の式で計算されます。
\[
IoU = \frac{Intersection}{Union}
\]
矩形間の距離について
さて、最初にGIoUを使用するメリットとして、2つの矩形間の距離を考慮した指標であると言いました。
次の図は2つの矩形A, Bが重ならない場合の例です。

この矩形A, BのGIoUは次で計算できます。”Union”はAとBの面積の合計です。”Intersection”は0ですので、IoUは0ですね。2つめの項の分子\(C \ – \ Union\)は黄色の部分の面積です。
\[
GIoU = \frac{Intersection}{Union} – \frac{C \ – \ Union}{C} = \frac{C \ – \ Union}{C}
\]
この式からわかるように、GIoUはCの面積が大きいほど小さくなり、Cの面積は矩形AとBの距離が離れるほど大きくなります。イメージとしては、次の図のように上の図よりももっと矩形A, Bが離れた場合です。

これらの図では、矩形AとBの大きさが変わっていません (見え方はお手元の環境で違うかもしれませんが、そういうことにさせて下さい)。ですが、AとBの距離が離れるほどCの面積が大きくなるので、GIoUの値は小さくなります。一方で、従来のIoUはどちらも0となり、矩形AとBの距離が反映されていませんでした。
GIoUが取り得る値の範囲は-1~1となります。最大値1となるのは、2つの矩形が完全に重なった場合で、従来IoUが1となり、CとUnionが一致した場合です。最小値-1は、Cの面積が無限大に発散した場合を指します。つまり、矩形AとBが極限まで遠くに離れた場合です。
GIoUを計算するコード例
GIoUを計算する関数の実装例を次に示します。
def giou(a, b):
# a, bは矩形を表すリストで、a=[xmin, ymin, xmax, ymax]
ax_mn, ay_mn, ax_mx, ay_mx = a[0], a[1], a[2], a[3]
bx_mn, by_mn, bx_mx, by_mx = b[0], b[1], b[2], b[3]
# 矩形Aの面積を計算
a_area = (ax_mx - ax_mn + 1) * (ay_mx - ay_mn + 1)
# 矩形Bの面積を計算
b_area = (bx_mx - bx_mn + 1) * (by_mx - by_mn + 1)
# intersectionの面積を計算
abx_mn = max(ax_mn, bx_mn)
aby_mn = max(ay_mn, by_mn)
abx_mx = min(ax_mx, bx_mx)
aby_mx = min(ay_mx, by_mx)
w = max(0, abx_mx - abx_mn + 1)
h = max(0, aby_mx - aby_mn + 1)
intersect = w*h
# unionの面積を計算
union = (a_area + b_area - intersect)
# IoUを計算
iou = intersect / union
# convex shape Cの面積を計算
abx_mn = min(ax_mn, bx_mn)
aby_mn = min(ay_mn, by_mn)
abx_mx = max(ax_mx, bx_mx)
aby_mx = max(ay_mx, by_mx)
c_area = (abx_mx - abx_mn + 1) * (aby_mx - aby_mn + 1)
# GIoUを計算
giou = iou - (c_area - union) / c_area
return giou
前半は従来のIoUの計算と同じです。次の部分で凸形 (convex shape) Cの面積を計算していますが、単に、2つの矩形A, Bを囲む矩形Cの左上の点の座標(abx_mn, abx_mn)と、右下の点の座標(abx_mx, abx_mx)を計算し、その2点で表される長方形の面積”c_area”を計算しているだけです。
# convex shape Cの面積を計算
abx_mn = min(ax_mn, bx_mn)
aby_mn = min(ay_mn, by_mn)
abx_mx = max(ax_mx, bx_mx)
aby_mx = max(ay_mx, by_mx)
c_area = (abx_mx - abx_mn + 1) * (aby_mx - aby_mn + 1)
次の行で、IoUとAとBのUnion、”c_area”を使ってGIoUを計算します。
# GIoUを計算
giou = iou - (c_area - union) / c_area
GIoUを計算する関数giou()の使用例
上で示した、GIoUを計算する関数giou()の使用例を記載します。
矩形A (a = [100,100,200,200]) と矩形B (b = [150,150,250,250]) のGIoUを計算してprint文で標準出力する例です。以前紹介した関数iou()を使い、従来のIoUも計算し標準出力しています。
if __name__ == '__main__':
a = [100,100,200,200]
b = [150,150,250,250]
# IoUを計算し、標準出力
iou_ab = iou(a, b)
print("iou =", iou_ab)
# GIoUを計算し、標準出力
giou_ab = giou(a, b)
print("giou =", giou_ab)
これを実行した標準出力は次の通りです。
iou = 0.14611538677602381
giou = -0.07317324091574409
おわりに
今回は2つの矩形の重なり具合を表すIoUの一般形であるGIoUの計算方法を紹介しました。
GIoUは、提案論文で、YOLOv3、Faster R-CNN、Mask R-CNNといった物体検出AIの損失関数として使用する場合の優位性が示されています。
また、最近提案された物体検出AIであり、学習を高速で行うことができるTTFNet (Training-Time-Friendly Network) で使われており、今後はよりポピュラーになる概念かもしれません。
コメント