るぴブロ

備忘録とかです(*'ω'*)

AR Fukuoka リアルタイム手書き数字認識AIを作って遊ぼうに参加してきました!

こんにちは → こんばんは!!
今日はエンジニアカフェでこちらに参加、少しだけお手伝いしてきました😊

xr-fukuoka.connpass.com

f:id:rupic:20200215131833p:plain エンジニアカフェでイベント開催すると黒板にイラストつきな紹介を書いてくれてるんですが、何気にこれ嬉しいですよね!!

今回イベントに参加しながらブログを書いているので、ちょっと長めになっちゃってます🙇🏻‍♂️

イベントの雰囲気

f:id:rupic:20200215132053p:plain
師匠によるいつもの挨拶

f:id:rupic:20200215132206p:plain
IST 甲斐さんから今日やることのご紹介

ディープラーニングとは

f:id:rupic:20200215133007p:plain
ガッキーは可愛いので正義

本日のお題

Tensorflow + Keras + Opencv を利用して、PCのカメラで撮影した手書き文字をリアルタイムで認識

  • 難しい事はさておいて、ざっくり説明すると Tensorflow はゲームのハード、Keras がゲームソフト

まずはMNISTで遊んでみよう

MNISTをKerasで使える形にする

import numpy as np
from keras.datasets import mnist
from keras.utils import np_utils

#MNISTデータセット呼び出し
(X_train, y_train), (X_test, y_test) = mnist.load_data()

#データの正規化
X_train = np.array(X_train)/255.
X_test = np.array(X_test)/255.

#CNNに入れるために次元を追加
X_train = np.expand_dims(X_train, axis=-1)
X_test = np.expand_dims(X_test, axis=-1)

#ラベルのバイナリ化
y_train = np_utils.to_categorical(y_train)
y_test = np_utils.to_categorical(y_test)

モデルの構築

from keras.layers import Input, Dense, Conv2D, MaxPooling2D
from keras.models import Model
from keras.layers.core import Flatten

inputs = Input(shape=(28,28,1)) #入力層

conv1 = Conv2D(16, (3, 3), padding='same', activation='relu')(inputs) #畳込み層1
pool1 = MaxPooling2D((2, 2))(conv1)
conv2 = Conv2D(32, (3, 3), padding='same', activation='relu')(pool1) #畳込み層2
pool2 = MaxPooling2D((2, 2))(conv2)

flat = Flatten()(pool2) #全結合層に渡すため配列を一次元化
dense1 = Dense(784, activation='relu')(flat) #全結合層

predictions = Dense(10, activation='softmax')(dense1) #出力層

model = Model(inputs=inputs, outputs=predictions) #モデルの宣言(入力と出力を指定)

モデルのコンパイル、学習実行

model.compile(optimizer="sgd", loss="categorical_crossentropy", metrics=["accuracy"])
hist = model.fit(X_train, y_train, batch_size=16, verbose=1, epochs=1, validation_split=0.9)

学習結果の評価

score = model.evaluate(X_test, y_test, verbose=1)
print("正解率(acc):", score[1])

モデルを保存

model.save("MNIST_test.h5")

作成されたモデルの確認

f:id:rupic:20200215135707p:plain
モデルが作成された!

f:id:rupic:20200215140253p:plain

作成した学習モデルを利用して評価する

from keras.models import load_model
import cv2
import numpy as np

model = load_model("mnist.h5") # 学習済みモデルをロード

Xt = []
img = cv2.imread("6.jpg", 0)
img = cv2.resize(img,(28, 28), cv2.INTER_CUBIC)

Xt.append(img)
Xt = np.array(Xt)/255
Xt = np.expand_dims(Xt, axis=-1)

result = model.predict(Xt, batch_size=1)
print(result[0])

画像加工処理をやってみよう

OpenCVを利用して、撮影している画像をリアルタイムにKerasで認識できる形に加工する

f:id:rupic:20200215140837p:plain
こんなイメージ。分かり易い!

f:id:rupic:20200215142807p:plain
皆さん真剣に聴かれてますね!

まずはOpenCVを使って画像を表示してみる

f:id:rupic:20200215143715p:plain
界隈では有名人な Lenna さんの画像を利用

import cv2

