じゃがいもログ

脊髄性筋萎縮症(SMA)1型の息子と生活中

Arduino UNO Q で ML Powered タッチセンサースイッチ(実装編)

前回の準備編でArduino UNO Qを利用する準備が整ったので、AI Poweredタッチスイッチの開発に取り掛かります。ここからは、機械学習のトレーニング用のデータ計測、機械学習モデル構築とトレーニング、トレーニング済みモデルを使ったタッチスイッチの実装、という順番で進めていきます。

回路はこんな感じです。以前作ったタッチセンサースイッチとは、使っているピンが違うだけで、基本的には同じです。UNO Qの絵がなかったので、UNOで代用しています。

機械学習用トレーニングデータの計測

トレーニングデータの計測は、Arduino IDEでシリアルモニターに測定値を記録すれば済む話ではありますが、せっかくなのでUNO Qっぽく、マイコン側で計測した値をDebian上で動くPythonプログラムに渡してファイルに書き込むようにします。

Arduino App Lab上で新しいAppを作り、Pythonプログラムとスケッチをそれぞれ準備していきます。
マイコン用のスケッチはこちら。
ポイントは48行目で、Bridge.call("log_sensor_value", val) というところで、Python側のlog_sensor_value関数を呼び出し、計測結果の値valを引数として渡します。

Python側のコードはこんな感じ。log_sensor_value関数を定義し、Bridge.provide()で、マイコン側に公開します。最後にApp.run()を呼び出します。

準備ができたら、Arduino App Lab上でRunボタンを押してUNO Qで作ったAppを実行します。起動後、タッチセンサーのタッチを触ったり離したりして、計測データをため込んでいきます。計測データは、作ったAppのフォルダ直下にsensor_log_<日時>.csvとして保存されます。このファイルを開き、タッチオンとみなしたい数値には1を、オフとみなしたい数値には0を2列目として書き込んでいきます。編集が終わったら、train_data.csvとして保存します。

raw_data,label
2,0
-5,0
4,0
2,0
3,0
3,0
....
8,0
2578,1
2789,1
2804,1
2752,1
2738,1
2685,1
2688,1
2782,1
....

これで学習用データの準備ができました。

機械学習モデル構築とトレーニング

機械学習は正直全くの素人なので、この部分は生成AIの助けを借りました。今回はMS Copilotを使って、プロンプトでやりたいことを伝えて出てきたコードを少し手直して、つぎのようなPythonプログラムを用意しました、このプログラムでトレーニングデータを使ってモデルを学習し、学習結果のパラメータをtouch_sensing_model.pthというファイル名で保存します。

モデルのトレーニングは、Arduino UNO Q上でやらなくても、パソコンとかでやってもよいと思います。私はWindowsのWSL環境でやりました。

トレーニング済みモデルを使ったタッチスイッチの実装

MLモデルのトレーニングが完了したら、Arduino App Labに戻り、先ほどのAppをタッチセンサースイッチ用に作り直していきます。
スケッチとPythonプログラムはそれぞれ次のようになります。

スケッチ側の80行目で、Python側で用意しているトレーニング済みモデルを使ったオンオフ判断の関数を呼び出し、結果をtouch_state変数に格納しています。また、Bridge.call().result()の戻り値は、実際にリモート関数を呼び出せたかどうかのbool値が返ってくるみたいなので、それがtrueのときだけ、touch_stateの値を使ってオンオフ最終判断するようにします。
そういえば、Python側はnumpyやtorchなどのライブラリを追加したいときは、pythonフォルダ上にrequirements.txtというファイルを作り、そこに追加したいライブラリを書いておくと、Dockerコンテナが立ち上がった時にpipが自動でインストールしてくれます。今回の場合は、以下の2行でいいはず。

numpy
torch

コードの準備ができたら、Arduino App Lab上でRunボタンを押して実行です。この動画のような感じで、AIでオンオフ検知したタッチセンサーが動くことでしょう。
youtu.be

実際に運用するときは、Arduino UNO Qを起動したときに自動でAppを起動するよう、Run at startupをチェックしておけば便利だと思います。

考察

