Tensorflow.jsとPoseNetでパペット人形

今回やること

PoseNetを使用して、上下左右に向きを変えることができるパペット人形を作ります。

用意するもの

  • obniz Board
  • サーボモーター x2
  • ブラウザ・PoseNetが使えるPC / スマートフォン
  • 強力両面テープ
  • パペット人形
  • 棒状のものやL字金具等、パペット人形とサーボモーターを固定する機構
  • 土台
  • おもり

組み立て方

筐体

写真のような向きで2つのサーボモーターを固定します。下のサーボモーターはヨー軸(z軸)用、上のサーボモーターはピッチ軸(y軸)用です。

サーボモーター間の接点には大きな力が加わるので、下のサーボモーターに大きめの台座を取り付け、強力両面テープなどで頑丈に接着してください。

また、ピッチ軸用の上のサーボモーターに棒を取り付け(写真は青い棒)、この棒に人形を被せます。人形が空回りしないように工夫しましょう。

配線

サーボモーターのライブラリを参考に、以下の表や図のように配線します。ヨー軸用とピッチ軸用にそれぞれ1つずつサーボモーターを使用しています。

obniz サーボモーター
0 ヨー軸用 signal
1 ヨー軸用 Vcc
2 ヨー軸用 GND
3 ピッチ軸用 signal
4 ピッチ軸用 Vcc
5 ピッチ軸用 GND

プログラム

今回作成したプログラムでは、以下の3種類のモードを切り替えることができます。

  • モード1: スマートフォンの姿勢を真似る(スマートフォン内蔵慣性センサ使用)
  • モード2: 自分の顔の姿勢を真似る(Webカメラ+PoseNet使用)
  • モード3: 人がいる方を向く(Webカメラ+PoseNet使用)

PoseNetとは、Googleの機械学習ライブラリTensorFlowのJavaScript版であるTensorFlow.jsを用いた、Webブラウザでリアルタイムに人間の姿勢を推定できる機械学習モデルです。

「完成したプログラム」を参照しながら読むことを推奨します。

モード選択

classとして view-contents-mode1view-contents-mode2view-contents-mode3 を指定し、それぞれ各モードのときにのみ表示するようにします。 select要素でモードを1~3から選択できるようにし、SETボタンを押すと、そのとき選択されていたモードの値に応じて表示を切り替え、PoseNetを使用する場合はbindPage()を実行することで姿勢推定を開始します。

慣性センサの値取得

モード1では端末の慣性センサの値を使用します。 HTMLの読み込みが終わった時点で $(document).ready が実行され、

if (window.DeviceOrientationEvent) {
window.addEventListener('deviceorientation', function (eventData) {

の部分で,端末の姿勢を取得するたびにeventDataからヨー、ピッチを算出し、smp.yaw, smp.pitch としていつでも参照できるようにしています。constrain関数を使って値を調整しているのは、サーボモーターが90度を中心として0〜180度の間で動くことに合わせるためです。

PoseNetによる姿勢推定

モード2と3ではWebカメラとPoseNetによる姿勢推定を使用します。 こちらのサイトを参考にさせていただきました。 http://developers.gnavi.co.jp/entry/posenet/hasegawa

bindPage(); の中でPoseNetとWebカメラのセットアップを行い、detectPoseInRealTime(video, net); で姿勢推定を開始します。 net.estimateSinglePose(video, imageScaleFactor, flipHorizontal, outputStride); が実際に姿勢推定している部分です。

drawPoints(poses, ctx); では、姿勢推定結果posesから目や鼻等のパーツの座標を取得し、videoに重ねたcanvasに、各パーツのある場所に点とそのパーツ名を描画しています。

また、推定の精度を表すscorecanvas上に描画しています。さらに,モード2のために自分の顔の向きのヨー、ピッチ値を簡易的に三角関数を用いて求め、 mypose として呼び出せるようにしてあります。またモード3のために画面内における鼻の位置から、人形が向くべき方向のヨー、ピッチを三角関数を用いて簡易的に算出し、 myposition としています。

サーボモーターの駆動

サーボモーターの駆動は、startServo関数内でsetIntervalを用いることで、定期的にサーボモーターの角度が更新されます。

各モードを開始すると、
モード1 …startServo(smp, 50);
モード2 …startServo(mypose, 100);
モード3 …startServo(myposition, 100);
がそれぞれ実行され、例えばモード1では、変数 smp を参照し、そのヨー、ピッチの値に応じてサーボモーターの角度を更新する関数がupdateServoとして50msごとに呼び出されるようになります。

サーボモーターの角度は、 moveServoToward(src.yaw, src.pitch, max_deg, min_deg); 関数内で計算されて更新されます。この関数では、サーボモーターの動きを滑らかにするために、簡単なローパスフィルタをプログラムとして実装しています。lpf_aはローパスフィルタの係数で、s_yaws_pitchは更新前のサーボモーターのヨー、ピッチの値です。サーボモーターの角度を直接更新する関数は setServo 内にあり、ここで同時に s_yaws_pitch にその角度を代入しています。

完成したプログラム

うごかす

動作確認

本プログラムでは、モード1ではスマートフォンの慣性センサ、モード2と3ではWebカメラとPoseNetを使用するため、端末によって動作しない場合があります。そのため、プログラムを実行する前に動作確認を行ってください。(以下の動作確認のみであれば、obnizに繋ぐ必要はありません。)

慣性センサの動作確認方法

プログラムを実行し、モード1を選んでSETボタンを押すと、画面の下の方の Status 内の RAW: と書かれた行に、ヨー、ピッチ、ロールの値が表示されます。 スマートフォンを水平に保ったまま水平面上で方位磁針のように回転させると、一番左の値(ヨー)が0〜360度の間で変化すれば、ちゃんと動作しています。

<動作確認済>

  • Xperia Z3 (Android 6.0) + Chrome
  • AQUOS PHONE (Android 8.0) + Chrome

PoseNetの動作確認方法

プログラムを実行(HTTPS接続必須)し、モード2または3を選び少し待ちます。カメラの映像が映り、目や鼻の位置が認識されれば対応しています。

<動作確認済>

  • iPhone X + Safari
  • AQUOS PHONE (Android 8.0) + Chrome
  • MacBook Pro 2017 + Chrome

スマートフォンの場合はカメラを使うモードでは1fps程度の低速動作となりますが、MacBook Proでは10fps程度出ました。

操作方法

動作確認ができたら実際にパペットを動かしてみましょう。obnizに電源を繋ぎ、プログラムを実行します。

obnizが繋がったら、モードを選択してSETボタンを押すと、動作を開始します。モード1では、スマートフォンと人形の向きを揃えるため、STARTボタンを押すまで動作が開始しません。 STOPボタンを押すと、そのままサーボモーターを停止します。 RESETボタンを押すと、サーボモーターを初期位置(真ん中)に戻して停止します。

また、サーボモーターが荒ぶるのを防ぐため、Max SpeedとLPFをバーで調節できるようにしてあります。LPFはローパスフィルタの係数です。右のほうがフィルタが強くなり、一番右では止まります。 Max Speedが小さく、LPFが大きいほうが穏やかに動きますが、反応が遅くなります。逆にすると、反応は早いですが荒ぶります。

スマートフォン画面のキャプチャ