物体検出モデルの精度評価を理解して実際に算出する

物体検出モデルを構築したときにそのモデルがいかに優れているかを説明・評価するためにさまざまな指標があります．
この記事ではその指標の概要と計算方法，実際の算出までを紹介します．
間違っていることがありましたらコメントや編集リクエストをいただければ幸いです．

# IoU (Intersection over Union)
**正解とした範囲**に対して**モデルが予測した範囲**が**どの程度重なっているのか**を示す指標です．

式は以下のように表されます．

$$\mathrm{IoU} = \frac{Intersection}{Union} = \frac{重なっている部分}{2つの範囲の和集合}$$

値は 0 から 1 の間で表され，数字が大きいほど正確に予測できていることを示します．

## 画像で見る具体例

「猫」とする領域(正解)を赤枠，モデルが「猫」と判定した領域(予測)を青枠とします．

![iou1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106674/a325c817-b01b-82c5-d9a1-797a5bb8ac1c.png)

この時，IoU は，
![IoverU.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106674/33039009-d950-3241-6086-529732172632.jpeg)

を計算します．

## 計算
重なり分を適切に処理する必要があります．

<details><summary>計算 および プログラム例</summary><div>
重なり部分は左上では 2 つの枠の左上での最大x, y座標，右下では 2 つの枠の最小のx, y座標として計算できます．
分母は正解枠と予測枠の領域を足して重なり分引く必要があることに注意します．
図にすると以下のようになります．
![ioucalc.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106674/106ad612-2e71-6e9b-1a14-5d4f93424f2f.jpeg)

Python プログラム例

```python
def iou(gt: list, pred: list) -> int:
    # Groud Truth / Predict
    # 形は [(x_right, y_right), (x_left, y_left)] とする
    gt_area = (gt[1][0] - gt[0][0]) * (gt[1][1] - gt[0][1])
    pred_area = (pred[1][0] - pred[0][0]) * (pred[1][1] - pred[0][1])
    tp_x = (min(gt[1][0], pred[1][0]) - max(gt[0][0], pred[0][0]))
    tp_y = (min(gt[1][1], pred[1][0]) - max(gt[0][1], pred[0][1]))
    tp_area = tp_x * tp_y

    return tp_area / (gt_area + pred_area - tp_area)

g = [(50, 50), (200, 200)]
p = [(20, 20), (220, 220)]

print(iou(g, p))
# iou = 0.5625
```
</div></details>


## IoU しきい値の目安

重なり具合と IoU を直感的に対応づけるため 1 枚の画像にしてみました．
青色部が重なっている部分，白色部が重なっていない部分です．グレーは背景です．

![iou.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106674/98ca3607-2ee4-b0e2-4d11-45ade5fb1fdc.png)



<details><summary>生成に利用した Python スクリプト</summary><div>

```python

import cv2
import numpy as np
import math

for t in range(0, 105, 5):
  t /= 100
  img = np.full((200, 200, 3), 128, dtype=np.uint8)
  a = 1 - math.sqrt((2 * t) / (1 + t))  # ずれ割合
  intersect = int((1 - a) * 100)  # 重なりの一辺の長さ
  cv2.rectangle(img, (50 - int(100 * a / 2), 50 - int(100 * a / 2)), (150 - int(100 * a / 2), 150 - int(100 * a / 2)), (255, 255, 255), thickness=-1)
  cv2.rectangle(img, (50 + int(100 * a / 2), 50 + int(100 * a / 2)), (150 + int(100 * a / 2), 150 + int(100 * a / 2)), (255, 255, 255), thickness=-1)
  cv2.rectangle(img, (100 - int(intersect / 2), 100 - int(intersect / 2)), (100 + int(intersect / 2), 100 + int(intersect / 2)), (255, 0, 0), thickness=-1)
  cv2.imwrite('iou' + str(t) + '.png', img)
```
</div></details>





# 適合率(Precision)

$$\frac{\mathrm{TP}}{\mathrm{TP + FP}} = \frac{予測が当たっている}{正解と予測した}$$

モデルが正解と予測したもののうち実際に正解だった割合です．
数値が高いと**正確**であると言えます．
一方で，人が正解としたものを見落としていても適合率には影響しません．

# 再現率(Recall)

$$\frac{\mathrm{TP}}{\mathrm{TP + FN}} = \frac{実際の正解のうち，モデルが当てた}{実際の正解}$$

人が正解と指定した物のうちモデルが正解と当てた割合です．
数値が大きい方が**見落としが少ない**ということになります．
一方で，人が正解としていないものまで過剰に正解だと予測していても再現率には影響しません．

# PR曲線

PR曲線(= Precision-Recall Curve) は 横軸に再現率，縦軸に適合率をプロットしたグラフです．

プロット点は以下のように計算されます．

1. 1つの対象に対して信頼度の高い順番にならべます(Rank)
1. その中でそれが本当に正しかったのか(Correct?)を元に，適合率(Precision) と 再現率(Recall) を算出します．どちらも算出する対象とそれより上の信頼度を持っているものだけが対象です．

例えば表の6番目について考えているとき上にはTとFが半分ずつ存在するので適合率が 0.5 ，全正解 5 つのうち 3 つは予測できたので再現率 0.6 となります．

![Untitled.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106674/6607ec79-72f1-991c-c149-94516206ed56.png)

グラフを描くと以下のようになります．適合率も再現率も高いほど良いので，右上に近づくほどより良いと言えます．

![Untitled 1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106674/4a7e4a00-105b-f50f-751d-becfc8d29f00.png)


