ESP32-S3を用いたメガネ装着型カメラモジュール
はじめに
この記事は2024年に開催された電気情報機器学(BDM)の最終課題作成の備忘録です。また、2025年五月祭のEEIC企画で展示されていた同作品の詳細記事でもあります。
やりたかったこと
人の顔が覚えられない! これは、人の顔を見て話せない私の問題でしかないのだが、せっかくなので私の人間的成長以外の手段で解決してみたくなった。
そこで、人の顔を覚える、認識する、という行為を外部に委託すればいいのではないか、というモチベーションで作成された。
最終的には、ESP32-S3を用いて、メガネに装着できるカメラモジュールを作成し、PC側で顔認証を行い、LINE Notifyで通知する、というものを作成した。
使用したもの
XIAO ESP32-S3 Sense
装着するにあたって小型軽量のマイコンを使用する必要があったため、ESP32を選択した。XIAO ESP32-S3 SenseとESP32-CAMなどが標準でカメラ(OV2640CMOS等)を搭載するポートがついてるため、候補にあがった。スペック比較は海外の方がYouTubeに上げてくださっていたので、そちらを参照した。
結論として、どちらもESP32-S3を搭載しているが、XIAO ESP32-S3 Sence側には8MBのフラッシュが搭載されているのに対し、ESP32-CAM側は4MBのフラッシュしか搭載されていないため、XIAO ESP32-S3 Senceを選択した。ただし、XIAO ESP32-S3 Senceは小型である分発熱がかなり大きく、また付属のアンテナ以外で技適を取得しているか不明なため、その点を考慮しながら使用する必要があった。
MacBook Air(M1)
顔認証をして、それをLINE Notifyで通知したかったために、PC側でPythonを使用して顔認証を行うこととした。 顔認証は face_recognition
というライブラリを使用した。これを使用することで、顔認証のための学習や、顔認識を行うことができる。
3Dプリンタ
顔に装着するためにESP32をメガネに装着しようとなり、3Dプリンタでメガネにひっかける籠のようなものを作成した。3Dプリンタは、学内の3Dプリンタを使用した。
その他
- Gemini
最近無料になっていたのでコードのリファクタリングで使ってみた(BDMはデスマーチだったためかなり汚いコードになっていた)。
コード
firmware/
以下にESP32のコードが、その他はPython用のコードが書かれている。 以下それぞれのコードの軽い説明を行う。
ESP32側のコード
基本的にはESP32のお試しライブラリのなかにあるCameraWebServerを参考にする。
変更点としては、
- XIAO ESP32 以外のボード用のコードを削除(Face_detection.ino, camera_pins.h)
- HTTPではなくWebSocketをもちいることで双方向での通信が容易になった(app_https.cpp)
などがある。
WebSocketの関数などをArudiono IDEのlibraryでAsync WebServerから持ってくることができたため、ESP32側のコードはあまり難しくなかった。
以下起動後のESP32側のコードの流れを示す。
- ESP32の起動
起動するとまずWiFiに接続。 WiFiのSSIDとパスはハードコーディングされている。 - WiFiに接続するとHTTPサーバーを起動する。この時点ではカメラは起動していないため。サーバーのタスクのみが起動している。
- WebSocketの接続があり、かつWebSocket越しから"start_stream"のメッセージが来ると、カメラを初期化し、ストリーミングを開始する。
- ストリーミング中は、WebSocket越しに"stop_stream"のメッセージが来るまで、カメラから取得した画像をJPEG形式でPC側に送信する。
- "stop_stream"のメッセージが来ると、カメラを停止し、psramを解放する。Websocketの接続が切れた場合も同様に処理する。
Python側のコード
GUI(Tkinter)、Websocket、Face_recognitionの3つを動かす。Tkinterはメインスレッドで動かし、WebSocketとFace_recognitionを別スレッドで動かすようにした。これにより、Tkinterのメインスレッドがブロックされることなく、リアルタイムで顔認識を行うことができた。
以下、Python側のコードの流れを示す。
- Appの起動
main()
起動するとまず環境変数の読み込みを行い、その後Tkinterのrootを作成し、その後Appクラスを作成する。 - Appクラスの初期化
__init__
Appクラスの初期化では、Tkinterのウィンドウを作成し、既存の顔データの読み込み。logger
のセットアップ、WebSocketインスタンスの作成などを行なう。 - キューの作成
WebSocketとTkinterでは別スレッドで動作するため、 データ処理を杜撰におこなうとSIGSEGVが発生する。これを抑制するため、logデータやfpsデータはキューを使用してメインスレッドに渡し、画像データはthreading.Lock
に格納して安全にデータをやり取りするようにした。 - WebSocketの接続
start_process()
GUI上で接続ボタンを押すと、WebSocketの接続を行う。接続が成功すると、ESP32側に"start_stream"のメッセージを送信し、カメラのストリーミングを開始する。 - ストリーミングの開始
_on_websocket_message
ストリーミングが開始されると、ESP32側からJPEG形式の画像データが送信されてくる。これを受信すると同スレッド上で顔認証を開始する。また、ESP32から文章形式のデータが送信されてくる場合もあるため、これを受信した場合はloggerに出力する。 - 顔認証
face_recognition
受信したJPEG形式の画像データをopenCVでnumpy配列に変換し、グレースケールに変換するなどの処理を施した後、face_recognition
ライブラリを使用して顔認証を行う。認識した顔の位置や名前を取得し、受信した画像の上に短形を描画してself.latest_frame
に保存する。また認識した顔の名前をLINE Notifyで通知する。 - GUIの更新
update_image()
self.latest_frame
に格納された画像データをメインスレッドでTkinterのウィンドウに表示する。これにより、リアルタイムで顔認識の結果を確認することができる。
実装が大変だった点
ESP32側
-
Arduino IDE の使いづらさ:
PSRAMを起動するためにArduino IDEのオプションを選択しないといけないのがわかりづらい! また最近になってAsyncWebServerのライブラリが破壊的変更がされていたため、ESP32のコードが動かなくなった。
Python側
-
Tkinterによる謎のSIGSEGV:
Tkinterはスレッドセーフではないため、メインスレッドで動かす必要がある。これにより、WebSocketの受信処理とTkinterのUI更新処理を別スレッドで行う必要があり、データのやり取りにキューを使用する必要があった。これが開発当初知らなかったため謎のSIGSEGVで悩まされた。
課題点
ESP32側
- 発熱の問題
5min程度の稼動でもESP32が熱くなりすぎてWiFIが切断されることがあった。また、httpサーバーの起動はそのままに、カメラを休止させようとしたがそれに必要なピン(PWDN端子)がGPIOに割り当てられていなかったため、カメラを休止させることができなかった。
詳しくは以下参照
- アンテナの問題
Xiao ESP32 Senceは技適の都合で付属のアンテナ以外で使用することができないため、ESP32-CAMを使用した場合と比べて通信距離が短くなってしまった。これにより、PC側からの通信が途切れやすくなってしまった。
現実的でない解決方法
電波法に基づくと、電波暗室内においては技適のないアンテナを使用しても問題ない。実際、筆者が電波暗室においてアンテナに鉄でできたクリップをつけて使用したところ、通信が大幅に改善された。しかし、電波暗室内で相手の顔がわからなくなるケースがあるとは思えないため、実用的ではない。
Python側
-
デザイン:
Tkinterダサい。 -
顔認証の登録:
現状はファイル直下に画像を直置きして、ファイル名で名前を指定する形になっているが、これだと画像の管理が大変である。ちゃんとDBなどで管理して、アクセスも高速にしたい。また、現状だと画像の新規登録時はUnknownで登録し、後で名前を変更する必要があるが、これも面倒である。新規登録時に名前を指定できるようにしたい。 -
通知の問題:
LINE Notifyは便利かつ簡単でよかったのだが、サ終した。代替案として、SlackやDiscordのWebhookなどがあるので、いつか実装したい。折角ならそこへ返信することで新規画像の名前登録などできればいいなと思っている。
まとめ
ESP32-S3を用いたメガネ装着型カメラモジュールと、PythonによるPC側での顔認証システムを構築しました。人の顔を覚えるという個人的な課題を技術で解決するというモチベーションから始まり、小型軽量なデバイス選定、WebSocketによるリアルタイム通信、そしてface_recognition
ライブラリを用いた顔認証の実装に挑戦しました。
上記のような問題はあれど、少なくとも動くものが自分で実装できたのはやっぱり嬉しいですね。実際の発表の場では少々トラブルに見舞われましたが、最終的には無事に発表でき、成績も優をいただきました。これからもこういう工作をやりたいですね。
Discussion