今回、AIタッチセンサーは動くことは動きましたが、オンオフ検知のレスポンスが、閾値ベースで判断するよりも格段に遅くなりました。また、実際の検知結果を見ると、それなりに誤動作もしているようです。もともとはAIモデル側にオンオフを完全に任せようかと思っていましたが、誤検知やチャタリングの扱いが怪しかったので、けっきょく閾値ベースのセンサーと同じように、オンオフがすぐに切り替わるケースは無視するなどの追加のロジックを入れる必要がありました。この辺りはAIモデルの構築方法とトレーニング方法を見直すと改善するのかもしれませんが、いかんせん機械学習やニューラルネットワークは素人なので手の出しようがありませんでした。このあたり、基礎から学習して出直してみようと思います。
あと、Arduino UNO Q単体の課題としては、起動の遅さが気になりました。電源を入れてタッチスイッチが使えるようになるまでに、1分30秒ほどかかります。まあ、Linuxを起動して、スケッチをマイコンにロードして、Python側はDockerでコンテナ立ち上げて、ということを毎回やっているようなので、そりゃおそくなるよな、という感じです。

Arduino UNO Q で ML Powered タッチセンサースイッチ(準備編)

ArduinoからLinux(Debian)が動くボードArduino UNO Qが出たと知り、機械学習を使ったタッチセンサースイッチが作れるのではと思い、さっそく実機を入手していろいろ試してみました。

ボードの入手と技適特例申請

Arduino UNO Qはまだ日本では購入できないようだったので、DigiKeyを使って米国から個人輸入してみました。技適は未認証なので、到着後にさっそく総務省のサイトで利用者登録して、特例申請をしておきました。これで安心してArduino UNO Qが使えます。

特例申請は180日間限定なので、後々も利用できるようにWiFi/Bluetoothともに早々につぶしちゃいます。ただしWiFiについては、Pythonライブラリのダウンロード等でインターネット接続が必要になるので、手持ちのBuffaloのWiFiドングルを挿して使えるようにします。

Bluetoothのservice停止

Bluetoothは全く使う予定がないので、systemctlでserviceを停止・無効化します。

$ sudo systemctl stop bluetooth
$ sudo systemctl disable bluetooth

Kernel moduleの無効化

lsmod等でKernel module間の依存関係を見ながら、以下のKernel moduleを無効化することにしました。

moduleの無効化については、こちらのDebianのサイトの情報が参考になりました。
/etc/modprobe.d/にblacklist.confというファイルを作り、そこに停止したいmoduleごとに以下のような行を追加します。

blacklist ath

ファイルを保存して、以下のコマンドを実行します。

$ sudo depmod -ae
$ sudo update-initramfs -u

これだけだと、kernelによるmoduleのautloadを防げないとのことなので、同じディレクトリに <module名>.conf というファイルを作り、以下の内容で保存しておきます。

install <module名> /bin/true

OSを再起動して、lsmod等で該当のmouduleがロードされていないことを確認します。

WiFiドングルの有効化

続いて、手持ちのBuffalo製WiFiドングル(WLI-UC-GNM2)の有効化をします。これがけっこう大変でした。USB Type-Cハブ経由で挿しただけでは、ifconfigで該当のインターフェースが出てこないばかりか、lsmodしても該当のUSBデバイスが出てきません。
WLI-UC-GNM2のチップはRalink RT3070ということがわかり、まずはこちらを参考に、firmware-misc-nonfreeパッケージをインストールすることにしました。

$ sudo apt install firmware-misc-nonfree

これで、lsusbするとWiFiドングルが出てくるようになりました。でも先ほどのサイトには、これで rt2800usb kernel moduleが自動でロードされるとありますが、ロードされません。案の定、UNO Q用のDebianイメージには必要最低限のkernel moduleしか含まれておらず、rt2800usbを自分でビルドしないといけないようです。
こちらこちらのサイトを参考にして、Kernelをコンパイルして、rt2800usb.koを作ります。UNO Q単体だとストレージの容量が全然足りないので、USBストレージが必要になると思います。私はHDDをmountしてそこでgit cloneしました。

$ cd /media/usb/tmp/
$ git clone --depth=1 --branch qcom-v6.16.7-unoq https://github.com/arduino/linux-qcom 
$ sudo apt install flex bison pahole jfsutils reiserfsprogs xfsprogs squashfs-tools btrfs-progs pcmciautils quota nfs-common libssl-dev make libncurses-dev
$ cd linux-qcom
$ make menuconfig

make menuconfigでは、Device Drivers -> Network Device Support -> Wireless LAN -> Ralink driver support --> Ralink rt27xx/rt28xx/rt30xx (USB) supportを [M] にして保存・終了します。
続いてKernelのbuildに進みます。

$ make

Buildが完了するのに数時間かかるので気長に待ちます。完了したら、drivers/net/wireless/ralink/rt2x00/配下に拡張子koのファイルができていることを確認します。rt2800usb.koも存在しているはず。それらを/lib/modules/6.16.7-g0dd6551ae96b/配下にコピーして、OSに認識させます。

