こんにちは
なんとなく、pythonの骨格推定パッケージのmediapipeを使って矢状面から撮影した動画から股関節、膝関節の時系列の角度を出すスクリプトかいてみました。
mediapipeを用いたpythonスクリプトに関しては過去の記事でも作成しているので、そちらの記事も併せて参考にしてみるのもよいでしょう。
参考URL: https://himahimaknowledge.blogspot.com/2021/12/mediapipe.html
pythonでmediapipeを使った関節角度推定のコードはこんなかんじ。
import numpy as np import mediapipe as mp import cv2 import tkinter, tkinter.filedialog, tkinter.messagebox #ここまで必要なパッケージのインポート
#自作関数
def calangle(data1,data2):
#data1とdata2はそれぞれ差分をとった後の各軸の変位データ
i = np.dot(data1, data2)
n = np.linalg.norm(data1) *np.linalg.norm(data2)
c = i / n
Ang = np.arccos(c)* 180 / np.pi
return Ang
def get_data_only_path(dir_name,typ):
#ファイルパスの取得をGUIで行う関数
# dir_name:対象となるディレクトリ名(dataが保存されているディレクトリ名)
# str_name:matファイルのstruct形式の変数名
idir = dir_name
root = tkinter.Tk()
file_path = tkinter.filedialog.askopenfilename(initialdir = idir,filetypes = [('テキストファイル',typ)] )
#delete root window
root.withdraw()
return file_path
#ファイルの選択GUIの適用
media_filename = get_data_only_path('raw_data', 'mp4')
#mediapioe の設定もろもろ
mp_drawing = mp.solutions.drawing_utils
mp_holistic = mp.solutions.holistic
data_land = np.zeros((0,99))
#動画データをopencvに読み込ませる
cap = cv2.VideoCapture(media_filename)
cap.set(cv2.CAP_PROP_BUFFERSIZE, 1)
#一連の動画をmediapipipeに適用、各フレームの各関節のピクセル値を取得
with mp_holistic.Holistic(
min_detection_confidence=0.5,
min_tracking_confidence=0.5) as holistic:
while cap.isOpened():
success, image = cap.read()
if not success:
print("Ignoring empty camera frame.")
break
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image.flags.writeable = False
results = holistic.process(image)
image.flags.writeable = True
image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
mp_drawing.draw_landmarks(
image, results.left_hand_landmarks, mp_holistic.HAND_CONNECTIONS)
mp_drawing.draw_landmarks(
image, results.right_hand_landmarks, mp_holistic.HAND_CONNECTIONS)
mp_drawing.draw_landmarks(
image, results.pose_landmarks, mp_holistic.POSE_CONNECTIONS)
#get coordinate
data_land2 = np.zeros((1,3))
for x in range (0,33):
data1 = results.pose_landmarks.landmark[x].x
data2 = results.pose_landmarks.landmark[x].y
data3 = results.pose_landmarks.landmark[x].z
keydata = np.hstack((data1,data2,data3)).reshape(1,-1)
data_land2 = np.hstack((data_land2,keydata))
data_land2 = data_land2[:,3:]
data_land = np.vstack((data_land,data_land2))
cv2.imshow('MediaPipe Holistic', image)
if cv2.waitKey(5) & 0xFF == 27:
break
cap.release()
#実際に出力された各関節座標の定義
d23_l_hip = data_land[:,69:72]
d24_r_hip = data_land[:,72:75]
d25_l_knee = data_land[:,75:78]
d26_r_knee = data_land[:,78:81]
d27_l_ank = data_land[:,81:84]
d28_r_ank = data_land[:,84:87]
l_hip = d23_l_hip[:,0:2]
r_hip = d24_r_hip[:,0:2]
l_knee = d25_l_knee[:,0:2]
r_knee = d26_r_knee[:,0:2]
l_ank = d27_l_ank[:,0:2]
r_ank = d28_r_ank[:,0:2]
#角度計算用にベクトル化
l_vec1 = l_hip-l_knee
l_vec2 = l_knee-l_ank
r_vec1 = r_hip-r_knee
r_vec2 = r_knee-r_ank
base_vec = np.zeros((len(r_hip[:,0]),2))
base_vec[:,1] = base_vec[:,1]-1
l_hip_ang = np.zeros(len(l_vec1))
r_hip_ang = np.zeros(len(r_vec1))
l_knee_ang = np.zeros(len(l_vec2))
r_knee_ang = np.zeros(len(r_vec2))
#角度計算
for x in range(len(l_vec1)):
r_hip_ang[x] = calangle(base_vec[x], r_vec1[x])
l_hip_ang[x] = calangle(base_vec[x], l_vec1[x])
r_knee_ang[x] = calangle(r_vec1[x], r_vec2[x])
l_knee_ang[x] = calangle(l_vec1[x], l_vec2[x])
data = np.vstack((r_hip_ang,l_hip_ang,r_knee_ang,l_knee_ang)).T
np.savetxt('keypoint_results.csv',data,delimiter = ',')ざっくりと書くとこんな感じです。以前に紹介したGUIでファイルを選択するスクリプトや角度を計算するサンプルを入れています。
このスクリプトを動かすうえで一つ条件があります。それは動画内に映っている人物が一人、かつ一連の動画内で一名の被験者の全身が必ず映っている必要があります。
そのため、見切れているフレームや複数人移っているところは編集で削除する必要があります。
なぜこういった条件があるかというと、単に自分がIF関数とかを使って映っていない際の条件分けをしていないのでロストするとエラーして停止してしまうという欠陥を抱えているからです。
あと保存はcsvで行われていますが、常に出力されるcsvファイル名は一緒なので、コードを回す度に上書きされてしまいます。必要なデータは常に外部に抽出して補完しておきましょう。
あとこのコードを記載したファイルと同じディレクトリに動画データを保存するraw_dataという名前のフォルダを作成してください。
これがこのコードを動かすための下準備と条件になります。あとはnumpyとopencvとmediapipe などの必要なパッケージをあらかじめインストールしておく必要はあります。
※かなりざっくり作ってしまったのでその内多分更新します。。。
座標データの視覚化にはスティックピクチャによる表示が便利。
これらの座標データについては、単に各座標の数字だけだと、イメージが付きにくい場合が多いです。動作分析系の研究で関節位置の座標データを扱う場合には、スティックピクチャを用いた視覚化を用いる場合が多いです。
こちらについてもまた別記事で紹介していこうと思います。