表及び図の引用元: [mAP (mean Average Precision) for Object Detection](https://jonathan-hui.medium.com/map-mean-average-precision-for-object-detection-45c121a31173)


# AP

平均適合率(AP = Average Precision)
1つの対象物に対して信頼度の高い順に並べた上で，正解が出た時点までの適合率を求め，それを平均したものです．高いほど良いです．

算出は前項の図の赤色の点線より下の面積を合計します．(より大きな面積を確保するように算出します)

![Untitled 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106674/20ccff65-7410-3c49-f003-6dc18fe76d44.png)

引用元: [https://github.com/rafaelpadilla/review_object_detection_metrics/blob/main/data/images/interpolations_toyexample.png](https://github.com/rafaelpadilla/review_object_detection_metrics/blob/main/data/images/interpolations_toyexample.png)

### mAP

mAP とは mean Average Precision の略です．
前項で求めた AP を各対象ごとに求めそれを平均したものです．高いほど良いです．


### AP @.5 / AP @.75

AP @.5 は IoU しきい値を 0.5 として(IoU が 50% なら正解として) AP を算出した値です．
AP @.75 は同様にしきい値を 0.75 として AP を計算したものです．

### AP@[.5:.05:.95]

IoU 0.5 から 0.95 まで 0.05 の幅で変えて AP を算出し平均したものです．

### AP small /  AP medium /  AP large
正解とした対象物のサイズごとに AP を算出したものです．

$\mathrm{AP_S}$ = 〜32x32 ピクセル の小さなものを対象にして算出したAP値です．
$\mathrm{AP_M}$ =  32x32 〜 96x96 の中くらいの大きさのものを対象にして算出したAP値です．
$\mathrm{AP_L}$ =  96x96〜 の大きめのものを対象にして算出したAP値です．


# 算出させる

こちらのリポジトリを利用することで簡単な GUI 操作で算出できました．
<a href="https://github.com/rafaelpadilla/review_object_detection_metrics"><img src="https://github-link-card.s3.ap-northeast-1.amazonaws.com/rafaelpadilla/review_object_detection_metrics.png" width="460px"></a>

モデルからの出力はテキストファイルに落とし込む必要がありますが，ラベリングのフォーマットは多く対応しています．

## 起動方法

anaconda 環境を強く推奨しているようです．

`$ git clone http://github.com/rafaelpadilla/review_object_detection_metrics.git`

`$ cd review_object_detection_metrics` 

`$ conda env create -n <env_name> --file environment.yml`

`$ conda activate <env_name>`

`$ python setup.py install`

`$ python run.py`

## 起動画面

![_2021-02-02_21.55.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106674/62cdb975-1171-2f1e-0858-53a8d1a83a3e.png)

- Ground truth

一番上からラベリングしたアノテーションが入ったディレクトリ，画像が入ったディレクトリ，1行ごとに名称が書かれたテキストファイルを右側の「...」から指定します．

形式を選択します．

- Detections

事前にモデルから予測させ「Coordinates format」に書かれているようなフォーマットで画像名と同名のテキストファイルを出力します．

Ground truth と同様に生成させたテキストファイルを入れたディレクトリを指定，上で classes に指定したファイルを指定します．

「show ground-truth statics」を押すと正しく読み込めているかどうか確認できます．

![_2021-02-01_23.02.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106674/364398ac-5212-a4fc-23ef-1eb44317c306.png)


Class ごとの量を示したグラフも生成できます．

![_2021-02-01_23.03.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106674/118b2df1-d799-07c7-452f-7a44fc3e7d5f.png)


最後に Output を適当なディレクトリを作成し指定して RUN を押します．

[zzh8829](https://github.com/zzh8829)/[yolov3-tf2](https://github.com/zzh8829/yolov3-tf2) にある Jupyter Notebook([Colab](https://colab.research.google.com/github/zzh8829/yolov3-tf2/blob/master/colab_gpu.ipynb)) の Training New Dataset を実行したもので試してみたところ以下のような結果を得られました．

```
COCO METRICS:
AP: 0.06837763326058421
AP50: 0.1868576730227902
AP75: 0.03095519938699847
APsmall: 0.0004302127872885811
APmedium: 0.016053953627700405
APlarge: 0.09127128247522165

AR1: 0.10058433646297864
AR10: 0.10728608386833267
AR100: 0.10728665854794878
ARsmall: 0.0003240126897739027
ARmedium: 0.034383132577691516
ARlarge: 0.13461170366648245

PASCAL METRIC (AP per class)
person: 0.1159136520925125
aeroplane: 0.3518502936766839
tvmonitor: 0.24476247092079823
train: 0.36924761622708907
boat: 0.010777597586126918
dog: 0.20145806613727765
chair: 0.07374382107992518
bird: 0.14140996203283426
bicycle: 0.18542453707150133
bottle: 0.046736469223465846
sheep: 0.0734351460183224
diningtable: 0.093843920933622
horse: 0.14886477741299112
motorbike: 0.17256132725779022
sofa: 0.18585212095867804
cow: 0.043834979906067044
car: 0.22067197240235656
cat: 0.5916645310282492
bus: 0.4243239482690493
pottedplant: 0.023270893359588103

PASCAL METRIC (mAP)
mAP: 0.18598240517974643
```

生成されたグラフ(種類ごとにあります)


![aeroplane.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/106674/7adb241c-64fc-1d9c-ddc3-608858a3a558.png)


現状(2021/2) CLI から操作する方法はないようです．
[Provide example of use with command line interface · Issue #5 · rafaelpadilla/review_object_detection_metrics](https://github.com/rafaelpadilla/review_object_detection_metrics/issues/5)