はやし雑記

はやしです

【AFTAC2020】google/mediapipeのデータをサクッと取ってきて使うやつを作った

この記事はの附属天王寺OBOG Advent Calendarの21日目記事です。

English version

dev.to

adventar.org

去年は眺めてたら埋まっちゃってたので、今年は書きます。 てか全然埋まってへんやんけ!どないなっとんじゃワレ!

私は中61期高55期生で、母校(大阪教育大学附属高等学校天王寺校舎)を卒業してもう8年くらいになります。

壮大な記事を書こうと思っていたのですが、ここ最近結構忙しくてあんまり何もできなかったので、 3ヶ月前くらいに作ったものの紹介でお茶を濁します。

今回紹介するのは、mediapipeのデータをサクッと取ってきて使うためのアプリケーションqoinと愉快な仲間達です。

github.com

github.com

github.com

github.com

これらのアプリケーションは技育展というイベントのために作りました。

What is mediapipe?

そもそも、mediapipeとは何かというと、 機械学習技術を使ってカメラの映像からリアルタイムで手や顔、物体の検出を行うアプリケーションです。

Tensorflowが中で頑張っています。

Googleが開発し、オープンソースです。主にC++で記述されています。 github.com

しかし、mediapipeをビルドして実行しても、検出データを取り出すことはできません。 コードを書き換えて何らかの方法で出力させる必要があります。

mediapipeを使って色々遊んでみたかったのですが、 コードをforkして書き換えるのとupstreamとconflictしてしまいそうですし、 あまり良手とは思えません。

そこで、mediapipeの検出部のコードをincludeして、別のデータの出力方法を実装したアプリケーションを作ることにしました。 それがqoinです。

mediapipeについてはDeveloper IOでいくつか紹介記事が上がっています。

dev.classmethod.jp

qoin

qoinはWebカメラで読み込んだ映像からリアルタイムで手や顔を検出し、その情報をgRPCで外部に送るアプリケーションです。

f:id:hayashikunsan:20201221002036p:plain

なぜ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種類を実装しています。 他の検出方法の実装も簡単にできますがめんどくさいのでやってません。

f:id:hayashikunsan:20201221163321p:plain f:id:hayashikunsan:20201221163304p:plain

Bazel

ビルドツールには、これまたGoogleが開発し、mediapipeで使われているBazelというビルドツールを使いました。

bazel.build

BazelはC++だけではなく、JavaやPythonなどの多くの言語に対応しており、.protoからC++のコードに変換する処理なども記述できます。

ちょっと古いですが、こちらの記事がわかりやすいです。

knowledge.sakura.ad.jp

まぁ正直なところ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_protobufrules_ccの両方で定義されていて、 loadする方を間違えるとうまくビルドできなかったりするのでハマりました。

qoinのBUILDの中では、@mediapipeなどとすることで、依存関係先のソースを呼び出せます。

愉快な仲間達

poin

github.com

手の動きに合わせて画面上のポインタを動かすアプリケーションです。 Rustで記述しています。

このとき、qoinはサーバーとして、PullStreamを起動させます。

f:id:hayashikunsan:20201221162752g:plain

GUIフレームワークにはconrodを使いました。

Rustには公式のgRPC実装はありませんが、tonicなどの実装があります。

build時にqoinとmediapipeの.protoを探しています。

poin/build.rs at master · hayashikun/poin · GitHub

pyoin

github.com

qoinから送られてきた検出データから、手の形(janken, hand_number)や、頭の向き(head_direction)を判別して表示するプログラムです。 Pythonで記述しています。

このとき、qoinはサーバーとして、PullStreamを起動させます。

f:id:hayashikunsan:20201221163633g:plain f:id:hayashikunsan:20201221163717g:plain

先ほどと同様に、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を別のマシンで動かすことができます。

矢印は検出データの流れる向きです。

f:id:hayashikunsan:20201221164423p:plain

最初に説明した通り、qoinはサーバーとしてもクライアントのどちらでも動かすことができます。

qoverは、qoinからPushStreamでqoverに検出データが送られ、 qoverからpoinやpyoinにPullStremで検出データが送られます。

つまり、カメラを起動し検出を行うPCに対してはgRPCサーバーとして起動し、 別のマシンのpoinやpyoinに対してはクライアントとして動作します。

これはWebページにqoverから受信した手のデータを表示した例です。

f:id:hayashikunsan:20201221170939g:plain

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を使っていたけどめちゃ重かったから多分そのせい。

来年のアドベントカレンダーは壮大な話をしたい。 (毎年言ってる)(来年は多分今年より忙しいから多分無理)

メリークリスマス!