👋

COCO APIのCOCOevalを使ってみる

2023/08/08に公開

はじめに

物体検出の性能を評価するにあたって 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() を順に呼び出せばいいようです。

https://github.com/cocodataset/cocoapi/blob/8c9bcc3cf640524c4c20a9c40e89cb6a2f2fa0e9/PythonAPI/pycocotools/cocoeval.py#L13-L20

Detectron2の例

Detectron2 は物体検出とセグメンテーション用のライブラリです。Detectron2 には COCOEvaluator クラスがあり、pycocotools の COCOeval を使って AP を計算できるようになっています。デフォルトでは非公式の実装が使用されるようですが、正確な値を求めるときは公式実装を使用することが推奨されています。

https://github.com/facebookresearch/detectron2/blob/57bdb21249d5418c130d54e2ebdc94dda7a4c01a/detectron2/evaluation/coco_evaluation.py#L85-L88

COCOEvaluator がどのように COCOeval を利用しているのか見ていきます。まず、予測結果を instances_to_coco_json() で COCO 形式のアノテーションに変換しています。

https://github.com/facebookresearch/detectron2/blob/57bdb21249d5418c130d54e2ebdc94dda7a4c01a/detectron2/evaluation/coco_evaluation.py#L171

instances_to_coco_json() の中ではバウンディングボックスのフォーマットを変えたり、マスクを RLE フォーマットにエンコードしたりしているようです。

https://github.com/facebookresearch/detectron2/blob/57bdb21249d5418c130d54e2ebdc94dda7a4c01a/detectron2/evaluation/coco_evaluation.py#L392-L451

そして、得られた予測結果のアノテーションをまとめて、以下の行で COCO オブジェクトにしています。COCO クラスの loadRes() メソッドの実装を見るに、渡されたアノテーションを使って新しいデータセットのインスタンスを作成してくれるようです。

https://github.com/facebookresearch/detectron2/blob/57bdb21249d5418c130d54e2ebdc94dda7a4c01a/detectron2/evaluation/coco_evaluation.py#L590-L591

あとは諸々のパラメータを設定して、その後 evaluate()accumulate()summarize() を呼び出しています。

https://github.com/facebookresearch/detectron2/blob/57bdb21249d5418c130d54e2ebdc94dda7a4c01a/detectron2/evaluation/coco_evaluation.py#L627-L629

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

となっており、それらしい値が得られました。

参考記事

脚注
  1. 公式のリポジトリは過去 Python 3 に対応していなかったこともあり、PyPI の pycocotools は公式ではなくフォークされたリポジトリの ppwwyyxx/cocoapi になっています。 ↩︎

  2. 特に 2023 年現在の Google Colab では TensorFlow 1.x のサポートが終了しています。手動でインストールできた時期もありましたが、Python 3.10 になってからはそれも難しいようです。 ↩︎

Discussion