サンタ画像からサンタ識別器を作る

# はじめに

人類の皆さん、こんにちは。
バーチャルYouTuber輪廻ヒロのソフトウェア担当、矢口です。

今回は動画で取り上げた内容である「サンタ識別器」の解説をします。

v YouTube動画のリンクになっています。よろしければご視聴、もしお気に召されましたらチャンネル登録お願いします。 v

[![【輪廻ヒロ】サンタ識別器でオレはサンタになれるか？](http://img.youtube.com/vi/LXz_itNGFV8/0.jpg)](http://www.youtube.com/watch?v=LXz_itNGFV8)


なお、ソースコードはgithubに公開しています。
ごく短いコードなのでまずはそちらをご覧ください。

https://github.com/hyaguchi947d/santa_recognition

# 問題設定

問題として、「サンタのイラストとサンタでないイラストの識別を行う」という設定にしました。
週一で動画を仕上げる都合上、実際にデータセット作成、コーディング、検証にかけられる時間は一日しかありません。ので、最低限のセットで設計します。

## 画像データセット

ここが一番大変です。なぜなら、弊社は営利企業であり動画投稿は営業活動です。
すなわち商用利用可と明示してあるデータ以外は使ってはいけません。

なので、まずは「いらすとや」様の規約に基づく20枚で作成していましたが、
あまりにもデータに偏りがあるため、「Pixabay」様「素材Good」様より追加を行い、
最終的には29+29の計58枚となりました。

更に、正負双方のデータをtrain:test = 22:7に分割しました。
この際にも、配布元のデータが偏らないように分けています。

なお、データセットの再配布やリンクは規約に抵触するため行いません。

## 採用する手法について

動画の内容として三段落ちにすることは決めていたので、
最初は最もかんたんな色ヒストグラム、
最後はディープラーニングとして使ってことのあるYOLO v3としました。
中間の一つですが、HOGと迷いましたがBag of Visual Wordsとしました。
どれも自分でコードを書く内容を最低限に抑えるため、
OpenCVで1時間くらいで実装できるもの、
あるいは実装せずとも動かせるもの、という条件もありました。

# 実装

## 色ヒストグラム

与えられた問題は単純な2クラス分類です。
ともかく色ヒストグラムが取れさえすれば、
あとはSVMにかけて判定してもらえばよい。

ということで、OpenCV Pythonとscikit-learnでさくっと作ります。

色ヒストグラムのとり方はOpenCVにチュートリアルがあり、
https://docs.opencv.org/master/dd/d0d/tutorial_py_2d_histogram.html

SVMもscikit-learnのわかりやすいチュートリアルがあります。
https://scikit-learn.org/stable/modules/generated/sklearn.svm.SVC.html

やっていることはごく単純で、

```
def color_hist_from_file(imgfile):
    img = cv.imread(imgfile)
    hist = cv.calcHist([img], [0, 1, 2], None, [8, 8, 8], [0, 256, 0, 256, 0, 256])
    hist_1d = hist.flatten()[1:-1]  # remove bins with (0,0,0) and (255,255,255)
    n_hist_1d = hist_1d / np.linalg.norm(hist_1d)
    return n_hist_1d
```

この関数の中ではbgr各チャンネルを8つに区切ったヒストグラム(8*8*8=512次元)を生成し、
そこから最初と最後のビン(真っ黒と真っ白、背景色であるため)を除いた510次元ベクトルを正規化したものを分類のためのベクトルとして用います。

SVMの学習は、正例負例を連結したリストと、
それぞれに対応する(今回は1と0)ラベルのリストを生成して行います。

```
trains = hist_train_p + hist_train_n
labels = [1] * len(hist_train_p) + [0] * len(hist_train_n)

clf = svm.SVC()
clf.fit(trains, labels)
```

これだけです。


## Bag of Visual Words

OpenCVのまともなチュートリアルがなかった。

ただし自力実装したことがあるアルゴリズムなので最悪はなんとか、
と思っていましたが、少し悩んでOpenCVのものを使えました。

こちらの記事が参考になりました。
https://hazm.at/mox/machine-learning/computer-vision/recipes/similar-image-retrieval.html

なお、特徴点にはAKAZEを利用しています。
SIFT,SURFは特許で保護されているためです。
(この話は3ヶ月後にもう一度することになると思いますが、またその時に。)

手順としては、
- 教師画像のAKAZE特徴点のkeypointsとdescriptorsを計算
- `BOWKMeansTrainer`にdescriptorsを追加し、辞書を作成する。
    - このとき、descriptorだけを用いているのは、desciptorのクラスタリング結果から辞書サイズ数と同じVisual Wordsを生成している。
    - Bag of Visual Wordsなのは、画像中にこれらVisual Wordsがいくつ含まれるかにより、画像からヒストグラムを生成するため。次元数は辞書サイズ数。
- `BOWImgDescriptorExtractor`に辞書を設定
- これにより、各画像のBag of Visual Wordsが求められる。
- あとはSVMを用いて画像を分類


## YOLO v3

darknetを使いました。以上。

しいていうなら、データセットの生成にVoTTを以前使っていたのですが、
v2になってからYOLOのデータが生成できなくなっていて、
V1はそれはそれでバグがあり(なぜかアノテーション済み画像を漏らす)、苦労しました。


# 実験結果について

## 色ヒストグラム

実は単純にヒストグラム取るだけ+SVMやるだけだと、
境界部分で少し輝度が違うだけで隣接ビンに入ってしまい、
類似度が極端に低くなるケースもありました。
本来なら少し重なるようにビンを設定するとか、
隣接ビン同士は距離近くするとか、対策が必要ですが時間がなかった。

なので、識別対象であるサンタヒロのイラストを寄せています。
当初サンタ服をFF0000で書いていたところ、見事にサンタじゃなくなったので
少し色調整してDD0000になおしています。

## Bag of Visual Words

当初グレースケールだけでやっていましたが、
RGBの各チャンネルで分離してBoVWを３つ作り、連結したものでもやりました。
結果はほとんど変わりませんでした。

HOGと迷った理由の一つが、
ベクタグラフィクスが多かった(輪廻ヒロもベクタグラフィクス)ため、
エッジが優位に効くのでは、と思ったからでした。

動画内でも指摘されてますが、
うまく識別できなかったのは単純に画風の問題かと思います。

## YOLO v3

動画の中で声担当の人がもらしていたように、
本当はここできっちり別れていればよかったんですけどね。

ちなみに、当初はサンタの1クラスのみを識別するようにしていましたが、
これだとnotサンタも全部検出してしまう、人型イラスト検出器になってしまいました。
ここまで20時間30000epochくらい学習していましたが一度破棄し、
2クラスにして15時間20000epochくらいで再度やり直しています。
(経験的に20000epochで十分識別性能は出る)
とはいえひたすら待つだけなので実際にはそこまで手間はかかっていませんが。

こちらも、そこまで色は優位に出ないんだな、という気づきがありました。


# おわりに

週一の番組で紹介する以上、
時間もなく個人的には納得の行く結果ではなかったのですが、
どんな結果でもともかくエンタテインメントとしてオチをつけなければならなかったので、
台本自体はどんな結果に転んでもいいように書いていました。
とはいえ不正確なこと、いい加減なことを発信するわけにはいかないため、
10分の枠内で講義ができるくらいの内容をぎっちり詰め込んでみました。

さすがにこの密度を毎週提供するのは難しいので
ガチ技術の話は月一くらいになるかもしれませんが、
これからも輪廻ヒロをよろしくお願いします。