たまたまになるのですが、MediaPipeという手や顔の動きをリアルタイムで検出するライブラリを発見。
面白そうだったので触ってみることにしました。
■MediaPipeとは?
MediaPipeは、Googleが提供しているライブメディアやストリーミングメディア向けのMLソリューションであり、
CPUのみで検出が行えるツールになります。
MediaPipeの特徴として、
・エンドツーエンドの高速化:一般的なハードウェアでも高速化された組み込みの高速ML推論と処理
・一度構築してどこにでもデプロイ:統合ソリューションはAndroid、iOS、デスクトップ/クラウド、ウェブ、IoTで機能します
・すぐに使用できるソリューション:フレームワークのフルパワーを実証する最先端のMLソリューション
・フリーでオープンソース: Apache 2.0でのフレームワークとソリューション、完全に拡張可能でカスタマイズ可能
があります。
本ライブラリのソリューションとして、
・顔検知
・フェイスメッシュ
・手検知
・ポーズ
・物体検出
・インスタントモーショントラッキング
などなど、、、
12種類用意されております。
また、MediaPipeは様々なプログラミング言語に対応しております。
対応しているプログラミング言語やソリューションの詳細は下記をご確認ください。
https://google.github.io/mediapipe/
■MediaPipeインストール
今回はpythonを用いて手の動作検知を行っていきます。
そこで、pythonでMediaPipeが利用できるようにMediaPipeをインストールしていきます。
といってもpythonであれば、
pip install mediapipe or pip3 install mediapipe
でOKです。
pythonはライブラリインストールが簡単でありがたいです。
自分はインストール済みでしたが、OpenCVが必要になります。
インストールがまだの方は下記を実行してOpenCVをインストールください。
pip install opencv-python pip install opencv-contrib-python or pip3 install opencv-python pip3 install opencv-contrib-python
■MediaPipeを用いて手の動作を検知
では早速、てのモーションをリアルタイムで検知していきたいと思います!!
コードは公式が掲載しているコードを少し改変したものを載せます。
https://google.github.io/mediapipe/solutions/hands.html
コードはこちら。
import cv2 import mediapipe as mp class hands: mp_drawing = mp.solutions.drawing_utils mp_drawing_styles = mp.solutions.drawing_styles mp_hands = mp.solutions.hands after_img = "" StartFlg = False rsp = "" FinishFlg = False def HandsLoop(self): cap = cv2.VideoCapture(0) with self.mp_hands.Hands( min_detection_confidence=0.5, min_tracking_confidence=0.5) as hands: while cap.isOpened(): success, image = cap.read() if not success: print("Ignoring empty camera frame.") continue image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) before_image = image.copy() results = hands.process(image) if results.multi_hand_landmarks: for hand_landmarks in results.multi_hand_landmarks: self.mp_drawing.draw_landmarks( image, hand_landmarks, self.mp_hands.HAND_CONNECTIONS, self.mp_drawing_styles.get_default_hand_landmarks_style(), self.mp_drawing_styles.get_default_hand_connections_style()) self.after_img = cv2.flip(image - before_image, 1) if __name__ == '__main__': cv2.imshow('MediaPipe Hands', self.after_img) if cv2.waitKey(5) & 0xFF == 27: break cap.release() if __name__ == '__main__': m_hands = hands() m_hands.HandsLoop()
こちらのコードで手のリアルタイム検知結果がimshowで表示させることが出来ます。
パラメータに関してはとりあえずデフォルト設定で利用しておりますが、
・min_detection_confidence:検出が成功したと判断するための最小信頼値(0.0 ~ 1.0の範囲)
・min_tracking_confidence:手のランドマークが正常に追跡されたと見なされるためのランドマーク追跡モデルからの最小信頼値(0.0 ~ 1.0の範囲)
を意味しております。
処理フローですが、
まずOpenCVのVideoCaptureによりカメラからのフレーム画像を取得します。
その後、min_detection_confidence、min_tracking_confidenceをそれぞれ0.5のパラメータで
hands.processにてフレーム画像の解析を行い、
self.mp_drawing.draw_landmarksにて解析結果を画像上に描画しているようです。
最後に、imshowにて画像を表示。
この際、手の検知結果のみを表示させるために、
image - before_image
により検出結果の画像から元画像を差し引いています。
こちらのコードを実行するとこちらのような映像が表示されます。
■グー、チョキ、パーを認識してみる
手を検知するだけだとサンプルのまんまですので、、、
簡単にグー、チョキ、パーかを判別してみたいと思います。
コードはこちら。
import cv2 import mediapipe as mp from PIL import ImageFont, ImageDraw, Image import numpy as np import time FONT = ImageFont.truetype('C:\Windows\Fonts\meiryo.ttc' , 32) class hands: mp_drawing = mp.solutions.drawing_utils mp_drawing_styles = mp.solutions.drawing_styles mp_hands = mp.solutions.hands after_img = "" StartFlg = False rsp = "" FinishFlg = False def HandShape(self, hand_landmarks, image): image_width, image_height = image.shape[1], image.shape[0] index_finger = [ int(hand_landmarks.landmark[8].y * image_height), int(hand_landmarks.landmark[6].y * image_height) ] index_middle = [ int(hand_landmarks.landmark[12].y * image_height), int(hand_landmarks.landmark[10].y * image_height) ] index_ring = [ int(hand_landmarks.landmark[16].y * image_height), int(hand_landmarks.landmark[14].y * image_height) ] index_pinky = [ int(hand_landmarks.landmark[20].y * image_height), int(hand_landmarks.landmark[18].y * image_height) ] fingersList = [index_finger, index_middle, index_ring, index_pinky] return fingersList def JudgeRPS(self, fingersList): hand_ret = "" if fingersList[0][0] > fingersList[0][1] and fingersList[1][0] > fingersList[1][1] and \ fingersList[2][0] > fingersList[2][1] and fingersList[3][0] > fingersList[3][1]: hand_ret = "グー" elif fingersList[2][0] > fingersList[2][1] and fingersList[3][0] > fingersList[3][1]: hand_ret = "チョキ" else: hand_ret = "パー" return hand_ret def HandsLoop(self): cap = cv2.VideoCapture(0) with self.mp_hands.Hands( min_detection_confidence=0.5, min_tracking_confidence=0.5) as hands: while cap.isOpened(): success, image = cap.read() if not success: print("Ignoring empty camera frame.") continue image.flags.writeable = True image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) before_image = image.copy() results = hands.process(image) if results.multi_hand_landmarks: for hand_landmarks in results.multi_hand_landmarks: self.mp_drawing.draw_landmarks( image, hand_landmarks, self.mp_hands.HAND_CONNECTIONS, self.mp_drawing_styles.get_default_hand_landmarks_style(), self.mp_drawing_styles.get_default_hand_connections_style()) fingerList = self.HandShape(hand_landmarks, image) self.rsp = self.JudgeRPS(fingerList) self.after_img = cv2.flip(image - before_image, 1) img_pil = Image.fromarray(self.after_img) draw = ImageDraw.Draw(img_pil) draw.text((10, 350), self.rsp, font = FONT, fill = (255,0,255)) self.after_img = np.array(img_pil) cv2.imshow('MediaPipe Hands', self.after_img) if cv2.waitKey(5) & 0xFF == 27: break cap.release() if __name__ == '__main__': m_hands = hands() m_hands.HandsLoop()
先ほど解析しませんでしたが、
hand_landmarks
に検知した手の節?の座標が格納されております。
hand_landmarksは{x,y}構造体の配列になっておりそれぞれ下記画像の通り割り当てられております。
こちらの座標情報を元に、
手の形状解析を行っております。
解析のコードはこちら。
def HandShape(self, hand_landmarks, image): image_width, image_height = image.shape[1], image.shape[0] index_finger = [ int(hand_landmarks.landmark[8].y * image_height), int(hand_landmarks.landmark[6].y * image_height) ] index_middle = [ int(hand_landmarks.landmark[12].y * image_height), int(hand_landmarks.landmark[10].y * image_height) ] index_ring = [ int(hand_landmarks.landmark[16].y * image_height), int(hand_landmarks.landmark[14].y * image_height) ] index_pinky = [ int(hand_landmarks.landmark[20].y * image_height), int(hand_landmarks.landmark[18].y * image_height) ] fingersList = [index_finger, index_middle, index_ring, index_pinky] return fingersList
解析の結果から、グー、チョキ、パーの解析を行います。
コードはこちら。
今回は簡単のために、y座標のみをチェックかつ先端(TIP)がDIPの位置以下になった場合に指が曲がったと判断し、
その曲がった指をチェックすることでグー、チョキ、パーを解析しています。
def JudgeRPS(self, fingersList): hand_ret = "" if fingersList[0][0] > fingersList[0][1] and fingersList[1][0] > fingersList[1][1] and \ fingersList[2][0] > fingersList[2][1] and fingersList[3][0] > fingersList[3][1]: hand_ret = "グー" elif fingersList[2][0] > fingersList[2][1] and fingersList[3][0] > fingersList[3][1]: hand_ret = "チョキ" else: hand_ret = "パー" return hand_ret
後は解析した結果を画像に重畳させてimshowで表示。
self.after_img = cv2.flip(image - before_image, 1) img_pil = Image.fromarray(self.after_img) draw = ImageDraw.Draw(img_pil) draw.text((10, 350), self.rsp, font = FONT, fill = (255,0,255)) self.after_img = np.array(img_pil) cv2.imshow('MediaPipe Hands', self.after_img)
こちらのコードを実行した結果は下記に掲載しました。
よろしければご覧ください。
youtu.be
■最後
MediaPipeですが、結構検出精度が高く使っていてとても楽しかったです。
もう少し使ってみたいと思います。
また、何か出来たら掲載していきたいと思います。