『animeface-2009』をPythonで使うための悪あがき & PASCAL VOC形式のXMLファイルへ出力する関数の実装


<img width="500" alt="animeface2009_and_python.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491308/48a9ddef-384f-c18e-c0d9-b6412cbccb3f.png">

Pythonでアニメの画像からキャラクターの顔を検出したい場合、[nagadomi](https://twitter.com/ultraistter)さんが実装された[lbpcascade_animeface](https://github.com/nagadomi/lbpcascade_animeface)使うケースをしばしば見かけます。このlbpcascade_animefaceは顔のバウンディングボックスを推定するモジュールです。

また、前述のnagadomiさんが実装されたモジュールである[animeface-2009](https://github.com/nagadomi/animeface-2009)は、顔だけでなく、顔のパーツ(目、鼻、口、あご)のバウンディングボックスも検出可能[^1]です。ただし、こちらはRubyのみバインディングレベルのAPIが提供されています。  
`lbpcascade_animeface`のREADME.mdによると、「検出精度は`animeface-2009`の方が高い」という旨が書かれています。したがって、ぜひともPythonで使いたいものです。

[^1]: ただし、鼻とあごのバウンディングボックスは1x1ピクセルですので、ランドマークと呼んだ方がいいかもしれません。


# 『animeface-2009』をPythonから呼び出す関数

今回実装したもののGitリポジトリは[こちら](https://github.com/meow-noisy/animeface_result2xml)です。

Ruby用に実装されたC言語のコードをPython用に書き直すのは、私のようなクソザコエンジニアには簡単なことではありません。
そこで、悪あがきの策ではありますが、Pythonのsubprocessモジュールでシェルを介してRubyを起動し、`animeface-2009`のRubyスクリプトを実行する[Pythonの関数](https://github.com/meow-noisy/animeface_result2xml/blob/master/animeface_poor_caller.py)を実装しました。  
もちろん、**このコード単体で顔検出できるわけではありません**。事前準備が必要です。ディレクトリの構成を上述のGitリポジトリのようにし、また、[animeface-2009](https://github.com/meow-noisy/animeface-2009/tree/a39361157ba8bf16ccd838942fa2f333658e9627)をビルドする必要があります[^2]。

[^2]: オリジナルの`animeface-2009`は複数の環境でビルドが難しかったので、私が変更を加えた[forkリポジトリ](https://github.com/meow-noisy/animeface-2009/tree/a39361157ba8bf16ccd838942fa2f333658e9627)を使用しています。こちらのリポジトリのREADME.mdに、ビルド時に必要な手順を追記していますので併せてご確認いただけると幸いです。


```python
# a poor python caller of animeface-2009
# call rb module via shell by using subprocess...

import subprocess

import sys
import json
from pathlib import Path

this_file_dir = (Path(__file__).resolve()).parent


def detect_animeface(im_path):

    im_path = Path(im_path).resolve()
    assert im_path.exists()

    ruby_script_path = this_file_dir / 'animeface-2009/animeface-ruby/sample.rb'
    ret = subprocess.check_output(["ruby", str(ruby_script_path), str(im_path)]).decode('utf-8')

    ret = ret.replace("=>", ":")
    ret = ret.replace(">", "\"")
    ret = ret.replace("#", "\"")
    list_ = json.loads(ret)

    return list_
```



RubyからPythonへデータは直接渡していません。検出したバウンディングボックス情報を標準出力した後、Python側でその文字列をJSONフォーマットになるようになんとか整形し、辞書型にしています[^4]。
関数のインタフェースですが、入力は画像パス、返り値は検出結果を下記のような辞書のリストを返します。一つの顔検出結果が一つの辞書になっています。

[^4]: 一部、不要な要素(Rubyのオブジェクトを表す文字列)が含まれてしまっていますが、後処理が面倒だったので、そのまま出力するようにしています。

```python
[{'chin': {'x': 228, 'y': 266},
  'eyes': {'left': {'colors': ['<Magick::Pixel:0x00007ff93e969148',
                               '<Magick::Pixel:0x00007ff93e968e28',
                               '<Magick::Pixel:0x00007ff93e968d88',
                               '<Magick::Pixel:0x00007ff93e968bf8'],
                    'height': 31,
                    'width': 39,
                    'x': 222,
                    'y': 181},
           'right': {'colors': ['<Magick::Pixel:0x00007ff93e968040',
                                '<Magick::Pixel:0x00007ff93e968018',
                                '<Magick::Pixel:0x00007ff93e968180',
                                '<Magick::Pixel:0x00007ff93e9681a8'],
                     'height': 28,
                     'width': 31,
                     'x': 165,
                     'y': 202}},
  'face': {'height': 127, 'width': 127, 'x': 158, 'y': 158},
  'hair_color': '<Magick::Pixel:0x00007ff93e969cb0',
  'likelihood': 1.0,
  'mouth': {'height': 12, 'width': 25, 'x': 210, 'y': 243},
  'nose': {'x': 207, 'y': 233},
  'skin_color': '<Magick::Pixel:0x00007ff93e96a020'},
 {'chin': {'x': 379, 'y': 243},
  'eyes': {'left': {'colors': ['<Magick::Pixel:0x00007ff93e96b6a0',
                               '<Magick::Pixel:0x00007ff93e96b8d0',
                               '<Magick::Pixel:0x00007ff93e96b9e8',
                               '<Magick::Pixel:0x00007ff93e96bab0'],
                    'height': 29,
                    'width': 32,
                    'x': 418,
                    'y': 177},
           'right': {'colors': ['<Magick::Pixel:0x00007ff93e963568',
                                '<Magick::Pixel:0x00007ff93e963478',
                                '<Magick::Pixel:0x00007ff93e963298',
                                '<Magick::Pixel:0x00007ff93e9631a8'],
                     'height': 31,
                     'width': 39,
                     'x': 354,
                     'y': 157}},
  'face': {'height': 139, 'width': 139, 'x': 329, 'y': 121},
  'hair_color': '<Magick::Pixel:0x00007ff93e96ab38',
  'likelihood': 1.0,
  'mouth': {'height': 12, 'width': 20, 'x': 383, 'y': 218},
  'nose': {'x': 401, 'y': 205},
  'skin_color': '<Magick::Pixel:0x00007ff93e96a7c8'}]
```

ちなみに、Pythonのファイルをスクリプト実行すると、検出結果を描画した画像を出力します。

![result_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491308/b11f3ea3-4431-e4c6-e6e8-53d84c5e7f4a.png)



# 検出結果をPASCAL VOC形式のXMLファイルへ出力するモジュール

上述の関数を使って、アニメ顔(パーツ)検出をするのでもよいですが、すべての顔を検出できるわけではありません。
検出性能のスケーラビリティの観点から、DNNベースの検出器への移行も視野に入れます。  
そこで、animeface-2009の検出結果を用いて、DNNを学習するための物体検出データセットを作る関数を実装しました。
データセットのフォーマットは定番のPASCAL VOCにしました。

- [animeface_result2xml.py](https://github.com/meow-noisy/animeface_result2xml/blob/master/animeface_result2xml.py)

`$ python animeface_result2xml.py [検出対象の画像のディレクトリ] [XMLファイルを出力するディレクトリ] [作成したXMLファイルリストのテキストファイルパス]`

予めディレクトリ内に検出したい画像をまとめておき(サブディレクトリ構成可)、XMLファイルを出力するディレクトリへのパスと、作成したファイルの一覧を示すテキストファイルを指定します。

ちなみに、XMLファイルは下記のようになります。

```xml
<annotation>
    <folder>folder_name</folder>
    <filename>img_file_path</filename>
    <path>/path/to/dummy</path>
    <source>
    <database>Unknown</database>
    </source>
    <size>
        <width>600</width>
        <height>600</height>
        <depth>3</depth>
    </size>
    <segmented>0</segmented>
    <object>
        <name>face</name>
        <pose>Unspecified</pose>
        <truncated>1</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>158</xmin>
            <ymin>158</ymin>
            <xmax>285</xmax>
            <ymax>285</ymax>
        </bndbox>
    </object><object>
        <name>right_eye</name>
        <pose>Unspecified</pose>
        <truncated>1</truncated>
        <difficult>0</difficult>
        <bndbox>
            <xmin>165</xmin>
            <ymin>202</ymin>
            <xmax>196</xmax>
            <ymax>230</ymax>
        </bndbox>
...
```

## ツールを使ったラベルの確認
XMLファイルを編集する時は、アノテーションツールである[LabelImg](https://github.com/tzutalin/labelImg)を使用するとよいと思います。
推定したバウンディングボックスをグラフィカルに確認でき、誤っている場所に対してはその場で修正もできます。

<img width="1183" alt="labelimg.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/491308/95e4d1e3-d1b1-8a88-4a2b-0f8ff910086c.png">


後は、作ったXMLを、ディープラーニングフレームワークのデータローダにパースさせ、物体検出DNNモデルを学習するという流れになります。



# おわりに
アニメの顔パーツ検出を行うRubyのモジュールである`animeface-2009`を何とかPythonで扱うためのdirtyな関数を実装しました。また、その検出結果をPascal VOC形式のXMLファイルにする関数を実装しました。
これを使って、アニメ機械学習の応用可能性の幅を広げていきたいです。


なお、本記事ではDNNでアニメの物体検出をするにはアノテーションラベルが必要という立場をとりましたが、やや面倒と思われる方もいらっしゃるかもしれません。
最近kanosawaさんが、物体検出モデルの学習にDomain Adaptationテクニックを使った記事を公開されました。これは、アニメ顔のバウンディングボックスのアノテーションなしで物体検出モデル(Faster RCNN)の学習ができるので、ご興味があればご確認いただければと思います。
- [高精度なアニメ顔検出をアノテーションなし(Domain Adaptation)で実現してみた](https://qiita.com/kanosawa/items/537938b1505e233f62bf)