今回はArduinoに機械学習(AI)を埋め込んで、webカメラから読み込んだストリーミング画像の画像分類結果からカギを解錠・施錠できるシステムを作ってみた。
機械学習(AI)をハードウェアで実際に取り込んで作ったのはVitis AI以来、今回がはじめて。
Arduino系の記事はたくさんあるけど、機械学習を使った例はあんまなかったので、今回はその作成過程をまとめてく。
目次
1.CNN+Arduinoの鍵の開錠・施錠システムの構造
2.データセットの作成・CNNで学習
3.CNN+Arduinoで金庫の鍵の開錠・施錠
1.CNN+Arduinoの鍵の開錠・施錠システムの構造
用意したもの
Arduinoの回路図の概略
作業手順
2.CNNで学習
3.key.txtに開錠番号を記載
4.Webカメラで取り込んだストリーミング画像を推論
=> 推論結果とkey.txtと同じ番号ならArduinoでカギの解錠
今回工夫したのは、
・Webカメラからデータセットを作れるようにした
・Webカメラのストリーミング画像を推論してkey.txtの番号と一致したら解錠する仕組み
の2点。
実物
2.データセットの作成・CNNで学習
学習から推論までは下のフローで進めてく。Webカメラからデータセットを作成=>訓練=>Webカメラのストリーミング画像を推論
解錠システムのデータセット作成から推論までの流れは図にするとこんな感じ
Webカメラから学習データ収集
訓練画像にしたい部分をwebカメラのバウンディングボックス内に写す。
そのとき、PCキーボードの番号(0~5)を押せばその番号のimgファルダに画像が保存されるので、大量の画像を短時間で作れる。
今回は訓練画像は6種類。何も写ってない画像を0として含めた。
訓練画像1~5
Webカメラで写しながら、キーボードの数字を押せばimgファルダの該当番号のファルダに保存されてく仕組み。
import cv2 n0 = 0 n1 = 0 n2 = 0 n3 = 0 n4 = 0 n5 = 0 cap = cv2.VideoCapture(0) while True: ret, frame = cap.read() gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) xp = int(frame.shape[1]/2) yp = int(frame.shape[0]/2) d = 400 # bbox size resize = 256 cv2.rectangle(gray, (xp-d, yp-d), (xp+d, yp+d), color=0, thickness=10) cv2.imshow('gray', gray) gray = cv2.resize(gray[yp-d:yp + d, xp-d:xp + d],(resize, resize)) c =cv2.waitKey(10) if c == 48:#0 cv2.imwrite('img/0/{0}.png'.format(n0), gray) n0 = n0 + 1 elif c == 49:#1 cv2.imwrite('img/1/{0}.png'.format(n1), gray) n1 = n1 + 1 elif c == 50:#2 cv2.imwrite('img/2/{0}.png'.format(n2), gray) n2 = n2 + 1 elif c == 51:#3 cv2.imwrite('img/3/{0}.png'.format(n3), gray) n3 = n3 + 1 elif c == 52:#4 cv2.imwrite('img/4/{0}.png'.format(n4), gray) n4 = n4 + 1 elif c == 53:#5 cv2.imwrite('img/5/{0}.png'.format(n5), gray) n5 = n5 + 1 elif c == 27:#Esc break cap.release()
$ tree img img ├── 0 │ └── 0.png ├── 1 │ └── 1.png ├── 2 │ └── 2.png ├── 3 │ └── 3.png ├── 4 │ └── 4.png └── 5 └── 5.png
CNNで学習
MNIST並みに単純な画像なので、普通のCNNで訓練した。
Google colab上でGPUで訓練してから、学習済みモデルをローカルに落として使うことで、簡単に再訓練ができる構造。
def create_model(inputs_): o = Conv2D(32, (3, 3), padding='same', kernel_initializer='random_uniform')(inputs_) o = channel_spatial_squeeze_excite(o) o = MaxPooling2D((2, 2))(o) o = Conv2D(64, (3, 3), padding='same', kernel_initializer='random_uniform')(o) o = channel_spatial_squeeze_excite(o) o = MaxPooling2D((2, 2))(o) o = Conv2D(64, (3, 3), padding='same', kernel_initializer='random_uniform')(o) o = channel_spatial_squeeze_excite(o) #x = Dense(64, activation='relu')(x) o = Flatten()(o) o = Dense(6, activation='softmax')(o) model = Model(inputs=inputs_, outputs=o) model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc']) return model def make_train_dataset(img_dir): x = [] y =[] label = 0 for c in os.listdir(img_dir): print('class: {}, class id: {}'.format(c, label)) d = os.path.join(img_dir, c) try: imgs = os.listdir(d) except: continue for i in [f for f in imgs if ('png' in f)]: x.append(cv2.imread(os.path.join(d, i), 0)) y.append(label) label += 1 return np.array(x)/255, to_categorical(y) X, Y = make_train_dataset('img') H = X.shape[1] W = X.shape[2] X = np.reshape(X, (len(X), H, W, 1)) model = create_model(inputs) model.summary() try: model.fit(X, Y, batch_size=batch_size, epochs=num_ep, callbacks=callback, validation_data=(valid_X, valid_y), shuffle=True) finally: model.save('CNN_model.h5')
3.CNN+Arduinoで金庫の鍵の開錠・施錠
Webカメラで深層学習の画像分類できるかテスト
バウンディングボックスに写った画像を推論。
推論結果がkey.txtの番号と一致していたら開錠の指令をシリアル通信でArduinoに送る仕組み。
Webカメラで写したPC画面はこんな感んじ。
Security.py
import numpy as np import os import cv2 import keras from keras.models import Model from keras.layers import * from train_modules.scse import channel_spatial_squeeze_excite from CNN_model import create_model import serial import time H = 256 W = 256 inputs = Input((H, W, 1)) model = create_model(inputs) model.load_weights('CNN_model.h5') key_file = 'key.txt' cap = cv2.VideoCapture(0) with serial.Serial('/dev/cu.usbmodem14301', timeout=0.1) as ser: while True: ret, frame = cap.read() gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) xp = int(frame.shape[1]/2) yp = int(frame.shape[0]/2) d = 400 # bbox size resize = 256 cv2.rectangle(gray, (xp-d, yp-d), (xp+d, yp+d), color=0, thickness=10) cv2.imshow('gray', gray) if cv2.waitKey(10) == 27: break a = ser.read() print(a) if a == b'1': print('pass') gray = cv2.resize(gray[yp-d:yp + d, xp-d:xp + d],(resize, resize)) img = np.asarray(gray,dtype=np.float32) img = np.reshape(img, (1, 256, 256, 1)) img = img/255 y_pred = model.predict(img) c = int(np.argmax(y_pred, axis=1)) print(c) with open('key.txt', 'r') as f: b = int(f.read()) if b==0: if c!=0: time.sleep(5.0) # wait 5 second ser.write(b"o") with open(key_file, 'w') as f: f.write(str(c)) print('close') else: if b==c: time.sleep(5.0) # wait 5 second ser.write(b"c") with open(key_file, 'w') as f: f.write('0') print('open') cap.release()
Aruduinoで画像分類して、サーボモータで鍵を開錠・施錠
シリアル通信で開錠の指令がきたらサーボモータを180度回転させて解錠。
番号が間違ってたら施錠したまま。
Security.ino
#include <Servo.h> Servo myservo; // Servoオブジェクト宣言 void setup() { Serial.begin(9600); myservo.attach(9); //9番ピンでサーボモータを動かす pinMode(2, INPUT_PULLUP); // Inputモードでプルアップ抵抗を有効 pinMode(LED_BUILTIN, OUTPUT); myservo.write(120); // angle digitalWrite(LED_BUILTIN, HIGH); } void loop(){ static int flag=0; if(digitalRead(2)==LOW){ flag=1; } else{ if(flag==1){ flag=0; Serial.write('1'); delay(500); } } if(Serial.available()>0){ char a = Serial.read(); if(a=='o'){ myservo.write(120); // angle digitalWrite(LED_BUILTIN, HIGH); } else if(a=='c'){ myservo.write(20); // angle digitalWrite(LED_BUILTIN, LOW); } } }
PythonコードとArduinoコードが用意できたら、ラストはPC側でPythonコードを実行。
Arduino回路のスイッチを押せば推論が行われるので、推論結果がkey.txtと合ってれば解錠。
# PC側で実行 $ python3 Security.py >>> Using TensorFlow backend. StreamExecutor device (0): Host, Default Version b'' b'' b’1' # 推論実行 pass 5 close # 不一致なので施錠 ~ b'' b'' b’1' # 推論実行 pass 3 open # key.txtの番号と推論結果が一致したので解錠 b''
下の画像は解錠・施錠を交互に行った動画。
金庫がないのでわかりにくいけど、USBカメラに画像を写せば「ピピっ」と漫画みたいに反応した後、キーの解錠・施錠ができる。
セキュリティのために金庫の鍵の解錠施錠とかいろんな用途に使えそう。
Vitis AIでチュートリアルをやった以来、はじめてハードウェアで機械学習(AI)を組み混んでモノを作ってみた。
Arduinoは速度は置いといて、仕組みさえわかれば、かなり簡単に機械学習システムを組み込めた。
参考サイト