Pythonでアニメの画像からキャラクターの顔を検出したい場合、nagadomiさんが実装されたlbpcascade_animeface使うケースをしばしば見かけます。このlbpcascade_animefaceは顔のバウンディングボックスを推定するモジュールです。
また、前述のnagadomiさんが実装されたモジュールであるanimeface-2009は、顔だけでなく、顔のパーツ(目、鼻、口、あご)のバウンディングボックスも検出可能1です。ただし、こちらはRubyのみバインディングレベルのAPIが提供されています。
lbpcascade_animeface
のREADME.mdによると、「検出精度はanimeface-2009
の方が高い」という旨が書かれています。したがって、ぜひともPythonで使いたいものです。
『animeface-2009』をPythonから呼び出す関数
今回実装したもののGitリポジトリはこちらです。
Ruby用に実装されたC言語のコードをPython用に書き直すのは、私のようなクソザコエンジニアには簡単なことではありません。
そこで、悪あがきの策ではありますが、Pythonのsubprocessモジュールでシェルを介してRubyを起動し、animeface-2009
のRubyスクリプトを実行するPythonの関数を実装しました。
もちろん、このコード単体で顔検出できるわけではありません。事前準備が必要です。ディレクトリの構成を上述のGitリポジトリのようにし、また、animeface-2009をビルドする必要があります2。
# 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フォーマットになるようになんとか整形し、辞書型にしています3。
関数のインタフェースですが、入力は画像パス、返り値は検出結果を下記のような辞書のリストを返します。一つの顔検出結果が一つの辞書になっています。
[{'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のファイルをスクリプト実行すると、検出結果を描画した画像を出力します。
検出結果をPASCAL VOC形式のXMLファイルへ出力するモジュール
上述の関数を使って、アニメ顔(パーツ)検出をするのでもよいですが、すべての顔を検出できるわけではありません。
検出性能のスケーラビリティの観点から、DNNベースの検出器への移行も視野に入れます。
そこで、animeface-2009の検出結果を用いて、DNNを学習するための物体検出データセットを作る関数を実装しました。
データセットのフォーマットは定番のPASCAL VOCにしました。
$ python animeface_result2xml.py [検出対象の画像のディレクトリ] [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を使用するとよいと思います。
推定したバウンディングボックスをグラフィカルに確認でき、誤っている場所に対してはその場で修正もできます。
後は、作ったXMLを、ディープラーニングフレームワークのデータローダにパースさせ、物体検出DNNモデルを学習するという流れになります。
おわりに
アニメの顔パーツ検出を行うRubyのモジュールであるanimeface-2009
を何とかPythonで扱うためのdirtyな関数を実装しました。また、その検出結果をPascal VOC形式のXMLファイルにする関数を実装しました。
これを使って、アニメ機械学習の応用可能性の幅を広げていきたいです。
なお、本記事ではDNNでアニメの物体検出をするにはアノテーションラベルが必要という立場をとりましたが、やや面倒と思われる方もいらっしゃるかもしれません。
最近kanosawaさんが、物体検出モデルの学習にDomain Adaptationテクニックを使った記事を公開されました。これは、アニメ顔のバウンディングボックスのアノテーションなしで物体検出モデル(Faster RCNN)の学習ができるので、ご興味があればご確認いただければと思います。