Arduino+ラズパイ3+Azureで生物検出システムを作る
コロナ真っ最中のGW、いかがお過ごしでしょうか。
最近、仕事でエッジ関連の話題が出ているため、積読だったこの本を読んで、生物検出システムを作ってみました。
どんなシステム?
何か生き物が近づいたら、カメラが起動し写真を撮って、「何の生物が近づいたか?」をLINEに通知するシステムです。
こんな通知が届きます。
鳥とか獣でも試したかったですが、テストできそうになかったので諦めました。
こんな時、ペットを飼っていれば…。
アーキテクチャ図
物理的なマシンは灰色、クラウド上は青色で示しています。
ほぼ本に書いてあった通りの構成です。ただ、本では機械学習部分を「自分で教師データをスクレイピングしてきて、アノテーションして、学習させてね!」と書いてあったので、
そこだけはAzure Cognitive Servicesの中のComputer Visionを使っています。
今この本が出たら、絶対同じように既存のAPI使うだろうな。このあたりの技術の進歩はすごいですね。(この本は2017年5月発売)
ちなみに、実際のデバイス類はこのような感じです。
ラズパイ、名刺サイズで普通にOSが動くんですね。すごい。
処理の流れ
①センサーが動くものを検知する
②検知したことをブレッドボードからBluetoothでラズパイに通知する
③通知が来たら写真を撮って、wifi通してAzureに送る
④IoT Hubで一時的に保持して、StreamAnalyticsに送る
⑤StreamAnalyticsで処理(今回は全データ取得するだけ)して、ServiceBusQueueに送る
⑥ServiceBusQueueで一時的に保持して、AzureVMに送る
⑦AzureVM上のpythonで、届いた画像を引数にComputerVisionのAPIを呼び出す
⑧そのレスポンスと画像をLINEに通知する
⑨LINEにメッセージが届く
①ー③の物理的な部分はわかりやすいですが、④以降のAzureサービス部分の役割が少しわかりにくいですね。
具体的には、なぜStreamAnalyticsやServiceBusQueueを挟むのか?IoT Hubから直接AzureVMでもよいのでは?と思いましたが、
以下のブログにこのような記載がありました。(この方の場合は、ServiceBusの後がAzureVMではなくLogicAppsですが)
クラウドでは、この"イベント(プログラムの区切り)"を意識して、各々の処理を得意とするサービスを使うと、楽にサービスを組み立てることができます。
今回のシナリオではこんな感じです。
- IoT Hubがテレメトリデータの収集と保持に集中
- Stream Analytics が時間軸を意識した処理に集中
- Service Bus が発生イベントの保持に集中
- Logic Apps がメール配信処理に集中
これらは要件は1サーバー内に構築することも可能です。しかし、それでは部分的なスケールアウトやサーバー停止に対して脆弱な状態となってしまいます。
そこで、"処理"と"保持"をイベントの切り替えタイミングでうまく利用してそれぞれの作業を得意とするサービスを使います。
こうすることで、例えばメールサーバーの停止に伴うエラーがたくさん出たとしても、少なくともテレメトリデータの保持や時間軸を意識した警告には影響が無い仕組みを簡単に作ることができます。
つまり、1つのサービスでもできるが、一部分の性能UPや障害の対応がよりやりやすいように、「保持」と「処理」を分けているということでした。
機械学習では「前処理」「学習」「推論」を分けて実装して、問題の特定が素早くできるようにしますが、それのストリーミング処理版というところですかね。
大変だったこと
プログラムは書籍についていたので、これをそのまま流せば楽勝…と思っていましたが、ところどころエラーが発生して、その処理に時間がかかりました。
逆に、慣れていないハード面の扱い(基盤に抵抗刺したり、Bluetoothを飛ばしてラズパイで検知したり)のところは書いてある通りやればいけました。
中でも一番時間がかかったのが、保存したカメラの写真をAzureに送るために開こうとすると発生するUnauthorizedAccessException。
これは画像を開いてそれをBase64エンコードする処理ですが、そのReadAllBytesで発生していました。
byte[] bufPhoto = await Task.Run(() => File.ReadAllBytes(photoFile.Path)); string b64Photo = CryptographicBuffer.EncodeToBase64String(bufPhoto.AsBuffer());
しかし、なぜか以下のOpenReadAsyncで開くと正常に動作します。
using (var photoStream = await photoFile.OpenReadAsync()){ // 撮った写真を画面に表示する BitmapImage bitmap = new BitmapImage(); bitmap.SetSource(photoStream); capturedImage.Stretch = Stretch.None; capturedImage.Source = bitmap;
原因は結局分からなかったので、後者のやり方で画像を開いてBase64エンコードする処理をgoogle検索し続けました。
(2つの処理でそれぞれ返り値の型が異なるので、そのままは使えない。このあたりのC#の文法の理解も時間がかかりました)
結果、以下の神Stackoverflowを見つけ、何とか解決。
how to convert an image to base64string in c# - Stack Overflow
ほぼ流用させてもらい、以下のように修正したら通りました。
正直、このDataReaderが何をしているのかさっぱりわかりません。わかる方いたら教えてください。
byte[] fileBytes = null; using (var photoStream = await photoFile.OpenReadAsync()) { fileBytes = new byte[photoStream.Size]; using (var reader = new DataReader(photoStream)) { await reader.LoadAsync((uint)photoStream.Size); reader.ReadBytes(fileBytes); } string b64Photo = Convert.ToBase64String(fileBytes);
気づき
API化は正義
マイコン、Azure領域は、かなりAPI化が進んでいたのでそこまで句ではありませんでした。
例えばマイコンだと、Bluetoothに送るときは
Serial.write(1);
で終わりです。
また、AzureもAI関連のAPIが多いので画像さえ取得できれば学習もさせずにいろいろなことができます。
ラズパイの上での実装は、C#そのまま書くのではなくなにかフレームワークが欲しいところです。
統合開発環境は正義
一般的には、IDE使えば画面もDBもロジックも開発できると思います。
ただ、今回のIoTシステムではArduino IDEとVisualStudio2019とVSCodeの3つを使いました。
マイコン、ラズパイ、Azure上で開発するためにはそうする必要があったからですが、ここも統一できるようにして欲しいところです。
物理的な動作はオモロイ
基本的にソフトウェアの開発をずっとしていたので、「実際のセンサが動く」「カメラで写真が撮られる」のようなこととは無縁でした。
今回実際やってみて、このあたりの物理的なデバイスの動作を見るのがかなり面白かったです。
ブレッドボードに素子ぶっさすのも楽しかったので、IoTシステムは他にも作りたいと思いました。