#Lennaを召喚する呪文:0→グレースケール、1→カラー
image = cv2.imread('Lenna.png',1)
#testウインドウにLennaを召喚
cv2.imshow('test',image)
#何かしら操作があるまで待つ:0なので無限に待つよ
cv2.waitKey(0)
#ウインドウを破棄する
cv2.destroyAllWindows()
#Macの人は以下を入れとかないとエラー
cv2.waitKey(1)

f:id:rupic:20200215144434p:plain
実行するとこんな感じですね!美人さんです。

teratail.com

読み込んだ画像をグレーに変えてみる

*方法は簡単。2行ほど追記するだけ!

#グレースケールに変換
gray = cv2.cvtColor(image,cv2.COLOR_BGR2GRAY)

#グレー用のウインドウを追加
cv2.imshow('gray',gray)

f:id:rupic:20200215150328p:plain
先ほどのカラーとグレースケールのLennaさんが表示されます。

次は画像に資格の枠を表示する

  • OpenCV は画像の左上を原点として座標を指定します。
  • 以下のコードを追加
#四角を表示する
cv2.rectangle(image,(50,50),(100,100),(255,0,0))

f:id:rupic:20200215150924p:plain
こんな感じに枠が表示されますね

画像に文字を書く

#(窓,書き込む文字列,座標,フォント,サイズ,色,太さ,線の種類)
cv2.putText(image,'Hello World',(0,30),cv2.FONT_HERSHEY_SIMPLEX,1,(255,255,255),1,cv2.LINE_AA)

f:id:rupic:20200215151804p:plain
日本語などの2バイト文字を表示する為には少し工夫が必要

画像を二値化→白黒反転→ブラーをかける

#大津の方法で二値化する
_, th = cv2.threshold(gray,0,255,cv2.THRESH_OTSU)
#白黒反転
th = cv2.bitwise_not(th)
#ブラーを書ける
th = cv2.GaussianBlur(th,(9,9),0)

f:id:rupic:20200215152420p:plain
二値化した画像

f:id:rupic:20200215152538p:plain
白黒反転した画像

f:id:rupic:20200215152650p:plain
ブラーをかけた画像

画像をトリミング

th = th[50:150,50:150]

f:id:rupic:20200215153043p:plain
指定したサイズにトリミングした画像

画像を縮小

#50 x 50 にトリミング
th = cv2.resize(th,(50,50),cv2.INTER_CUBIC)

f:id:rupic:20200215153226p:plain
指定サイズに縮小する

MNISTの中身をみてみよう

ここまでの内容を合体させて、先ほど利用したMNISTの中身の画像をOpenCVを利用して見てみましょう

from keras.datasets import mnist
import cv2

#MNISTを画像部分だけロード
(X_train,_),(_,_) = mnist.load_data()
#X_trainの100番目を取得して表示
cv2.imshow('mnist',X_train[100])

cv2.waitKey(0)
cv2.destroyAllWindows()
cv2.waitKey(1)

f:id:rupic:20200215154008p:plain
指定した配列番号のもじが表示される

PC内臓のカメラの画像を表示する

  • PCのカメラにアクセスする為には、ご利用の環境によって段階を踏まないといけません。
    ① どのカメラを利用するか指定する
    ② カメラから画像を読み込む
    ③ カメラの画像を表示する
    ④ カメラを開放する

静止画

import cv2

#①カメラを指定
cap = cv2.VideoCapture(0)
#②カメラから画像を読み込む
ret,frame = cap.read()
#③カメラの画像を表示する
cv2.imshow('frame',frame)

cv2.waitKey(0)

#④カメラを開放する
cap.release()
cv2.waitKey(1)

cv2.destroyAllWindows()
cv2.waitKey(1)

f:id:rupic:20200215160523p:plain
油断してるとおかしな顔が取れるので気をつけてください

動画

  • OpenCVでは静止画をひたすら撮影→表示し続ける事で動画となるという思想なので以下で動画に対応できます
  • 終了判定がないと無限に動画が表示されてしまうので、今回はキーボードのQが入力されると終了するっていうコードに、静止画コードの②と③を変更します。
while(True):
    ret,frame = cap.read()
    cv2.imshow('frame',frame)

    #キーボードのQが押されたら終了させる
    k =  cv2.waitKey(1) & 0xFF
    if  k == ord('q'):break

