この記事はの附属天王寺OBOG Advent Calendarの21日目記事です。
English version
去年は眺めてたら埋まっちゃってたので、今年は書きます。 てか全然埋まってへんやんけ!どないなっとんじゃワレ!
私は中61期高55期生で、母校(大阪教育大学附属高等学校天王寺校舎)を卒業してもう8年くらいになります。
壮大な記事を書こうと思っていたのですが、ここ最近結構忙しくてあんまり何もできなかったので、 3ヶ月前くらいに作ったものの紹介でお茶を濁します。
今回紹介するのは、mediapipeのデータをサクッと取ってきて使うためのアプリケーションqoinと愉快な仲間達です。
これらのアプリケーションは技育展というイベントのために作りました。
What is mediapipe?
そもそも、mediapipeとは何かというと、 機械学習技術を使ってカメラの映像からリアルタイムで手や顔、物体の検出を行うアプリケーションです。
Tensorflowが中で頑張っています。
Googleが開発し、オープンソースです。主にC++で記述されています。 github.com
しかし、mediapipeをビルドして実行しても、検出データを取り出すことはできません。 コードを書き換えて何らかの方法で出力させる必要があります。
mediapipeを使って色々遊んでみたかったのですが、 コードをforkして書き換えるのとupstreamとconflictしてしまいそうですし、 あまり良手とは思えません。
そこで、mediapipeの検出部のコードをincludeして、別のデータの出力方法を実装したアプリケーションを作ることにしました。 それがqoinです。
mediapipeについてはDeveloper IOでいくつか紹介記事が上がっています。
qoin
qoinはWebカメラで読み込んだ映像からリアルタイムで手や顔を検出し、その情報をgRPCで外部に送るアプリケーションです。
なぜgRPCなのかというと、mediapipeは内部でProtocol Buffersでデータのやりとりが行われているからです。
qoinはgRPCサーバーを起動して、接続してきたクライアントにProtocol Buffersを返すか、 もしくは外部のgRPCサーバーにProtocol Buffersを送るようになっています。
前者の、qoinから検出データをレスポンスとして返すサービスをPullStreamと呼び、 後者の、検出したデータをリクエストとして送るサービスをPushStreamと呼びます。
qoin/qoin/proto at master · hayashikun/qoin · GitHub
qoinを利用したいアプリケーションは、この.protoに従ってgRPCクライアントもしくはサーバーを実装すれば、 C++を書いてmediapipeのコードをいじらなくても、簡単にmediapipeの検出データを使うことができます。
今のところ、face_meshとhand_trackingの2種類を実装しています。 他の検出方法の実装も簡単にできますがめんどくさいのでやってません。
Bazel
ビルドツールには、これまたGoogleが開発し、mediapipeで使われているBazelというビルドツールを使いました。
BazelはC++だけではなく、JavaやPythonなどの多くの言語に対応しており、.protoからC++のコードに変換する処理なども記述できます。
ちょっと古いですが、こちらの記事がわかりやすいです。
まぁ正直なところIDEが対応していなかったりで使いにくいです。
依存関係はWORKSPACE
に記述します。
この依存関係のほとんどはmediapipeのもので、 mediapipeを依存関係に書いてあとはmediapipeのWORKSPACEからよしなに引っ張って来てくれればいいのにと思いますが、それはできませんでした。
qoin/WORKSPACE at master · hayashikun/qoin · GitHub
ビルドルールはBUILD
に記述します。
qoin/BUILD at master · hayashikun/qoin · GitHub
qoin/BUILD at master · hayashikun/qoin · GitHub
cc_proto_library
などはcom_google_protobuf
とrules_cc
の両方で定義されていて、
loadする方を間違えるとうまくビルドできなかったりするのでハマりました。
qoinのBUILD
の中では、@mediapipe
などとすることで、依存関係先のソースを呼び出せます。
愉快な仲間達
poin
手の動きに合わせて画面上のポインタを動かすアプリケーションです。 Rustで記述しています。
このとき、qoinはサーバーとして、PullStreamを起動させます。
GUIフレームワークにはconrodを使いました。
Rustには公式のgRPC実装はありませんが、tonicなどの実装があります。
build時にqoinとmediapipeの.protoを探しています。
poin/build.rs at master · hayashikun/poin · GitHub
pyoin
qoinから送られてきた検出データから、手の形(janken, hand_number)や、頭の向き(head_direction)を判別して表示するプログラムです。 Pythonで記述しています。
このとき、qoinはサーバーとして、PullStreamを起動させます。
先ほどと同様に、codegen.py
で.protoを探してきてコード生成しています。
pyoin/codegen.py at master · hayashikun/pyoin · GitHub
jankenやhand_numberは簡単なNNモデルで判別しています。
pyoin/janken.ipynb at master · hayashikun/pyoin · GitHub
教師データは自分の手をこちらのsamplerで保存するだけなのでとても簡単です。
pyoin/hand_tracking.py at master · hayashikun/pyoin · GitHub
qover
以上の2つは同じPCの中で動かす事を想定していました。 qoverはネットワーク越しに検出データを渡すためのものです。
qoverを用いることで、qoinと、poinやpyoinを別のマシンで動かすことができます。
矢印は検出データの流れる向きです。
最初に説明した通り、qoinはサーバーとしてもクライアントのどちらでも動かすことができます。
qoverは、qoinからPushStreamでqoverに検出データが送られ、 qoverからpoinやpyoinにPullStremで検出データが送られます。
つまり、カメラを起動し検出を行うPCに対してはgRPCサーバーとして起動し、 別のマシンのpoinやpyoinに対してはクライアントとして動作します。
これはWebページにqoverから受信した手のデータを表示した例です。
PCで手の検出をし、ECS上にデプロイしたqoverを経由して、 スマホで開いたWebページに検出データが送られ、表示されています。
受信と表示はVueで以下のように書けます。
geekten20/HandTransfer.vue at master · hayashikun/geekten20 · GitHub
ブラウザで表示させる時は、CORSがあるためenvoyなどを通す必要があります。
qover/envoy.yaml at master · hayashikun/qover · GitHub
おわり
mediepipeで遊びたくてqoinを作り、発表のために愉快な仲間達も書いたけど、 飽きちゃって中途半端なところでやめちゃった。
またそのうち手を付けるかも?
師走は思ってたより忙しくて記事も雑になっちゃった。ぴえん🥺
Rustは楽しかった。 イベントハンドリング周りはちょっとつらかったけど。
久しぶりにC++を書いた書いたけどやっぱりちょっとつらかった。 私はCLionで書きたかったけど、CLionにはBazelのプラグインがなかったのでVScodeを使っていたけどめちゃ重かったから多分そのせい。
来年のアドベントカレンダーは壮大な話をしたい。 (毎年言ってる)(来年は多分今年より忙しいから多分無理)
メリークリスマス!