用意した画像は一枚だけ！？物体検出で将棋駒の認識

※こちらは[Pythonデータ分析勉強会#02](https://pythondata.connpass.com/event/105345/)の発表資料です。

[前回](https://qiita.com/shinmura0/items/dc0d02dc1e716b912932)は、YOLOv3でパトライトの監視を行いました。

今回は、YOLOv3で「将棋駒」を認識させます。
そして、**用意する画像は一枚だけ**という、無謀な挑戦をしてみます。

前回から、内容がガラリと変わっておりますが、まずはそのモチベーションを説明します。

![tume.jpg](https://qiita-image-store.s3.amazonaws.com/0/264781/da56b6f2-a0fc-4750-cabb-6da67333c11c.jpeg)

#なぜ、用意した画像は一枚なのか？

よくディープラーニングのサービスなんかを見ていると、用意する画像は1000枚とか
平気で書いてあります。

しかし、個人事業主や中小企業は、そんなに労力をかけて画像は用意できませんし、
1000枚用意しても、バリエーション豊かな画像じゃないと、意味がないことが多くあります。

用意する画像が一枚だけなら、**労力は最小限ですし**、バリエーションも気にしなくて良いです。

従って、「用意する画像が一枚」で精度が出るなら、世の中のディープラーニングの
サービスがより**使いやすく**なる可能性があります。

~~今回はその検証を行います。~~

今回はその方法を模索します。

#なぜ、将棋駒なのか？

+ 肖像権を気にすることなく、リアルな画像が撮れるから
+ 駒の形が似ており、誤検知が起きやすい？から
+ ~~個人的に将棋が好きだから~~

個人的な趣味はさておき、将棋駒をターゲットにすると、**YOLOの検証がしやすくなります。**

ただし、作業時間を考え、今回は「歩」と「金」だけを認識させることにしました。

本当はパトライトでやりたかったのですが、リアルな画像が用意できないので断念しました。

#用意した画像はこれだ！

![original0.jpg](https://qiita-image-store.s3.amazonaws.com/0/264781/8da2186d-8114-0714-f494-eb0c217e4f76.jpeg)

こちらは、100均で買った将棋駒です。

10年以上前に購入したため、かなり ~~汚い~~ 深みのある風合いになっています。

今回はこの写真一本で勝負します！
どうなることやら・・・

#学習の準備
※YOLOのインストールやアノテーション作業のやり方、学習の実行などは[前回](https://qiita.com/shinmura0/items/dc0d02dc1e716b912932)の記事を参考にしてください。

用意した画像は1枚でも、学習データが1枚では少なすぎます。
そこで、ディープラーニングでお得意のData Augmentation（水増し）を行います。

今回は、[PCA Color Augmentation](https://qiita.com/koshian2/items/78de8ccd09dd2998ddfc)（以下、PCACA）という強力な手法を使います。

そして、YOLOのモデルは、小さいtinyモデルを使います。
その理由は、将来的にラズパイやスマホで動かしたいからです。

#Data Augmentation
今回は、通常のData Augmentationに加え、光の加減が変わっても認識できるようにPCACAを使います。
コードは@koshian2さんのを拝借しています。

```py
import matplotlib.pyplot as plt
from keras.preprocessing import image
from keras.preprocessing.image import ImageDataGenerator
import cv2
import numpy as np
import os

def pca_color_augmentation_modify(image_array_input):
    assert image_array_input.ndim == 3 and image_array_input.shape[2] == 3
#    assert image_array_input.dtype == np.uint8

    img = image_array_input.reshape(-1, 3).astype(np.float32)
    # 分散を計算
    ch_var = np.var(img, axis=0)
    # 分散の合計が3になるようにスケーリング
    scaling_factor = np.sqrt(3.0 / sum(ch_var))
    # 平均で引いてスケーリング
    img = (img - np.mean(img, axis=0)) * scaling_factor

    cov = np.cov(img, rowvar=False)
    lambd_eigen_value, p_eigen_vector = np.linalg.eig(cov)

    rand = np.random.randn(3) * 0.1
    delta = np.dot(p_eigen_vector, rand*lambd_eigen_value)
    delta = (delta * 255.0).astype(np.int32)[np.newaxis, np.newaxis, :]

    img_out = np.clip(image_array_input + delta, 0, 255).astype(np.uint8)
    return img_out

img_path = 'picture/original02.jpg'
save_path = 'picture/'

# 画像ファイルをPIL形式でオープン
img = image.load_img(img_path)
# PIL形式をnumpyのndarray形式に変換
x = image.img_to_array(img)
# (height, width, 3) -> (1, height, width, 3)
x = x.reshape((1,) + x.shape)

datagen = ImageDataGenerator(
           rotation_range=5,
           width_shift_range=0.1,
           height_shift_range=0.1,
           shear_range=0,
           zoom_range=[0.9,1.1],
           horizontal_flip=False,
           vertical_flip=False)

max_img_num = 10
NO = 1

for d in datagen.flow(x, batch_size=1):
    # このあと画像を表示するためにndarrayをPIL形式に変換して保存する
    temp = image.img_to_array(d[0])
    temp = pca_color_augmentation_modify(temp)
    cv2.imwrite(save_path + "Aug_{0}.jpg".format(NO), np.asarray(temp)[..., ::-1])
    # datagen.flowは無限ループするため必要な枚数取得できたらループを抜ける
    if (NO % max_img_num) == 0:
        print("finish")
        break
    NO += 1
```

まずは、用意した画像と180度回転させた画像2つを用意します。

![test.jpg](https://qiita-image-store.s3.amazonaws.com/0/264781/1142c407-8e82-31e5-f351-f97b94d58e9b.jpeg)

将棋では、相手の駒は上下逆さになるため、180度回転させた画像も用意しています。
（matplotlibで描画すると、格子上の筋が入った画像になりますが、
実際の学習データに筋は入っておりません。）

そして、ベーシックな拡大/縮小と回転、水平・垂直移動させます。

![original.jpg](https://qiita-image-store.s3.amazonaws.com/0/264781/95a41bee-ac7b-a46a-4f3f-8c23f54b3d96.jpeg)


ここまでは前回と同じです。

そして、今回の目玉PCACAで明るさを変えて、光の加減が変わっても認識できるようにします。

![pca.jpg](https://qiita-image-store.s3.amazonaws.com/0/264781/1f054004-2b61-195d-5540-632c6f40b3c8.jpeg)


ここまでくると、バリエーション豊かな写真が出てきます。


今回はこれらの水増し画像を400枚用意しました。
あとは、水増しされた画像でひたすらアノテーション作業をします。

#結果

結果の前に、用意した画像を再掲します。
![original0.jpg](https://qiita-image-store.s3.amazonaws.com/0/264781/8da2186d-8114-0714-f494-eb0c217e4f76.jpeg)
この画像に対し、明るさを変えたり、駒の配置を変えたりして認識精度を見てみます。

まずは、暗めにした画像で試します。
![ganki.jpg](https://qiita-image-store.s3.amazonaws.com/0/264781/378846a6-a4aa-1aeb-fc84-fdaa8cececfd.jpeg)

歩は5枚中4枚を認識していますが、金は認識せず。。。
ただ、明らかに元画像より暗いのに、歩を認識できたのはPCACAのおかげといえるでしょう。

次に照明を強くした画像で試します。

![mino.jpg](https://qiita-image-store.s3.amazonaws.com/0/264781/f6f73f6f-42d8-8b57-1eb8-89638a053547.jpeg)

こちらは、残念な結果に・・・
陰影が強いのか、歩もあまり認識できませんでした。

次に、詰将棋の画像で試します。

![tume.jpg](https://qiita-image-store.s3.amazonaws.com/0/264781/da56b6f2-a0fc-4750-cabb-6da67333c11c.jpeg)

こちらは、伊藤看寿作[「煙詰め」](https://ja.wikipedia.org/wiki/%E7%85%99%E8%A9%B0)一部を抜粋しています。
詰将棋なので、上下逆さの相手駒があるわけですが、相手駒は正確に認識できず・・・

相手駒の「銀」を「金」と認識している点は惜しい！といったところでしょうか。
学習させた「金」には「将」という文字が入っており、銀にも入っているので、誤認識してしまったと思われます。

ただ、「と金」を「歩」と認識しなかった点は評価できます。
「と金」と「歩」の駒のサイズは全く同じで、誤認識してしまうかなぁ・・と思っていました。
ところが、杞憂でした。YOLOは、ちゃんと**文字（色）を見ていることが分かりました。**

最後に、動画で試してみます。

![uws8g-hz84f.gif](https://qiita-image-store.s3.amazonaws.com/0/264781/d444b838-45fd-44c0-32fc-bd6ff0a914b6.gif)

ご覧のとおり、散々な結果に・・・

金は認識しないし、「歩」を「金」と誤認識してしまいます。

全体に点数を付けるとすれば、20点くらいでしょうか。
精度アップさせる方法は、「まとめ」で書きましたので、参考になれば幸いです。

##失敗作

実は、このチャレンジ一回やり直しています。
最初は、以下の画像を用意しました。

![original.jpg](https://qiita-image-store.s3.amazonaws.com/0/264781/592c1305-341c-d5cc-45f3-5b587a21c363.jpeg)

しかし、学習させても「金」は認識しないし、「玉」を「金」と認識したり、
「歩」の認識をやたら連発することがありました。

失敗の理由は以下のとおりです。

+ YOLOは一枚の画像を49等分に分割して、分割した画像で学習させています。しかし、用意した画像では、駒が密集しすぎて、分割した画像に隣の駒が映り込んでしまい、**隣の駒も学習してしまった**と思われます。
+ 一枚の画像で「歩」が9枚、「金」が2枚と**不均衡なため、「金」を軽視する傾向にあります。**機械学習でよくある、「不均衡なデータで学習したら、多数派しか学習しない」というやつです。

##失敗から学ぶ
ということで、以下のポイントを意識して画像を撮り直しました。

+ 駒をまばらに置く。
+ 「歩」を2枚、「金」を2枚にして均衡な画像にする。

そして、撮り直したのが今回用意した画像です。
 
#まとめ
+ PCACAを使えば、用意した画像から光の加減が変わっても、ある程度物体検出ができる。**PCACA凄い！**
+ 正直、「用意した画像は１枚」はやめた方が良い。せめて**10枚くらい**のバリエーション豊かな写真を用意した方が無難です。
+ ただ、普通にやると**必要な画像が1000枚だったのが、PCACAを使うと10枚になる**可能性があると思われます。
+ 本稿を書き終えた後に、[Random Erasing Data Augmentation](https://qiita.com/dsanno/items/d32f3c928cdbcbe5de60)を行えば、さらに物体検出の精度が上がるという記事を見つけました。今度やるときは使ってみます。

次回はラズパイでYOLOを動かします。