動画の中心に四角を表示する

f:id:rupic:20200215162925p:plain
考え方はこちらです

while (True):
    ret,frame = cap.read()

    #サイズでかいとき対策として半分にする
    h,w,_=frame.shape[:3]
    frame = cv2.resize(frame, (int(w/2), int(h/2)))
    
    #画像の形(高さ,幅,チャンネル数)
    h,w,_=frame.shape[:3]
    #y軸の中心点を取得
    h_center=h//2
    #x軸の中心点を取得
    w_center=w//2
    
    cv2.rectangle(frame,(w_center-71,h_center-71),(w_center+71,h_center+71),(255,0,0))

リアルタイム手書き数字認識を作成する

  • ここまでくれば色々な事に応用できますね😊
  • 本日の本題であるリアルタイム文字認識を作成してみましょう!!
  • コツはPCのInカメラを利用した時に自分の動きが左右反転してしまうので、frame = cv2.flip(frame,1)で反転してあげると良い

完成コード

import cv2
from keras.models import load_model
import numpy as np

#学習モデルを読み込む
model = load_model('mnist.h5')

#カメラを指定する
cap = cv2.VideoCapture(0)

while (True):
    #判定用データを格納する変数
    Xt = []
    Yt = []
        
    ret,frame = cap.read()

    #サイズでかいとき対策として半分にする
    h,w,_=frame.shape[:3]
    frame = cv2.resize(frame, (int(w/2), int(h/2)))
    
    #画像の形(高さ,幅,チャンネル数)
    h,w,_=frame.shape[:3]
    #y軸の中心点を取得
    h_center=h//2
    #x軸の中心点を取得
    w_center=w//2
    
    cv2.rectangle(frame,(w_center-31,h_center-31),(w_center+31,h_center+31),(255,0,0))
    
    #トリミング(y,x)なので注意
    im=frame[h_center-30:h_center+30,w_center-30:w_center+30]
    #グレースケール化
    im=cv2.cvtColor(im,cv2.COLOR_BGR2GRAY)
    #二値化
    _,im =cv2.threshold(im,0,255,cv2.THRESH_OTSU)
    #白黒反転
    im=cv2.bitwise_not(im)
    #ブラーをかける
    im=cv2.GaussianBlur(im,(9,9),0)
    #28 x 28 にリサイズする
    im=cv2.resize(im,(28,28),cv2.INTER_CUBIC)
    
    Xt.append(im)
    Xt = np.array(Xt)/255
    Xt = np.expand_dims(Xt,axis=-1)
    #Xtと学習したデータを比較して判定結果を返す
    result = model.predict(Xt,batch_size=1)
    
    #画面の反転
    frame = cv2.flip(frame,1)
    
    for  i in range(10):
        #小数点以下第二位までで四捨五入
        r = round(result[0,i],2)
        #OpenCVで表示できる型に変換
        r  = "{0:0.2f}".format(r)
        #その数字が何かと一致する確率をセットで格納
        Yt.append([i,r])
        #Ytをソートする
        Yt = sorted(Yt,key=lambda x:(x[1]))
    
    #判定結果の上位1番目を画面上に表示する
    cv2.putText(frame,'1st:'+str(Yt[9]),(10,50),cv2.FONT_HERSHEY_SIMPLEX,0.8,(255,255,255),1,cv2.LINE_AA)
    
    cv2.startWindowThread()
    cv2.imshow("frame",frame)
    cv2.imshow("debug",im)
    key = cv2.waitKey(30) & 0xFF
    if key ==ord('q') :
        break

#カメラを開放する
cap.release()
#フリーズ対策としてwaitKeyで挟む
cv2.waitKey(1)
cv2.destroyAllWindows()
cv2.waitKey(1)

f:id:rupic:20200215171908p:plain
完成!!!
f:id:rupic:20200215172659p:plain
画面キャプチャはこんな感じ

まとめ

  • 甲斐さんの説明がめっちゃ丁寧だし分かり易いし感動!!!!
  • やっぱりガッキーは可愛い
  • ブログ書きながらは忙しすぎてしんどい事が判明w
  • いち早くビール飲みたい