PikaPikaLight

主に光モノ工作の忘備録

M5StickVでIME入力モードを判別してLチカさせてみた

f:id:PikaPikaLight:20210417084318p:plain

構想

M5StickVでパソコンのモニター画面のIMEのモード表示「あ」or「A」を判別させ、「あ」ならLEDを点灯させ、日本語モードであることがキーボードに目線を置いていても分かるようにする。ブラインドタッチができない人間(私)が日々悩まされている問題に先端技術を持って取り組む。

環境&Version

Windows10
SDカード:SanDisk 32G HC Class10
EasyLoader(V5.1.2)
VTraining-Client-VerA02B01

V-Training実施レポ

M5StickVはV-Trainingという画像判別のための至れり尽くせりなサービスがある。 M5Stack Docs - The reference docs for M5Stack products.
基本的にはこのページに従って進めていけばOK。

  1. EasyLoaderでファームウェアの書き込み

  2. VTraining-Client.zipをダウンロードして中身をSDカードに書き込み。

  3. M5StickVの電源を入れると学習用データ撮影モードのソフトが起動する。

学習には3クラス以上必要で、1クラス35枚以上の写真が必要と書いてあるので、「あ」、「A」、「×」の3種類の写真を35枚ずつ撮影。

f:id:PikaPikaLight:20210416205033p:plain
学習用の写真 3クラス
trainフォルダとvaildフォルダができるので二つのフォルダを選択した状態で(これ肝心らしい)→送る→圧縮(zip形式)フォルダ でzipファイルを作成。
それをデータアップロードページでアップロード
http://v-training.m5stack.com/
しばらく待つとメールで結果が返ってくる。

が、1回目の結果は

[V-Trainer] *** Online Training Request Failed
Hi! Sorry for that, there is some error happened during the training process. We attached the reason below, if you have any questions, welcome to report to us through Twitter, Facebook, or Forum. Or, check out our docs here: https://docs.m5stack.com/#/en/related_documents/v-training
USERID: *** CONTENT: Lake of Enough Train Dataset, Only 78 pictures found, but you need 90 in total.

Notice:
Image 16.jpg was removed for being too close to 82.jpg, hashdiff: 2
Image 93.jpg was removed for being too close to 82.jpg, hashdiff: 2
・・・

自分のつたない英語力でも読み取れます。「90個以上の写真がないとだめだけど君のは78個しかなかったよ。ちなみに似た画像があったから消しちゃったよ。 」
なるほど。似た画像になってしまうのは避けられないので、これはいっぱい写真を撮るしかない、ということで2回目は各100枚の写真を撮って、再チャレンジ。 今度は成功。

[V-Trainer] *** Online Training Request Finished
Model: Classification MobileNetV1 Alpha: 0.7 Depth: 1 f:id:PikaPikaLight:20210416210949p:plain

学習曲線がついていて、学習がうまくいったのか分かる仕組み。これが無料でつかえるとは有り難い。
メールのURLから結果ファイルをダウンロードして、中身をSDカードに書きこめばOK
SDカード直下のboot.pyが起動時に実行されるようなので、最初の撮影用のファイル一式は別フォルダ作って移動させておいた。  

起動して、パソコン画面にカメラを向けると、「おお!判別している」 (文字ちっちゃ ) けど正答率はいまいち。
手持ち撮影による不安定さが原因かもしれないと思い、今度はカメラを台座に固定して、しっかり位置キープした状態で再々チャレンジ。

初めてのPython

M5StickV、ボタン押しにくい。台座に固定した状態ではなおさら。なので1回押したら100回連続で撮影するようにプログラムを修正することにした。

元のコード のこの部分に

try:
    while(True):
        img = sensor.snapshot()
        if but_a.value() == 0 and isButtonPressedA == 0:
            if currentImage <= 30 or currentImage > 35:

for文を追加

try:
    while(True):
        img = sensor.snapshot()
        if but_a.value() == 0 and isButtonPressedA == 0:
            for num in range(100):
                if currentImage <= 30 or currentImage > 35:

生まれて初めてのPythonプログラミング 。コンパイルとかせずに、ただ書いたコードをSDに入れるだけで動くというのも驚きの仕組み。
ドキドキしながらシャッター(Aボタン)オン → 画面固まる → カチャ音100回 → 同じ写真が100枚取れる。

forループの中に  img = sensor.snapshot() を入れてなかった。サウンドも消してなかったので延々カチャカチャ。
それでもエラーではなかったので、気を取り直して修正。
validationデータが31-35の5枚だけなのもよくないかと思い、5枚に1枚をvalidationデータにするように修正。

