物体検出の評価方法mAPのプログラムを見つけた

# 見つけたもの

物体検出の評価でよく使われるmean Average Precision(mAP)がサクッと出せちゃうプログラム。
PrecisionやRecallもテキストとして出してくれるみたいです。

これは便利！

https://github.com/Cartucho/mAP

# だがしかし

正解データと検出結果を以下のフォーマットに書き換えて、inputディレクトリに入れないといけないのです。
また、ファイル名は同じ名前にしてね。とのこと。
しかも、Pascal VOCとかMSCOCOとかからの変換コードはないのです。。。
素晴らしきは、下記の結果データをPascal VOCやYolo形式にしてくれる変換コードは入っていたりします。


#### 正解データ

```
<class_name> <left> <top> <right> <bottom> [<difficult>]
```

#### 検出結果

```
<class_name> <confidence> <left> <top> <right> <bottom>
```


# 書いたコード

なので、Pascal VOCフォーマットから上記の正解データフォーマットに変換するコードを書いてみました。
検出結果は、人それぞれなので書けませんでした。

+ 動作確認
  + Ubuntu 16.04 STL
  + Python 3.5.2

```python:conv_voc2txt.py
##
# cording:utf-8
##

import os
import glob
import shutil
import argparse
from xml.etree import ElementTree as ET


def argparser():
    parser = argparse.ArgumentParser()
    parser.add_argument('xml_dir', type=str,
                        help='input dir in pascal voc xml')
    parser.add_argument('-o', '--output_dir', type=str,
                        default='input/ground-truth',
                        help='output dir name')
    return parser.parse_args()


def read_xml(xml_name):
    root = ET.parse(xml_name).getroot()
    object_list = []
    for member in root.findall('object'):
        label = member.find('name').text
        bbox = member.find('bndbox')
        object_list.append({'label': label,
                            'xmin': bbox.find('xmin').text,
                            'ymin': bbox.find('ymin').text,
                            'xmax': bbox.find('xmax').text,
                            'ymax': bbox.find('ymax').text})
    return object_list


def create_output_name(xml_name, output_dir):
    p = os.path.split(xml_name)[1]
    buf = os.path.splitext(p)[0]
    return os.path.join(output_dir,
                        '{}.txt'.format(buf))


def write_text(object_list, output_txt_name):
    """
    output format
    <class_name> <left> <top> <right> <bottom>
    """
    with open(output_txt_name, 'w') as fp:
        for obj in object_list:
            line = '{} {} {} {} {}\n'.format(obj['label'],
                                             obj['xmin'], obj['ymin'],
                                             obj['xmax'], obj['ymax'])
            fp.write(line)


if __name__ == '__main__':
    args = argparser()

    if not os.path.isdir(args.xml_dir):
        print('input dir name not found:{}'.format(args.xml_dir))
        exit()
    
    # create output dir
    if os.path.isdir(args.output_dir):
        shutil.rmtree(args.output_dir)
    os.makedirs(args.output_dir)

    xml_list = glob.glob(os.path.join(args.xml_dir, '*.xml'))
    for xml_name in xml_list:
        print(xml_name)
        obj_list = read_xml(xml_name)
        output_name = create_output_name(xml_name, args.output_dir)
        write_text(obj_list, output_name)
```

# 使い方

```
$ git clone https://github.com/Cartucho/mAP.git
$ cd mAP
$ mv hoge/conv_voc2txt.py ./
$ python3 conv_voc2txt.py [input_dir] -o [<output_dir>]
```

`input_dir`には、変換したいPascal VOCフォーマッ卜のXMLファイルが入っているディレクトリを指定します。
`input_dir`のみ指定して実行した場合は、`input/ground-truth`のディレクトリ内に出力結果のテキストが格納されます。
`output_dir`はオプションで、出力ディレクトリの名前を変更したい際に指定してください。

## ｍAP実行

認識結果のテキストファイルを`input/detection-results`に格納、正解データを`input/ground-truth`のディレクトリに格納します。
ここで、検出結果と正解データの両方のファイル数を同じにする処理をはさみます。
画像中の物体が一つも現出されずに、検出結果ファイルが作られない場合や過検出による本来ない正解データの作成です。
そういったファイルの抜けに対応するプログラムがｍAPで提供されています（作者さんすごい）。

```
$ python3 scripts/extra/intersect-gt-and-dr.py
```

その後、ｍAPの`main.py`を実行します。

```
python3 main.py -na
```

これで、作者さんのサイトで紹介されているような綺麗なｍAPのグラフや、結果のテキストファイルなどが手に入ります。
`-na`オプションは、ｍAP計算時に画像を出力するオプションを切る設定です。この`-na`を付けないととても処理が重くなります。
画像が欲しい方は、`-na`のオプションを消して、`input/images-optional`に、認識に使用した画像を入れて実行してください。

# 以上

急に必要になったのですが、このリポジトリのおかげでなんとかなりました。感謝。