ラズパイ× tensorflowでプラレール自動運転(3) 学習したモデルでエッジコンピューティング
tensorflowで駅の写真を学習・推論させ、プラレールをラズパイで自動運転する第3回!学習させたモデルを使い、エッジで推論させながら駅を見つけたら停車させます。
1.学習および動作環境
撮った写真はGoogle ColaboratoryでKerasを利用したpythonプログラムを組んで学習させます。出力層は「駅があるとき」「駅がないとき」の2層だけの分類とし、中間層を2層や3層、Dropoutさせたりさせなかったりしましたが評価データに対する精度=0.89くらい、F1スコア=0.85くらいなのはおよそ変わらず。世の中にころがっているサンプルと大きく変わらないので、この記載は省略。
ただし、Google ColaboratoryはデフォルトでTensorflow 2.x系が走ります。ラズパイの推論プログラムをTensorflow 1.x系で作っていて学習モデル読み込み時にエラーが起こったため、Google Colaboratoryで学習させる時は
%tensorflow_version 1.x
を実行して「TensorFlow 1.x selected.」と表示されたあとに学習プログラムを走らせます(これがうまく反映されないときは「メニュー→ランタイム→ランタイムを再起動」をクリック)。こうして同じディレクトリ内に生成される学習モデル「.h5ファイル」と「.jsonファイル」をラズパイにコピーします。
バージョンの不一致はつまずくポイントですよね。今回動作したバージョン一覧はこちら。
対象 | バージョン |
---|---|
ラズパイ | Raspberry Pi 3 Model B+ |
OS | Raspbian GNU/Linux 10 (buster) OS image Release date:2021-05-07 |
Keras | 2.3.1 |
Tensorflow | 1.14.0 |
python | 3.7.3 |
2.自動運転、やってみよう!
写真を撮りながら推論し、90%以上の確率で駅のある画像と判定されたら減速→停車→再力行と動かすプログラムは以下。
import numpy as np import keras from keras.models import model_from_json from keras.datasets import mnist import random, glob, picamera, time, shutil from keras.preprocessing.image import load_img, img_to_array import RPi.GPIO as GPIO from PIL import Image GPIO.setmode(GPIO.BOARD) # Raspberry Pi用 MAX14870搭載 デュアルモータードライバのピンアサイン DIR_1 = 18 DIR_2 = 22 EN_N = 29 PWM1 = 32 PWM2 = 33 # 各ピンを出力ピンに設定 GPIO.setup(DIR_1, GPIO.OUT, initial=GPIO.LOW) # LOW:forward GPIO.setup(DIR_2, GPIO.OUT, initial=GPIO.LOW) GPIO.setup(EN_N, GPIO.OUT, initial=GPIO.LOW) # LOW active GPIO.setup(PWM1, GPIO.OUT, initial=GPIO.LOW) GPIO.setup(PWM2, GPIO.OUT, initial=GPIO.LOW) # PWM オブジェクトのインスタンスを作成 p1 = GPIO.PWM(PWM1, 4000) p2 = GPIO.PWM(PWM2, 4000) #---------------------------------------------------------------- #モデルを読み込む(プログラムと同じディレクトリに入れておく) model = model_from_json(open('model_station.json').read()) #重みを読み込む(プログラムと同じディレクトリに入れておく) model.load_weights('model_station.h5') #損失関数、オプティマイザを指定 model.compile(loss='categorical_crossentropy', optimizer='adam') print("mode loaded") #---------------------------------------------------------------- # PWM信号を出力 p1.start(0) p2.start(0) p1.ChangeDutyCycle(1) p2.ChangeDutyCycle(1) try: while 1: #---------------------------------------------------------------- with picamera.PiCamera() as camera: camera.resolution = (640, 480) camera.rotation = 180 # 撮影し、ファイルに保存 camera.capture("pic_1_org" + '.jpg') #---------------------------------------------------------------- # 写真の右下(駅が映り込む部分)をトリミングする im = Image.open('/home/pi/Desktop/pic_1_org.jpg') im_crop = im.crop((320, 240, 640, 480)) im_crop.save('/home/pi/Desktop/pic_1.jpg', quality=95) filename = "pic_1" + '.jpg' #---------------------------------------------------------------- data = load_img(filename, target_size=(32,32)) #推論用の画像32x32で読み込み data = img_to_array(data) #3次元PILから3次元ndarrayに data = data.astype('float32')/ 255.0 #データを0.0~1.0へ正規化 data = np.expand_dims(data, axis=0) #次元を合わせる #--------------------------------------------------------------------- #推論する classifi = model.predict(data) #predsのインデックスでソートする index_sort = np.argsort(classifi) #最大のインデックスを出す index = index_sort[0][-1] #どのラベルと分類されたかを表示する label_list = ["nostation", "station_OK"] print("予測 : " +str(label_list[index])) #分類した結果の確率を表示する probability = preds[0][index] * 100 print("確率 : " + str(probability) + " %") #--------------------------------------------------------------------- if index == 1 and probability >= 90: p1.ChangeDutyCycle(0.2) #駅を見つけたら1秒間は減速して走る p2.ChangeDutyCycle(0.2) time.sleep(1) p1.ChangeDutyCycle(0.0) #2秒停車する p2.ChangeDutyCycle(0.0) time.sleep(2) p1.ChangeDutyCycle(0.2) #再びゆっくり走りだす p2.ChangeDutyCycle(0.2) time.sleep(1) else: p1.ChangeDutyCycle(28) p2.ChangeDutyCycle(28) except KeyboardInterrupt: # PWM を停止 p1.stop() p2.stop()
駅を見つけて停まった!
駅を発見できず通り過ぎることはなかったが、まれに全然関係ないところで勝手に停まるのはなんだろう、何を駅と間違えているのだ、、、。いずれにせよ学習データ集め~クラウドで学習~エッジで推論してH/Wを制御という一連の流れを行った。今後は、
- 推論側の処理速度向上・最適化
- 他の駅や前方に他の車両があるときも学習
- 分類ではなく回帰としてPWMの速度制御
ができそうかな。おしまい。