Edited at

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

※こちらはPythonデータ分析勉強会#02の発表資料です。

前回は、YOLOv3でパトライトの監視を行いました。

今回は、YOLOv3で「将棋駒」を認識させます。

そして、用意する画像は一枚だけという、無謀な挑戦をしてみます。

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

tume.jpg


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

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

平気で書いてあります。

しかし、個人事業主や中小企業は、そんなに労力をかけて画像は用意できませんし、

1000枚用意しても、バリエーション豊かな画像じゃないと、意味がないことが多くあります。

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

従って、「用意する画像が一枚」で精度が出るなら、世の中のディープラーニングの

サービスがより使いやすくなる可能性があります。

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

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


なぜ、将棋駒なのか?


  • 肖像権を気にすることなく、リアルな画像が撮れるから

  • 駒の形が似ており、誤検知が起きやすい?から

  • 個人的に将棋が好きだから

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

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

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


用意した画像はこれだ!

original0.jpg

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

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

今回はこの写真一本で勝負します!

どうなることやら・・・


学習の準備

※YOLOのインストールやアノテーション作業のやり方、学習の実行などは前回の記事を参考にしてください。

用意した画像は1枚でも、学習データが1枚では少なすぎます。

そこで、ディープラーニングでお得意のData Augmentation(水増し)を行います。

今回は、PCA Color Augmentation(以下、PCACA)という強力な手法を使います。

そして、YOLOのモデルは、小さいtinyモデルを使います。

その理由は、将来的にラズパイやスマホで動かしたいからです。


Data Augmentation

今回は、通常のData Augmentationに加え、光の加減が変わっても認識できるようにPCACAを使います。

コードは@koshian2さんのを拝借しています。

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

将棋では、相手の駒は上下逆さになるため、180度回転させた画像も用意しています。

(matplotlibで描画すると、格子上の筋が入った画像になりますが、

実際の学習データに筋は入っておりません。)

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

original.jpg

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

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

pca.jpg

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

今回はこれらの水増し画像を400枚用意しました。

あとは、水増しされた画像でひたすらアノテーション作業をします。


結果

結果の前に、用意した画像を再掲します。

original0.jpg

この画像に対し、明るさを変えたり、駒の配置を変えたりして認識精度を見てみます。

まずは、暗めにした画像で試します。

ganki.jpg

歩は5枚中4枚を認識していますが、金は認識せず。。。

ただ、明らかに元画像より暗いのに、歩を認識できたのはPCACAのおかげといえるでしょう。

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

mino.jpg

こちらは、残念な結果に・・・

陰影が強いのか、歩もあまり認識できませんでした。

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

tume.jpg

こちらは、伊藤看寿作「煙詰め」一部を抜粋しています。

詰将棋なので、上下逆さの相手駒があるわけですが、相手駒は正確に認識できず・・・

相手駒の「銀」を「金」と認識している点は惜しい!といったところでしょうか。

学習させた「金」には「将」という文字が入っており、銀にも入っているので、誤認識してしまったと思われます。

ただ、「と金」を「歩」と認識しなかった点は評価できます。

「と金」と「歩」の駒のサイズは全く同じで、誤認識してしまうかなぁ・・と思っていました。

ところが、杞憂でした。YOLOは、ちゃんと文字(色)を見ていることが分かりました。

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

uws8g-hz84f.gif

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

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

全体に点数を付けるとすれば、20点くらいでしょうか。

精度アップさせる方法は、「まとめ」で書きましたので、参考になれば幸いです。


失敗作

実は、このチャレンジ一回やり直しています。

最初は、以下の画像を用意しました。

original.jpg

しかし、学習させても「金」は認識しないし、「玉」を「金」と認識したり、

「歩」の認識をやたら連発することがありました。

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


  • YOLOは一枚の画像を49等分に分割して、分割した画像で学習させています。しかし、用意した画像では、駒が密集しすぎて、分割した画像に隣の駒が映り込んでしまい、隣の駒も学習してしまったと思われます。

  • 一枚の画像で「歩」が9枚、「金」が2枚と不均衡なため、「金」を軽視する傾向にあります。機械学習でよくある、「不均衡なデータで学習したら、多数派しか学習しない」というやつです。


失敗から学ぶ

ということで、以下のポイントを意識して画像を撮り直しました。


  • 駒をまばらに置く。

  • 「歩」を2枚、「金」を2枚にして均衡な画像にする。

そして、撮り直したのが今回用意した画像です。


まとめ


  • PCACAを使えば、用意した画像から光の加減が変わっても、ある程度物体検出ができる。PCACA凄い!

  • 正直、「用意した画像は1枚」はやめた方が良い。せめて10枚くらいのバリエーション豊かな写真を用意した方が無難です。

  • ただ、普通にやると必要な画像が1000枚だったのが、PCACAを使うと10枚になる可能性があると思われます。

  • 本稿を書き終えた後に、Random Erasing Data Augmentationを行えば、さらに物体検出の精度が上がるという記事を見つけました。今度やるときは使ってみます。

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