$ sudo mkdir -p /lib/modules/6.16.7-g0dd6551ae96b/kernel/drivers/net/wireless/ralink/rt2x00
$ sudo cp /media/usb/tmp/linux-qcom/drivers/net/wireless/ralink/rt2x00/*.ko /lib/modules/6.16.7-g0dd6551ae96b/kernel/drivers/net/wireless/ralink/rt2x00/
$ sudo depmod -a

念のためOSを再起動して、WiFiドングルを挿したら、無事に認識されてNetworkManagerを使ってアクセスポイントに接続することができました。

続きはこちら

参考にしたサイト:
wiki.debian.org
wiki.debian.org
forum.arduino.cc
ameblo.jp

タッチセンサースイッチの改修

手作りタッチセンサースイッチは、やはり長く使っているとノイズが乗って誤動作することがあります。特に、センサー端子に触れるか触れないかくらいの位置に指が長時間あったり、連打しているとノイズが乗っているように見えます。

そこでふと思いつき、オフ状態を一定回数検知したら、オフ状態の測定値の平均値をとり、それを使ってベースラインの値を更新するようにしました。実際の測定値をベースラインにフィードバックする感じです。

Arduinoスケッチはこんな感じ。125-136行目が今回追加した部分になります。フィードバックの頻度は、オフ状態検知100回ごとにしました。

今回の変更で、なんかすごく安定するようになった気がします。

ピエゾスイッチを少し改修

ピエゾスイッチの感度がイマイチなので、Arudinoスケッチを見直しました。
これまでは、電圧の変化値を見てスイッチオンを認識していましたが、ベースラインが変動しないことが分かったので、絶対値でスイッチオンを判断するように変更。たぶんスイッチオン認識の安定度が増したはず。

もとになっているピエゾセンサースイッチの作製はこちらの記事に書いてます。
jagaimolog.hatenablog.jp

文字盤練習アプリ Windows用

息子用に作った文字盤練習用のWindowsアプリがだいぶ安定してきました。
まあいないと思いますがもし興味がある方がいらしたら、コメント欄にてご連絡ください。

アプリの特徴をまとめるとざっくり以下のような感じです。

  • ひらがな/カタカナの入力のみで、漢字変換やウィンドウ操作などの機能はありません
  • 1スイッチモードのみで、文字選択のカーソルは自動で動き、行選択・文字選択をスイッチで行います
  • ひらがな/カタカナ、あ行を左配置/右配置、カーソルの移動スピード、入力を受け付けるキーが設定で変えられます
  • 文字選択時と「発音」選択時に、Windowsの合成音声を使って読み上げます。Windowsが提供しているフリーで使える合成音声のみを使用しています。最近のAI音声は選べません。音声はそれなりに不自然な感じ
  • あくまでも練習用なので、本格的なコミュニケーションには、市販の製品等を利用するのをおすすめします。

Xbox Adaptive Controllerのアップデート

Xbox Adaptive Controllerのボタンが、キーボードのキーにマッピングできるようになるそうです。今はまだ Insider Program のユーザーのみが対象で、全ユーザー向けには追って公開になるみたいです。これは楽しみ。
個人的にはAdaptive HubのほうをXboxで使えるようにするのもやってほしい。
news.xbox.com

Xbox Controllerのふりをするスイッチインターフェース

ゲーム関連はXbox Adaptive Controllerのおかげで何不自由していませんが、あの大きさは出先に持っていくには難ありです。検査入院とかで病院でNintendo Switchをやろうと考えた時に、あれを持っていくのは少し気が引けます。そこで、ArudinoにXbox Controllerのふりをしてもらうことにしました。

回路自体は以前作ったこちらと同じで、今回はXInputライブラリを使ってArduinoXbox 360 Controllerになってもらいます。
jagaimolog.hatenablog.jp

今回のスケッチはこんな感じ。カプセル化とかDRY (Don't Repeat Yourself) とか全無視のベタベタコードです。

XInputライブラリを使うときは、あわせてXInputに対応するボードもArduino IDEにインストールする必要があります。私が使っているArduino Microであれば、ArduinoXInput_AVRをインストールします。実はこれが曲者で、一度スケッチをアップロードすると、それ以降そのArduinoXbox 360 Controllerのふりをするので、Arduino IDEから見えなくなってしまいます。対処方法はリンク先にも書いてあるのですが、次回以降スケッチをアップロードするときは、アップロードが始まったらすぐにArduinoのリセットボタンを2度押しします。

MAGIC-NS経由でNintendo Switchに接続して動作確認したところ、問題なく機能しているようでした。
youtu.be