見出し画像

[ラズパイ / 電子工作]MediaPipeで気持ちよく「家トレ」

どうも~、IoT探検家のシンクンです。

今回はMediaPipeを使って、腕立てとスクワットの筋トレカウンターを作って、自宅でのトレーニングを気持ちよくできるようにしてみました。

MediaPipeとは
Googleが提供するオープンソースのメディアデータ向け機械学習用フレームワークで、顔認識や姿勢推定などのモデルをエッジで利用できる。

画像1

用意したもの

・ラズパイ本体(Raspberry Pi 4)
・4インチのLCDモニター
・外付けのカメラ
・モバイルバッテリー
・キーボード
・microSDカード

自作の流れ

1)ラズパイの初期設定
2)MediaPipeの初期設定
3)筋トレカウンターモデルを準備
4)  「家トレ」システム完成

1)ラズパイの初期設定

まずは、Raspberry Pi(ラズベリー パイ)、略してラズパイという小型コンピュータを用意して、初期設定をします。

詳細は「[普通科高校卒の週末プログラマー]ラズパイの初期設定をしてみた」を御覧ください。

利用したラズパイはRaspberry Pi 4で、インストールしたOSはRaspberry Pi OS (32bit)です。

2)MediaPipeの初期設定

次にラズパイでMediaPipeの設定をしていきます。

MediaPipeの初期設定は公式サイトを見ても、OSやプログラミング言語によって様々なバリエーションがあり混乱しますが、今回はRaspberry Pi 4にインストールされたRaspberry Pi OSでPythonを使えるようにしたいので、PyPiから提供されているパッケージを利用することにしました。

PyPiとは
Python Package Indexの略。プログラミング言語Pythonの、サードパーティーソフトウェアリポジトリ。


# FFmpegとOpenCVをインストール
sudo apt install ffmpeg python3-opencv

# 依存パッケージをインストール
sudo apt install libxcb-shm0 libcdio-paranoia-dev libsdl2-2.0-0 libxv1  libtheora0 libva-drm2 libva-x11-2 libvdpau1 libharfbuzz0b libbluray2 libatlas-base-dev libhdf5-103 libgtk-3-0 libdc1394-22 libopenexr23

# Pythonの仮想環境内でMediaPipeをインストール
pip3 install mediapipe-rpi4

手順に従ってラズパイのLXTerminalで上のコードを入力することで、PythonのパッケージがインストールされてラズパイでMediaPipeが利用できるようになりました。

※エラー対応
MediaPipeをインストールした直後に、試しに公式に掲載されている姿勢推定のコードを動かそうとしたらエラーが発生したので解消方法を忘備録として残しておきます。

a) エラーメッセージ
AttributeError: module 'mediapipe.python.solutions' has no attribute 'drawing_styles'

b) 解決方法
ラズパイの~/.virtualenvs/cv/lib/python3.7/site-packages/mediapipe/python/solutionsと公式のGithubのsolutionsフォルダを比べてみると、ラズパイのsolutionsフォルダにはdrawing_styles.pyが配置されていないため、公式からダウンロードしてラズパイに配置。そして、~/.virtualenvs/cv/lib/python3.7/site-packages/mediapipe/python/solutions/__init_.pyにimport mediapipe.python.solutions.drawing_stylesを追記。

3)筋トレカウンターモデルを準備

import cv2
import mediapipe as mp

mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_pose = mp.solutions.pose

cap = cv2.VideoCapture(0)
with mp_pose.Pose(
   min_detection_confidence=0.5,
   min_tracking_confidence=0.5) as pose:
 while cap.isOpened():
   success, image = cap.read()
   if not success:
     print("Ignoring empty camera frame.")
     # If loading a video, use 'break' instead of 'continue'.
     continue

   # Flip the image horizontally for a later selfie-view display, and convert
   # the BGR image to RGB.
   image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB)
   
   # To improve performance, optionally mark the image as not writeable to
   # pass by reference.
   image.flags.writeable = False
   results = pose.process(image)

   # Draw the pose annotation on the image.
   image.flags.writeable = True
   image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)
   
   mp_drawing.draw_landmarks(
       image,
       results.pose_landmarks,
       mp_pose.POSE_CONNECTIONS,
       landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style())
   
   cv2.imshow('MediaPipe Pose', image)
   
   if cv2.waitKey(5) & 0xFF == 27:
     break
     
