ABEJA Advent Calendar 2019の18日目です。
はじめに
今年の10月にABEJAにjoinしたimaisです。普段の業務では、ABEJA Insight for Retail開発チームでお客様の店舗内での動きを分析する動線分析の開発を担当しています。
今回の記事では、二次元画像から姿勢推定するOpenPoseと音楽向けプログラミング言語であるChucKを組み合わせて作った、簡易なインタラクティブ音楽プレイヤーについて、そのシステム概要と開発内容をご紹介します。
システム概要
**「人間が手拍子をすると、それに応じて一つずつ音楽トラックが追加されていく」**以下のようなシステムをMacBook Pro上に構築します。
システムは以下のようなステップで動作します。
- 制御アプリケーションは、OpenPoseによって推定した人間の姿勢情報を使って、特定のポーズ(今回の場合は手拍子)が取られたかをチェック。
- そのポーズが取られたら、制御アプリケーションは音楽トラックの追加コマンドをChuckアプリケーションに送信。
- Chuckアプリケーションは、予め用意されている音楽トラックを一つずつ追加。
- 追加された音楽トラックは、オーディオ接続を制御するjackを通じて、スピーカで再生。
開発環境の構築
OpenPose開発環境
今回は、ildoonet氏によるTensorFlow版のOpenPoseアプリ実装であるtf-pose-estimationを改変して、制御アプリケーションを開発することにしました。
mdo4nt6n氏によるチュートリアル【TensorFlow版】MacBookで行うOpenPose (osx Mojave対応)に従い、tf-pose-estimationの動作環境を構築します。
TensorFlowのみ、最新バージョンを入れるとうまく行かなかったので、今回は以下の通り旧バージョンをインストールしました。
$ pip install tensorflow=1.14
上記のチュートリアル記事にもあるように、以下のコマンドで、MacBookの内蔵カメラでリアルタイムにOpenPoseの姿勢推定が動作することを確認します。
$ python run_webcam.py --model=mobilenet_thin --resize=432x368 --camera=0
ChucK開発環境
ChucK
ChucKからMacOS X用のインストーラをダウンロードし、最新版であるv1.4.0をインストールします。
Jack
以下のコマンドで、Homebrewに用意されているパッケージをインストールします。
$ brew install jack
動作確認
まず、Jackを一つのTerminal上で以下のように起動します。
$ jackd -d coreaudio
Jackの起動を確認したら、別のTerminal上でChucKのソースコードに含まれるexamplesの中から、適当にファイルを選んで音楽を再生してみます。
$ chuck examples/rhodey.ck
音楽が再生されたら成功です!
制御アプリケーション
tf-pose-estimationに含まれるMacBookの内蔵カメラの姿勢推定デモアプリである、run_webcam.pyに以下のコードを加えます。
if 0 < len(humans) and hand_clapped(humans[0]) and \
HAND_CLAPPING_MASK_DURATION_SEC < (time.time() - time_hand_clapped):
time_hand_clapped = time.time()
osc.send_message('/sndbuf/beats', [1])
やっていることは単純で、一人以上人が検出されたら一人目の人が手拍子を行なっているかどうかをhand_clapped
によりチェックして、もし手拍子を検出したら、ChucKにコマンド(タイミングを制御するだけなので、ただの数字の1)を送っているだけです。ChucKはOpen Sound Control (OSC)によるデータ送受信をサポートしており、上ではOSCの/sndbuf/beats
チャンネルを通じてコマンドを送信しています。
あまり連続してコマンドが送信され過ぎないように、一度コマンドが送信されたらHAND_CLAPPING_MASK_DURATION_SEC
秒だけは手拍子が検出されたとしても、コマンドを送信しないようにしています。
手拍子を検出するhand_clapped
は以下のようになっています。
def hand_clapped(human):
parts = [part for idx, part in human.body_parts.items() if part.score > THRESHOLD_PART_CONFIDENCE]
is_rwrist, part_rwrist = include_part(parts, RWrist)
is_lwrist, part_lwrist = include_part(parts, LWrist)
if is_rwrist and is_lwrist:
dist = math.sqrt((part_rwrist.x - part_lwrist.x)**2 + (part_rwrist.y - part_lwrist.y)**2)
if dist < THRESHOLD_HAND_CLAPPING_DISTANCE:
return True
return False
これも非常に単純で、右手首(part_rwrist
)と左手首(part_lwrist
)の距離を計算して、それが閾値THRESHOLD_HAND_CLAPPING_DISTANCE
よりも小さかったら、手拍子として検出しています。
それなので、実際には右手と左手を近づければいいだけなのですが、手拍子した方がノリが良くて気持ちいいです☺️。
制御アプリケーション側はこれだけです!
ChucKアプリケーション
ChucKではCのような手続き型言語のスタイルで、音楽をプログラミングすることができます。
以下のコードでは予めbeats
に格納していた音楽トラックをメッセージを受け取る毎に追加しています。
ちなみに全てのトラックを追加し終わったら、追加したのと逆順に音楽トラックを一つずつ止めていくようにしています。
while (true) {
// wait for event to arrive
oe => now;
// grab the next message from the queue.
float msg;
while (oe.nextMsg() != 0) {
oe.getInt() => msg;
<<< "Beats received: ", msg >>>;
if (msg != 0) {
if (flag == 1) {
Machine.add(beats[i]) => beat_refs[i];
if (i == 6) {
0 => flag;
} else {
i + 1 => i;
}
} else {
Machine.remove(beat_refs[i]);
if (i == 0) {
1 => flag;
} else {
i - 1 => i;
}
}
}
}
}
出来上がった音楽のサンプル
実際にポーズを取っている所の動画をアップロードするのは恥ずかしいので、各種プログラムが動いているターミナルと出来上がった音楽のデモ動画を貼り付けおきます。実際には手拍子に合わせて、音楽トラックが追加されています。
ちなみに動画中で使っている音楽トラックは、ChucKのexamplesに含まれているものです。
[]
(https://player.vimeo.com/video/380290288)
まとめ
ChuckとOpenPoseを組み合わせて、音楽トラックの再生タイミングを制御できる簡易なインタラクティブ音楽プレイヤーを作りました。今回は手拍子をキッカケにして、音楽を追加するだけの単純なシステムですが、実際に出来上がったシステムは結構ノリが良くて楽しいです。今度は、アニメーションなども追加して、インタラクティブ性を上げていけたらと思います!