Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

※こちらは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を動かします。

shinmura0
自己紹介はツイッターをご覧ください。 https://twitter.com/shinmura0
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away