TensorFlow.js の Hand Pose Detection を使って「AR妖怪けむり」を作ってみる
数年前にちょっと話題になりましたが 「妖怪けむり」、製造終了してしまったのでもう売っていないそうですね。[1] あまり遊んだ記憶はないですが、もう無くなってしまったと聞くと少し寂しいものがあります。
TensorFlow.js の Hand Pose Detection を使ったら、Web ブラウザ上で動作する AR妖怪けむり 的なものが作れるんじゃなかろうか...
とおもったのでちょっと試してみました
完成したものはこちら
そもそも「妖怪けむり」とは...?
妖怪けむり、馴染みがない方も居られるかとおもうので一応。
-
ニコニコ大百科 - ようかいけむりとは
愛知県の堀商店が販売、小林商店が製造。
主な販路は駄菓子屋で、価格は1枚20円。
商品名は「ようかいけむり」「おばけけむり」「カードけむり」など。いずれも、力強い筆致で描かれたおどろおどろしい妖怪の絵が特徴。
紙製のカードに、パラフィン紙で保護された「くすり」が塗られている。これを指に取り、親指と人差し指をつけたり離したりすることで、煙のような白い物体が空中に舞う。 - YouTube 検索 - ようかいけむり
- Google 画像検索 - ようかいけむり
指先から煙[2]を発生させて楽しむ、素朴なおもちゃのことですね。
駄菓子屋さんで手に入るシャボン玉とかポリバルーンの仲間?みたいな。
TensorFlow.js
- 概要 https://www.tensorflow.org/js?hl=ja
- 機械学習用 JavaScript ライブラリ
- Hand Pose Detection https://github.com/tensorflow/tfjs-models/tree/master/hand-pose-detection
-
Live Demo https://storage.googleapis.com/tfjs-models/demos/hand-pose-detection/index.html?model=mediapipe_hands
- Web ブラウザ上で Webカメラの映像から リアルタイム に手指関節の座標が検出できる
- Web ブラウザ上で Webカメラの映像から リアルタイム に手指関節の座標が検出できる
-
Live Demo https://storage.googleapis.com/tfjs-models/demos/hand-pose-detection/index.html?model=mediapipe_hands
Hand Pose Detection で検出できる情報
Readme からどんな情報が取れるのか確認。
関節の番号
画像引用元: https://github.com/tensorflow/tfjs-models/tree/master/hand-pose-detection#keypoint-diagram
CMC ... carpometacarpal (手根中手根)
MCP ... metacarpophalangeal (中手指節)
IP ... interphalangeal (指節間)
PIP ... proximal interphalangeal (近位指節間)
DIP ... distal interphalangeal (遠位指節間)
JSON の構造
[
{
// 信頼度 (0 ~ 1)
score: 0.8,
// 右手 or 左手
handedness: 'Right',
// 2Dの座標
keypoints: [
{x: 105, y: 107, name: "wrist"},
{x: 108, y: 160, name: "pinky_finger_tip"},
...
],
// 3Dの座標
keypoints3D: [
{x: 0.00388, y: -0.0205, z: 0.0217, name: "wrist"},
{x: -0.025138, y: -0.0255, z: -0.0051, name: "pinky_finger_tip"},
...
]
}
]
Webカメラからの映像を解析して、毎フレームこちらの JSON が返ってくる感じ。
例えば keypoints[4]
が親指の先端、keypoints[8]
が人差し指の先端。
ローカル開発環境構築
公式が用意している Live Demo がとてもよく出来ているので、これを改造して目的を達成したいとおもいます。
まずは Readme に従って、Live Demo がローカル開発環境で動作するように。
# ソースコード取得
git clone git@github.com:tensorflow/tfjs-models.git
# live demo のフォルダへ移動
cd tfjs-models/hand-pose-detection/demos/live_video
# 古いキャッシュなどがあれば削除
rm -rf .cache dist node_modules
# 依存する package をビルド
yarn build-dep
# package のインストール
yarn
# 起動 (停止は Ctrl + C)
yarn watch
# ブラウザで下記URLにアクセス
http://localhost:1234/?model=mediapipe_hands
Live Demo のソースコード観察
hand-pose-detection/demos/live_video フォルダ以下が Live Demo のソースコード。こちらを追って、どこに手を入れたら良いか探ります。
src/camera.js ... Webカメラからの映像を解析して結果(2D/3Dの骨格など)を描画する class
src/option_panel.js ... 画面右上、動作設定用GUIの作成
src/shared/params.js ... GUIの選択項目など、設定値の定数
src/index.js ... エントリポイント
おおよそ上記のような構成となっているので camera.js
の処理、Canvas に 2D で手の関節を描画しているところを改変するのがよさそう。(drawResults, drawResult, drawKeypoints などが描画処理)
また index.html やソースコード上の import を確認すると
右上に表示されている動作設定の GUI 部分は dat.gui を、
左上のFPS表示は Stats.js をCDNから持ってきて使用している模様。
今回は GUI の設定項目も追加・変更するので dat.gui の API ドキュメントを確認しておく。
指先がくっついた、離れたを判定する
妖怪けむり は人差し指と親指の腹をくっつけたり離したりを繰り返すと、その間から煙状の物質が出る遊びなので、その指の動きを Hand Pose Detection が検出した指先の座標から「くっついた」「離れた」が行われたことを判定する必要が。
今回のケースでは
- 親指の先端 と 人差し指の先端 の座標がある一定以上に接近したら指が「くっついた」👌
- その後、座標がある一定の距離を離れたら「離れた」✋
と判定するだけでよさそう。
今回はざっくりと 15px 以下に接近したら指がくっついた、その後 80px 以上離れたら指が離れたとしておきます。[3]
また、煙を発生させるタイミングは 指が離れた時 に、煙を発生させる位置は親指と人差し指の座標の 中間点 にすればそれらしくできそう。
JavaScript のコードで書くとおおよそ以下のような感じ。
// 抜粋
// 親指の先端の座標
const thumbTip = keyPoints[4];
// 人差し指の先端の座標
const indexTip = keyPoints[8];
// 2点間の距離を算出
const distance = Math.sqrt(Math.pow(thumbTip.x - indexTip.x, 2) +
Math.pow(thumbTip.y - indexTip.y, 2));
// 親指と人差し指がくっ付いた状態かどうか (※ stillPinched は前フレームでくっついていたかどうか)
const pinched = stillPinched ? distane < 80 : distance <= 15
// 2点間の中心座標を算出。この座標に煙を発生させる
const midwayPoint = {
x: (thumbTip.x + indexTip.x) / 2,
y: (thumbTip.y + indexTip.y) / 2,
}
Smoke.js で指定した座標の位置に煙を発生させる
煙を発生させる位置が決まったので、あとはそこに煙を描画するだけ。
煙は Canvas 上に関節の位置を描画する処理に倣って Particle で描画すればいいのですが、自前で煙の Perticle をゼロから書くのはつらいものがあり😫
探してみたところ、Smoke.js という Canvas 上の任意の座標に煙のエフェクトを発生させる という今回のケースにぴったりの Package があったので、こちらを使って煙を描画します。
改造後のソースコード
改造後のソースコード全体は こちら を参照。
改造のポイントとしては以下のあたり
つまむ、はなすの検出部分
骨格を描画する Canvas の上に、煙を描画する Canvas を重ねる[4]
GUI に Smoke.js の設定項目を追加
デフォルトのモデルを mediapipe_hands
に設定。URLのパラメータが指定されていないくてもモデル未選択のエラーを出さない
2D/3D の骨格描画はデフォルトでOFFにしておく
静的サイトとしてビルドする
yarn build
を実行すれば、これまで作ってきたものを静的サイトとしてビルドすることができます。
Web サイトとして公開する際は、dist
フォルダに生成された index.html と src.xxxxxxxx.js[5] をアップロードするだけ。
完成したものを GitHub Pages に公開
今回作成したものは こちら に公開しました。
判定が甘いので時折おかしな動きをすることもありますが、
画面右上の GUI で煙の色(smokeColor)を変えたり、発生量(particles)を増やしたりして遊んでいただければとおもいます。
おわりに
TensorFlow.js と Smoke.js を使うことで、容易にそれっぽいものを作ることができました。
これでいつでも指先から煙を無限に発生させられます。
実際の「ようかいけむり」は霧状の煙ではなく お線香 のような細い煙がゆっくり発生するので、
Smoke.js の描画処理をカスタマイズしたり自前で Particle を書いたらもっとリアルに近づけられるかもしれないですね。
ほかにも手がカメラの近くにある時は煙を大きく、遠くにあるは煙を小さく描画するなどしたらよさそうです[6]
Hand Pose Detection のリアルタイム検出にはまだまだ面白いことができそうな可能性を感じるので、機会があればいろいろ試していきたいところです。
Discussion