cap.release()

ここからは「家トレ」システムに使う筋トレカウンターモデルをMediaPipeの公式で掲載されている姿勢推定モデル(上のコード)をカスタマイズして作っていきます。

画像3

上のコードを動かすとこのように全身のランドマークが取得できます。

今回カウントしたいのは腕立てとスクワットの2つの筋トレです。

画像2

MediaPipeの姿勢推定モデルでは上の図のように身体の33ヶ所のランドマークを推定できます。

そして、これらのランドマークの位置関係やランドマークを結んだ線の角度を計算することで腕立てとスクワットをカウントできるようにします。

A) 腕立て

def findPosition(image, draw=True):
  lmList = []
  
  mp_drawing.draw_landmarks(
      image,
      results.pose_landmarks,
      mp_pose.POSE_CONNECTIONS)

  for id, lm in enumerate(results.pose_landmarks.landmark):
      h, w, c = image.shape

      # 正規化される前のランドマークの座標cxとcyを取得
      cx, cy = int(lm.x * w), int(lm.y * h)

      lmList.append([id, cx, cy])

  return lmList

pose_landmarks.landmarkのアウトプットに含まれる座標は画像サイズによって正規化されているので、まずは上のようなメソッドで正規化される前の座標を取得して配列に入れます。

lmList = findPosition(image, draw=True)

# 11:right shoulder
# 12:left shoulder
# 13:right elbow
# 14:left elbow

if (lmList[12][2] and lmList[11][2] >= lmList[14][2] and lmList[13][2]):
     stage = "down"
     
if (lmList[12][2] and lmList[11][2] <= lmList[14][2] and lmList[13][2]) and stage == "down":
     stage = "up"
     counter += 1     

そして、左肩(11)と右肩(12)の高さがそれぞれ、左ヒジ(13)と右ヒジ(14)より低い位置になった時に1回カウントするコードを追記。

これで腕立てをカウントできるようになりました。つぎはスクワットです。

B) スクワット

def angle_between_points(a, b, c):
  a = np.array(a)
  b = np.array(b)
  c = np.array(c)
  ba = a - b
  bc = c - b

  cosine_angle = np.dot(ba, bc) / (np.linalg.norm(ba) * np.linalg.norm(bc))
  angle = np.arccos(cosine_angle)

  return np.degrees(angle)
画像4

まずは2辺のベクトルからヒザの角度を求めれるようにメソッドを追加します。(参考サイト:内積の幾何学的意味)

lmList = findPosition(image, draw=True)

# 24:left hip
# 26:left knee
# 28:left ankle

center_hip = (lmList[24][1], lmList[24][2])
center_knee = (lmList[26][1], lmList[26][2])
center_ankle = (lmList[28][1], lmList[28][2])

squat_left_angle = angle_between_points(
    center_hip, center_knee, center_ankle)

squat_pos = 1 if squat_left_angle <= 80 else 0

if prev_squat_pos - squat_pos == 1:
    count_of_squats += 1

そして左お尻(24)と左ヒザ(26)と左くるぶし(28)の座標から、ヒザの角度を求めて、それが80度より狭くなった時に1回カウントするコードを追記。

これでスクワットをカウントできるようになりました。

4) 「家トレ」システム完成

ここまで出来たら、いよいよカメラとスピーカーをラズパイに取り付けて組み立てをおこない、「家トレ」システムを動かしてみます。

画像9

A) 腕立て

画像5
画像6

肩の高さがヒジより低い位置になった時に肩のランドマークの色が赤から緑に変わり1回カウントされます。

B) スクワット

画像7
画像8

ヒザの角度が80度より狭くなった時にヒザのランドマークの色が赤から緑に変わり1回カウントされます。

振り返り

・MediaPipeを使うと簡単に姿勢推定モデルを試すことができる
・内積を使った計算をすれば座標から角度を求めれる
・MediaPipeでは姿勢推定以外にも物体や手の検知モデルも使えるので今度試してみたいです

Youtubeで見る

動画は少し音を出して少しエッチなコンテンツになっています。閲覧にご注意ください^_^



この記事が気に入ったらサポートをしてみませんか?