アプリとサービスのすすめ

アプリやIT系のサービスを中心に書いていきます。たまに副業やビジネス関係の情報なども気ままにつづります

webカメラ+Arduino Uno+CNN画像分類(AI)でカギの開錠・施錠システムを作ってみた【hardware,機械学習】

今回はArduino機械学習(AI)を埋め込んで、webカメラから読み込んだストリーミング画像の画像分類結果からカギを解錠・施錠できるシステムを作ってみた。

機械学習(AI)をハードウェアで実際に取り込んで作ったのはVitis AI以来、今回がはじめて。

Arduino系の記事はたくさんあるけど、機械学習を使った例はあんまなかったので、今回はその作成過程をまとめてく。


目次
1.CNN+Arduinoの鍵の開錠・施錠システムの構造
2.データセットの作成・CNNで学習
3.CNN+Arduinoで金庫の鍵の開錠・施錠


1.CNN+Arduinoの鍵の開錠・施錠システムの構造

用意したもの

Arduino Uno
・ジャックDIP化キット
サーボモータ
・ACアダプター(5V)
・USBカメラ
・スイッチ


Arduinoの回路図の概略f:id:trafalbad:20200601112800j:plain



作業手順

1.webカメラで学習用画像集めてデータセットを作成する

2.CNNで学習

3.key.txtに開錠番号を記載

4.Webカメラで取り込んだストリーミング画像を推論
=> 推論結果とkey.txtと同じ番号ならArduinoでカギの解錠

今回工夫したのは、
Webカメラからデータセットを作れるようにした

Webカメラのストリーミング画像を推論してkey.txtの番号と一致したら解錠する仕組み

の2点。


実物f:id:trafalbad:20200601113319j:plain



2.データセットの作成・CNNで学習

学習から推論までは下のフローで進めてく。

Webカメラからデータセットを作成=>訓練=>Webカメラのストリーミング画像を推論

解錠システムのデータセット作成から推論までの流れは図にするとこんな感じ
f:id:trafalbad:20200601112958j:plain





Webカメラから学習データ収集


今回はWebカメラから直接データセットを作れるようにした。

訓練画像にしたい部分をwebカメラバウンディングボックス内に写す。
そのとき、PCキーボードの番号(0~5)を押せばその番号のimgファルダに画像が保存されるので、大量の画像を短時間で作れる。

今回は訓練画像は6種類。何も写ってない画像を0として含めた。

訓練画像1~5f:id:trafalbad:20200601113108j:plain

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に送る仕組み。
f:id:trafalbad:20200601112840j:plain




Webカメラで写したPC画面はこんな感んじ。
f:id:trafalbad:20200601113042p:plain

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''


下の画像は解錠・施錠を交互に行った動画。

この投稿をInstagramで見る

保存用動画

Tatsuya Hagiwara(@gosei_creater)がシェアした投稿 -

金庫がないのでわかりにくいけど、USBカメラに画像を写せば「ピピっ」と漫画みたいに反応した後、キーの解錠・施錠ができる。

セキュリティのために金庫の鍵の解錠施錠とかいろんな用途に使えそう。

f:id:trafalbad:20200601113617j:plain



Vitis AIでチュートリアルをやった以来、はじめてハードウェアで機械学習(AI)を組み混んでモノを作ってみた。
Arduinoは速度は置いといて、仕組みさえわかれば、かなり簡単に機械学習システムを組み込めた。


参考サイト


Arduinoを用いてサーボモータを制御する