try:
    while(True):
        img = sensor.snapshot()

        if but_a.value() == 0 and isButtonPressedA == 0:
            for num in range(100):  #100回撮影
                time.sleep(0.1)  #100ms delay入れておく。
                img = sensor.snapshot()
                #if currentImage <= 30 or currentImage > 35:
                if currentImage % 5 != 0: #5枚に1枚をvalidationデータにする
                #以下略。play_sound("/sd/kacha.wav")はコメントアウト

再々チャレンジの結果はまずまず。カメラ位置の調整はしないといけないが、ちゃんと3クラスの判別ができている。
こんなに簡単にAIカメラが作れるとは驚き。

試しているとフリーズする現象が発生。調子いい時もあるが、だいたい途中でフリーズする。USBシリアルでログを見てみると pmax=max(plist) のところで型エラーが起きているみたい。 Pythonの型が全く分かっていないのでエラーを読み解けない。とりあえず覚えたての必殺技 try: except: pass でエラー起きても無視させてみる。結果、フリーズすることはなくなった。こんなんでいいのかと思いながらも先に進む。

あとは「あ」の時にLEDを光らせる仕組みを入れる。
M5StickV自身にもLEDはついているがカメラ側についているのでこれは使えない。使えるピンはGroveコネクタのところだけなので、ここを使うしかない。LED光らせるだけなら直接ドライブさせてもいいと思うが、手持ちにLEDドライバの基板があったのでこれを利用。コード的には直接ドライブと一緒。
あとLCDの画面は位置合わせの時には便利だが、常時ついていると気になるので、ボタンを押したら消せるようにしてみた。

最終的なコードがこれ(初めてのPython

import image
import lcd
import sensor
import sys
import time
import KPU as kpu
from fpioa_manager import *
from board import board_info
import KPU as kpu
from Maix import GPIO

fm.register(board_info.BUTTON_A, fm.fpioa.GPIO1)
but_a=GPIO(GPIO.GPIO1, GPIO.IN, GPIO.PULL_UP) #PULL_UP is required here!

fm.register(35, fm.fpioa.GPIOHS1, force=True)
pinout35 = GPIO(GPIO.GPIOHS1, GPIO.OUT)
pinout35.value(0)

lcd.init()
lcd.rotation(2)

try:
    from pmu import axp192
    pmu = axp192()
    pmu.enablePMICSleepMode(True)
except:
    pass

try:
    img = image.Image("/sd/startup.jpg")
    lcd.display(img)
except:
    lcd.draw_string(lcd.width()//2-100,lcd.height()//2-4, "Error: Cannot find start.jpg", lcd.WHITE, lcd.RED)

task = kpu.load("/sd/XXX_mbnet10_quant.kmodel")

labels=["1","2","3"] #You can check the numbers here to real names.

sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_windowing((224, 224))
sensor.run(1)

lcd.clear()

isButtonPressedA = 0
off_flg = 0

while(True):
    try:
        img = sensor.snapshot()
        fmap = kpu.forward(task, img)
        plist=fmap[:]
        pmax=max(plist)
        max_index=plist.index(pmax)
        if pmax > 0.95:
            # lcd.draw_string(40, 60, "Accu:%.2f Type:%s"%(pmax, labels[max_index].strip()))
            print(labels[max_index])
            if max_index == 0:
                pinout35.value(1)
            else:
                pinout35.value(0)
        if but_a.value() == 0 and isButtonPressedA == 0:
            isButtonPressedA = 1
            if off_flg == 0:
                lcd.set_backlight(False)
                lcd.clear()
                off_flg = 1
            else:
                lcd.set_backlight(True)
                lcd.clear()
                off_flg = 0
        if but_a.value() == 1:
            isButtonPressedA = 0
        if  off_flg == 0:
            a = lcd.display(img)
    except:
        pass
a = kpu.deinit(task)

ファーストステップ完成

セカンドステップ

M5StickVにはLCDとバッテリーがついていて便利なのだが、今回やりたいことに対してはこの機能は不要、むしろいらない機能といえる。
今回の目的に対してはUNIT-Vを使うのが最適なので、UNIT-Vで同じものを作ることにする。 2021/4/16現在、取組中。学習はいい結果が出せているが、モデルをUNIT-Vに入れて判別動作をさせると正答率がかなり悪いという問題で苦闘中。
その結果はまた記事にしたいと思います。