ラズパイ6日目②:カメラモジュールから動画撮影してポーズを判定する
Raspberry Pi + Edge TPU + PoseNetで、カメラモジュールで撮影した動画をリアルタイムに姿勢推定させてみます。
さらに、特定のポーズを判定してTerminalに出力するプログラムを作成しました。
- Raspberry Pi 3 Model B+
- カメラ:Raspberry Pi Camera B01 (Rev1.3)
- Edge TPU:Google Coral Edge TPU Accelerator vertion 1.0
- 機械学習モデル:Google Coral PoseNet
作ったもの
右手を上げたら「POSE:RIGHT HAND UP」、左手を上げたら「POSE:LEFT HAND UP」、両手を上げたら「POSE:BANZAI」を出力するプログラムです。また、顔部分には円形オブジェクトを重ねています。
カメラモジュールで撮影した動画をリアルタイムに姿勢推定する
この部分は用意されているサンプルプログラム pose_camera.py をそのまま流用したので割愛します。
特定のポーズを判定する
苦労したのがこの部分。Keypointの座標からどうやってポーズを判定させればよいものか…
このあたりのサイトを参考しようとするも、リンク先「やってみよう!Kinectアプリ開発」のサイトは閉鎖されていて詳細がわからない。 (英語サイトはいくつか見つけたが読む気が起きず… 技術者にはやはり英語力が必要だ…)
それでも
関節の向きから方向ベクトルを取得する方法と、関節の向き同士の角度を調べるために内積を計算する方法
てな一文があったので、「ベクトル 内積 角度」で検索けんさく
ベクトルの内積から関節角度を求める
numpyを利用して左右ひじ関節、左右ひざ関節の角度を求めることができました。
import numpy as np def calculate_jointAngles(pose): angles = [] #左ひじの角度 x1=pose.keypoints.get('left wrist').yx[1] y1=pose.keypoints.get('left wrist').yx[0] x0=pose.keypoints.get('left elbow').yx[1] y0=pose.keypoints.get('left elbow').yx[0] x2=pose.keypoints.get('left shoulder').yx[1] y2=pose.keypoints.get('left shoulder').yx[0] deg=np.round(inner_Calc(x0, x1, x2, y0, y1, y2)) angles.append(deg) #右ひじの角度 x1=pose.keypoints.get('right wrist').yx[1] y1=pose.keypoints.get('right wrist').yx[0] x0=pose.keypoints.get('right elbow').yx[1] y0=pose.keypoints.get('right elbow').yx[0] x2=pose.keypoints.get('right shoulder').yx[1] y2=pose.keypoints.get('right shoulder').yx[0] deg=np.round(inner_Calc(x0, x1, x2, y0, y1, y2)) angles.append(deg) #左ひざの角度 x1=pose.keypoints.get('left ankle').yx[1] y1=pose.keypoints.get('left ankle').yx[0] x0=pose.keypoints.get('left knee').yx[1] y0=pose.keypoints.get('left knee').yx[0] x2=pose.keypoints.get('left hip').yx[1] y2=pose.keypoints.get('left hip').yx[0] deg=np.round(inner_Calc(x0, x1, x2, y0, y1, y2)) angles.append(deg) #右ひざの角度 x1=pose.keypoints.get('right ankle').yx[1] y1=pose.keypoints.get('right ankle').yx[0] x0=pose.keypoints.get('right knee').yx[1] y0=pose.keypoints.get('right knee').yx[0] x2=pose.keypoints.get('right hip').yx[1] y2=pose.keypoints.get('right hip').yx[0] deg=np.round(inner_Calc(x0, x1, x2, y0, y1, y2)) angles.append(deg) return angles def inner_Calc(x0, x1, x2, y0, y1, y2): if all([x0, x1, x2, y0, y1, y2]): va=np.array([x1-x0,y1-y0]) vb=np.array([x2-x0,y2-y0]) innr=np.inner(va,vb) nrm=np.linalg.norm(va)*np.linalg.norm(vb) deg = np.rad2deg(np.arccos(np.clip(innr/nrm, -1.0, 1.0))) else: deg = -1 return deg
ポーズを判定する
関節の角度が取得できたので、次はポーズ判定させてみます。「右手をあげた状態」を
* 右ひじ関節の角度 >= 150°
* 右手首が右目よりも上にある
として判定させます。
「左手をあげた状態」「両手を上げた状態」も同様にして判定させます。
def pose_check(pose,angles): result = '' rHandUp = 0 lHandUp = 0 if angles[1]>150 and pose.keypoints.get('right wrist').yx[0] < pose.keypoints.get('right eye').yx[0]: rHandUp=1 if angles[0]>150 and pose.keypoints.get('left wrist').yx[0] < pose.keypoints.get('left eye').yx[0]: lHandUp=1 if rHandUp==1 and lHandUp==1: result='BANZAI' elif rHandUp==1: result='RIGHT HAD UP' elif lHandUp==1: result='LEFT HAD UP' return result
単純なポーズなら判定できることがわかりました。
ただ、判定結果には若干ゆれがあるので、「一定フレーム数以上で同じポーズ判定をされたら正式に認定する」みたいな処理をしないと使えなさそうです。