COCO APIのCOCOevalを使ってみる
はじめに
物体検出の性能を評価するにあたって COCO 公式のライブラリを触ってみようと思い、使い方を調べてみました。
物体検出の評価指標
物体検出では、AP (Average Precision) および mAP (Mean Average Precision) と呼ばれる評価指標がよく用いられます。詳細は省略しますが、要点は以下の通りです。
- ある物体を検出できたかどうかは、予測したバウンディングボックスと正解のバウンディングボックスの IoU がしきい値を超えているかで判断します。
- AP は適合率と再現率を元に計算されます。細かい計算方法はデータセットによって若干異なったりしますが、近年は COCO のものがよく使われます。
- カテゴリごとに求めた AP を平均して mAP とします。ただし、AP と mAP が区別されない場合もあります。
- IoU のしきい値を 50% とした場合、
あるいはAP@IoU=0.5 のように表記します。AP_{50} - AP を 50% から 95% まで 0.05 ポイント刻みで求め、それを平均した
といった指標も用いられます。AP@[.5:.05:.95]
インスタンスセグメンテーションではバウンディングボックスの代わりにマスクが使われますが、基本的な考え方は同じです。
COCO API
COCO (Common Objects in COntext) データセットは、物体検出、セグメンテーション、キーポイント検出、キャプション作成などの画像関連のタスクに使用される大規模データセットです。
COCO データセットを扱うための COCO API が用意されており、Python の API は pycocotools というパッケージ[1]になっています。COCO データセットの読み込みには coco モジュールの COCO クラス、AP 等の評価には cocoeval モジュールの COCOeval クラスを利用することができます。
COCOeval の使い方はコメントに書いてあります。cocoGt
は正解のデータセット、cocoDt
は予測結果から作成した COCO クラスのインスタンスと思われます。これらを COCOeval に渡し、evaluate()
、accumulate()
、summarize()
を順に呼び出せばいいようです。
Detectron2の例
Detectron2 は物体検出とセグメンテーション用のライブラリです。Detectron2 には COCOEvaluator クラスがあり、pycocotools の COCOeval を使って AP を計算できるようになっています。デフォルトでは非公式の実装が使用されるようですが、正確な値を求めるときは公式実装を使用することが推奨されています。
COCOEvaluator がどのように COCOeval を利用しているのか見ていきます。まず、予測結果を instances_to_coco_json() で COCO 形式のアノテーションに変換しています。
instances_to_coco_json()
の中ではバウンディングボックスのフォーマットを変えたり、マスクを RLE フォーマットにエンコードしたりしているようです。
そして、得られた予測結果のアノテーションをまとめて、以下の行で COCO オブジェクトにしています。COCO クラスの loadRes() メソッドの実装を見るに、渡されたアノテーションを使って新しいデータセットのインスタンスを作成してくれるようです。
あとは諸々のパラメータを設定して、その後 evaluate()
、accumulate()
、summarize()
を呼び出しています。
matterport/Mask_RCNNの例
matterport/Mask_RCNN は Keras と TensorFlow 1 で実装された Mask R-CNN のライブラリです。ここ数年更新が止まっていますが、長いこと定番ライブラリとされてきたのでまだ使われるケースもあるかと思います。[2]
このライブラリには compute_ap() などの関数が用意されていますが、COCO ではなく
Pascal VOC の AP を計算する実装になっているようです。なのであえて COCO API を使って AP を計算してみます。
まず、compute_ap() を使った公式のサンプルが samples/shapes/train_shapes.ipynb にあります。JupyterLab で学習と評価を実行した結果、mAP: 0.9625 が得られました。
これを、Detectron2 の実装を参考に COCO API を使って次のように書き換えます。
!pip install pycocotools
from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval
import pycocotools.mask as mask_util
def encode_mask(masks):
masks = np.transpose(masks, (2, 0, 1))
return [mask_util.encode(np.array(mask, order='F', dtype="uint8")) for mask in masks]
# Compute COCO AP
images = []
gt_anns = []
dt_anns = []
for image_id in dataset_val.image_ids:
# Load image and ground truth data
image, image_meta, gt_class_id, gt_bbox, gt_mask = modellib.load_image_gt(
dataset_val, inference_config, image_id, use_mini_mask=False
)
# Create image metadata
images.append({"id": image_id, "height": image_meta[1], "width": image_meta[2]})
# Create ground truth annotations
rles = encode_mask(gt_mask)
anns = [
{
"id": len(gt_anns) + i,
"image_id": image_id,
"category_id": class_id,
"segmentation": rle,
} for i, (class_id, rle) in enumerate(zip(gt_class_id, rles))
]
gt_anns.extend(anns)
# Run object detection
results = model.detect([image], verbose=0)
r = results[0]
# Create detection result annotations
rles = encode_mask(r["masks"])
anns = [
{
"id": len(dt_anns) + i,
"image_id": image_id,
"category_id": class_id,
"segmentation": rle,
"score": score,
} for i, (class_id, rle, score) in enumerate(zip(r["class_ids"], rles, r["scores"]))
]
dt_anns.extend(anns)
# Create temporary COCO dataset
tmp_coco = COCO()
tmp_coco.dataset['categories'] = [{"id": class_id} for class_id in dataset_val.image_ids]
tmp_coco.dataset['images'] = images
tmp_coco.createIndex()
# Create ground truth and detection result dataset
gt_coco = tmp_coco.loadRes(gt_anns)
dt_coco = tmp_coco.loadRes(dt_anns)
# Run evaluation
cocoeval = COCOeval(gt_coco, dt_coco, iouType="segm")
cocoeval.evaluate()
cocoeval.accumulate()
cocoeval.summarize()
以下のような出力が得られました。
Average Precision (AP) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.699
Average Precision (AP) @[ IoU=0.50 | area= all | maxDets=100 ] = 0.917
Average Precision (AP) @[ IoU=0.75 | area= all | maxDets=100 ] = 0.775
Average Precision (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.414
Average Precision (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.727
Average Precision (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = -1.000
Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 1 ] = 0.594
Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets= 10 ] = 0.730
Average Recall (AR) @[ IoU=0.50:0.95 | area= all | maxDets=100 ] = 0.730
Average Recall (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.429
Average Recall (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = 0.756
Average Recall (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = -1.000
結果を見ると、
- AP@[.5:.05:.95] : 0.699
- AP@IoU=0.5 : 0.917
- AP@IoU=0.75 : 0.775
となっており、それらしい値が得られました。
参考記事
- mAP (mean Average Precision) for Object Detection
- Mean Average Precision (mAP) in Object Detection
- facebookresearch/detectron2
- Mask R-CNNでAPを計算してみる
-
公式のリポジトリは過去 Python 3 に対応していなかったこともあり、PyPI の pycocotools は公式ではなくフォークされたリポジトリの ppwwyyxx/cocoapi になっています。 ↩︎
-
特に 2023 年現在の Google Colab では TensorFlow 1.x のサポートが終了しています。手動でインストールできた時期もありましたが、Python 3.10 になってからはそれも難しいようです。 ↩︎
Discussion