エンジニア ろっきーの探究ノート

"不"を"したい"にひっくり返すモノ/コトづくり(H/W, S/W, IoTシステム, アイデア発想)技術を探究します。

ラズパイ× 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の速度制御

ができそうかな。おしまい。