Pythonで画像認識 MediaPipeを試す -その2-

アバター

はじめに

前回、MediaPipeで顔と手のトラッキングを試してみました。
まだ他の機能もあるようなので、どんなものなのか、やってみようと思います。

MediaPipeの機能

Face Detection

画像内の、顔のパーツの位置を点で表します。


画像はわかりにくいのですが、白四角の枠と、赤い点が6つあります。
白四角は輪郭全体、赤点は両目・両耳・鼻・口の位置です。

Face Detectionサンプルコード

import cv2
import mediapipe as mp
import numpy as np

# フェイストラッキング関数
def face_detection_exec(img, facedetection):
img.flags.writeable = False
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# ランドマーク検出
results = facedetection.process(img)

img.flags.writeable = True
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)

# 背景を黒にして、ランドマーク画像だけ残す
img_h, img_w, _ = img.shape
blank = np.zeros((img_h, img_w, 3))
img = blank

if results.detections:
# 検出したランドマークを画像内に描画
for detection in results.detections:
mp_drawing.draw_detection(
img,
detection)

return img


mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_facedetection = mp.solutions.face_detection

cap = cv2.VideoCapture(0) # カメラID 内蔵カメラはおそらく0

# min_detection_confidence = ランドマーク検出成功判定の閾値
facedetection = mp_facedetection.FaceDetection(
model_selection=0,
min_detection_confidence=0.5)

# カメラが有効の場合のみ処理する
while cap.isOpened():

# カメラから画像1枚取得
success, image_org = cap.read()
if not success:
print("Ignoring empty camera frame.")
continue

tick = cv2.getTickCount() # fps計算用

# ホリスティックトラッキング
# 元画像から顔の特徴点のみ抽出
image = face_detection_exec(image_org, facedetection)

# image = cv2.flip(image, 1) # ミラー反転する

# fps計算し、画像内に埋め込む
fps = cv2.getTickFrequency() / (cv2.getTickCount() - tick)
cv2.putText(
image,
"FPS: " + str(int(fps)),
(image.shape[1] - 150, 40), # 画面右上あたり
cv2.FONT_HERSHEY_PLAIN,
2, # 文字の大きさ
(0, 255, 0), # 文字色(緑)
)

# PC画面に画像表示
cv2.imshow('MediaPipe Face Mesh', image)

#終了判定 ESCで終了する
if cv2.waitKey(5) & 0xFF == 27:
break

cap.release()

Face Mesh

顔を詳細に検出し、眉・目・口・輪郭を動きをトラッキングします。
FACEMESH_TESSELATIONでメッシュ表示すると、鼻も映りますね。
 ※眉や目は前髪で隠れやすく、隠れるとうまくトラッキングできないので注意



Face Meshサンプルコード

import cv2
import mediapipe as mp
import numpy as np

# フェイストラッキング関数
def face_mesh_exec(img, face_mesh):
img.flags.writeable = False
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 顔ランドマーク検出
results = face_mesh.process(img)

img.flags.writeable = True
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)

# 背景を黒にして、ランドマーク画像だけ残す
img_h, img_w, _ = img.shape
blank = np.zeros((img_h, img_w, 3))
img = blank

if results.multi_face_landmarks:
# 検出したランドマークを画像内に描画
for face_landmarks in results.multi_face_landmarks:
# 顔の中をメッシュ表示する場合、このコメントアウトを解除する
mp_drawing.draw_landmarks(
image=img,
landmark_list=face_landmarks,
connections=mp_face_mesh.FACEMESH_TESSELATION,
landmark_drawing_spec=None,
connection_drawing_spec=mp_drawing_styles.get_default_face_mesh_tesselation_style())
mp_drawing.draw_landmarks(
image=img,
landmark_list=face_landmarks,
connections=mp_face_mesh.FACEMESH_CONTOURS, # 目、口、輪郭の境界線
landmark_drawing_spec=None,
connection_drawing_spec=mp_drawing_styles.get_default_face_mesh_contours_style())
mp_drawing.draw_landmarks(
image=img,
landmark_list=face_landmarks,
connections=mp_face_mesh.FACEMESH_IRISES, # 虹彩
landmark_drawing_spec=None,
connection_drawing_spec=mp_drawing_styles.get_default_face_mesh_iris_connections_style())

return img


mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_face_mesh = mp.solutions.face_mesh

cap = cv2.VideoCapture(0) # カメラID 内蔵カメラはおそらく0

# max_num_faces = 画像内で検出する顔の数
# refine_landmarks = 目の周りを細かくするか
# min_detection_confidence = ランドマーク検出成功判定の閾値
# min_tracking_confidence = ランドマークトラッキング成功判定の閾値
# static_image_mode = 静止画かどうか
face_mesh = mp_face_mesh.FaceMesh(
max_num_faces=1,
refine_landmarks=True,
min_detection_confidence=0.5,
min_tracking_confidence=0.5,
static_image_mode=False)

# カメラが有効の場合のみ処理する
while cap.isOpened():

# カメラから画像1枚取得
success, image_org = cap.read()
if not success:
print("Ignoring empty camera frame.")
continue

tick = cv2.getTickCount() # fps計算用

# フェイストラッキング
# 元画像から顔の特徴点のみ抽出
image = face_mesh_exec(image_org, face_mesh)

# image = cv2.flip(image, 1) # ミラー反転する

# fps計算し、画像内に埋め込む
fps = cv2.getTickFrequency() / (cv2.getTickCount() - tick)
cv2.putText(
image,
"FPS: " + str(int(fps)),
(image.shape[1] - 150, 40), # 画面右上あたり
cv2.FONT_HERSHEY_PLAIN,
2, # 文字の大きさ
(0, 255, 0), # 文字色(緑)
)

# PC画面に画像表示
cv2.imshow('MediaPipe Face Mesh', image)

#終了判定 ESCで終了する
if cv2.waitKey(5) & 0xFF == 27:
break

cap.release()

Hands

手をトラッキングします。
顔のようなメッシュ表示はありませんが、指関節の細かな動きをとれます。
手を裏返す、手を握る、上下逆さにする、などしても、うまく追従できるようです。
ただ、手招きするような動作で、画面でみたとき手が平たくなるポイントがありますが、そのときはうまく追従できていませんでした。さすがに手の形から逸脱してしまうと、推論が難しくなるようです。


Handsサンプルコード

import cv2
import mediapipe as mp
import numpy as np

# ハンドトラッキング関数
def hands_exec(img, hands):
img.flags.writeable = False
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 手ランドマーク検出
results = hands.process(img)

img.flags.writeable = True
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)

# 背景を黒にして、ランドマーク画像だけ残す
img_h, img_w, _ = img.shape
blank = np.zeros((img_h, img_w, 3))
img = blank

if results.multi_hand_landmarks:
# 検出したランドマークを画像内に描画
for hand_landmarks in results.multi_hand_landmarks:
mp_drawing.draw_landmarks(
image=img,
landmark_list=hand_landmarks,
connections=mp.solutions.hands.HAND_CONNECTIONS,
landmark_drawing_spec=mp_drawing_styles.get_default_hand_landmarks_style(),
connection_drawing_spec=mp_drawing_styles.get_default_hand_connections_style())

return img


mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_hands = mp.solutions.hands

cap = cv2.VideoCapture(0) # カメラID 内蔵カメラはおそらく0

# max_num_hands = 画像内で検出する手の数
# min_detection_confidence = ランドマーク検出成功判定の閾値
# min_tracking_confidence = ランドマークトラッキング成功判定の閾値
# static_image_mode = 静止画かどうか
hands = mp_hands.Hands(
max_num_hands=2,
min_detection_confidence=0.5,
min_tracking_confidence=0.5,
static_image_mode=False)

# カメラが有効の場合のみ処理する
while cap.isOpened():

# カメラから画像1枚取得
success, image_org = cap.read()
if not success:
print("Ignoring empty camera frame.")
continue

tick = cv2.getTickCount() # fps計算用

# ハンドトラッキング
# 元画像から手の特徴点のみ抽出
image = hands_exec(image_org, hands)

# image = cv2.flip(image, 1) # ミラー反転する

# fps計算し、画像内に埋め込む
fps = cv2.getTickFrequency() / (cv2.getTickCount() - tick)
cv2.putText(
image,
"FPS: " + str(int(fps)),
(image.shape[1] - 150, 40), # 画面右上あたり
cv2.FONT_HERSHEY_PLAIN,
2, # 文字の大きさ
(0, 255, 0), # 文字色(緑)
)

# PC画面に画像表示
cv2.imshow('MediaPipe Face Mesh', image)

#終了判定 ESCで終了する
if cv2.waitKey(5) & 0xFF == 27:
break

cap.release()

Pose

顔から足まで、全身をトラッキングします。
顔や手の細かさは、やや簡素になります。

画像は肩から上と片腕を映しています。足まで映せば、ちゃんと追従します。
首は無いようですね。
顔は、目・鼻・口の位置だけになっています。
手は指4本が繋がって、ミトンを付けているような見た目になっています。指を曲げることはできません。

Poseサンプルコード

import cv2
import mediapipe as mp
import numpy as np

# ポーズトラッキング関数
def pose_exec(img, pose):
img.flags.writeable = False
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# ポーズランドマーク検出
results = pose.process(img)

img.flags.writeable = True
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)

# 背景を黒にして、ランドマーク画像だけ残す
img_h, img_w, _ = img.shape
blank = np.zeros((img_h, img_w, 3))
img = blank

if results.pose_landmarks:
# 検出したランドマークを画像内に描画
mp_drawing.draw_landmarks(
image=img,
landmark_list=results.pose_landmarks,
connections=mp_pose.POSE_CONNECTIONS,
landmark_drawing_spec=mp_drawing_styles.get_default_pose_landmarks_style())

return img


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

cap = cv2.VideoCapture(0) # カメラID 内蔵カメラはおそらく0

# min_detection_confidence = ランドマーク検出成功判定の閾値
# min_tracking_confidence = ランドマークトラッキング成功判定の閾値
# static_image_mode = 静止画かどうか
pose = mp_pose.Pose(
min_detection_confidence=0.5,
min_tracking_confidence=0.5,
static_image_mode=False)

# カメラが有効の場合のみ処理する
while cap.isOpened():

# カメラから画像1枚取得
success, image_org = cap.read()
if not success:
print("Ignoring empty camera frame.")
continue

tick = cv2.getTickCount() # fps計算用

# ハンドトラッキング
# 元画像から手の特徴点のみ抽出
image = pose_exec(image_org, pose)

# image = cv2.flip(image, 1) # ミラー反転する

# fps計算し、画像内に埋め込む
fps = cv2.getTickFrequency() / (cv2.getTickCount() - tick)
cv2.putText(
image,
"FPS: " + str(int(fps)),
(image.shape[1] - 150, 40), # 画面右上あたり
cv2.FONT_HERSHEY_PLAIN,
2, # 文字の大きさ
(0, 255, 0), # 文字色(緑)
)

# PC画面に画像表示
cv2.imshow('MediaPipe Face Mesh', image)

#終了判定 ESCで終了する
if cv2.waitKey(5) & 0xFF == 27:
break

cap.release()

Holistic

顔・手・ポーズを総合的にトラッキングします。
Face Mesh・Hands・Poseを組み合わせたようなものです。

画像のときは全身映せませんでしたが、画角におさめればちゃんと追従します。
HolisticのFaceでは、瞳を検出することはできないようです。
また、上半身モードがあるらしい記事もあったのですが、eightの環境ではできませんでした🥺

顔はFace MeshとPoseが重なっています。
手はHansとPoseが重なっています。
この重なり、Poseのほうを非表示にしたいのですが、方法がわかりませんでした😭

Holisticサンプルコード

import cv2
import mediapipe as mp
import numpy as np

# 総合トラッキング関数
def holistic_exec(img, holistic):
img.flags.writeable = False
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# ランドマーク検出
results = holistic.process(img)

img.flags.writeable = True
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)

# 背景を黒にして、ランドマーク画像だけ残す
img_h, img_w, _ = img.shape
blank = np.zeros((img_h, img_w, 3))
img = blank

if results.face_landmarks:
# 検出したランドマークを画像内に描画
mp_drawing.draw_landmarks(
image=img,
landmark_list=results.face_landmarks,
connections=mp_holistic.FACEMESH_CONTOURS,
landmark_drawing_spec=None,
connection_drawing_spec=mp_drawing_styles
.get_default_face_mesh_contours_style())

if results.right_hand_landmarks:
mp_drawing.draw_landmarks(
image=img,
landmark_list=results.right_hand_landmarks,
connections=mp_holistic.HAND_CONNECTIONS,
landmark_drawing_spec=None,
connection_drawing_spec=mp_drawing_styles
.get_default_hand_connections_style())

if results.left_hand_landmarks:
mp_drawing.draw_landmarks(
image=img,
landmark_list=results.left_hand_landmarks,
connections=mp_holistic.HAND_CONNECTIONS,
landmark_drawing_spec=None,
connection_drawing_spec=mp_drawing_styles
.get_default_hand_connections_style())

if results.pose_landmarks:
mp_drawing.draw_landmarks(
image=img,
landmark_list=results.pose_landmarks,
connections=mp_holistic.POSE_CONNECTIONS,
landmark_drawing_spec=mp_drawing_styles
.get_default_pose_landmarks_style())

return img


mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_holistic = mp.solutions.holistic

cap = cv2.VideoCapture(0) # カメラID 内蔵カメラはおそらく0

# static_image_mode = 静止画かどうか
# min_detection_confidence = ランドマーク検出成功判定の閾値
# min_tracking_confidence = ランドマークトラッキング成功判定の閾値
holistic = mp_holistic.Holistic(
static_image_mode=False,
min_detection_confidence=0.5,
min_tracking_confidence=0.5)

# カメラが有効の場合のみ処理する
while cap.isOpened():

# カメラから画像1枚取得
success, image_org = cap.read()
if not success:
print("Ignoring empty camera frame.")
continue

tick = cv2.getTickCount() # fps計算用

# ホリスティックトラッキング
# 元画像から顔の特徴点のみ抽出
image = holistic_exec(image_org, holistic)

# image = cv2.flip(image, 1) # ミラー反転する

# fps計算し、画像内に埋め込む
fps = cv2.getTickFrequency() / (cv2.getTickCount() - tick)
cv2.putText(
image,
"FPS: " + str(int(fps)),
(image.shape[1] - 150, 40), # 画面右上あたり
cv2.FONT_HERSHEY_PLAIN,
2, # 文字の大きさ
(0, 255, 0), # 文字色(緑)
)

# PC画面に画像表示
cv2.imshow('MediaPipe Face Mesh', image)

#終了判定 ESCで終了する
if cv2.waitKey(5) & 0xFF == 27:
break

cap.release()

Segmentation

画像内の人物と背景を分離します。
Web会議のバーチャル背景のような処理ができます。

背景を緑、人物を青で塗りつぶしました。

Segmentationサンプルコード

import cv2
import mediapipe as mp
import numpy as np

# ポーズトラッキング関数
def selfie_segmentation_exec(img, selfie_segmentation):
fg_image = None
bg_image = None

img.flags.writeable = False
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

# 人物と背景を分離
results = selfie_segmentation.process(img)

img.flags.writeable = True
img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)

# 人物のマスク
fg_image = np.zeros(img.shape, dtype=np.uint8)
fg_image[:] = (255, 0, 0)

# 背景のマスク
bg_image = np.zeros(img.shape, dtype=np.uint8)
bg_image[:] = (0, 255, 0)

condition = np.stack((results.segmentation_mask,) * 3, axis=-1) > 0.1

# 第2引数が人物用、第3引数が背景用
img = np.where(condition, fg_image, bg_image)

return img


mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
mp_selfie_segmentation = mp.solutions.selfie_segmentation

cap = cv2.VideoCapture(0) # カメラID 内蔵カメラはおそらく0

# model_selection = 人物
selfie_segmentation = mp_selfie_segmentation.SelfieSegmentation(
model_selection=1)

# カメラが有効の場合のみ処理する
while cap.isOpened():

# カメラから画像1枚取得
success, image_org = cap.read()
if not success:
print("Ignoring empty camera frame.")
continue

tick = cv2.getTickCount() # fps計算用

# ハンドトラッキング
# 元画像から手の特徴点のみ抽出
image = selfie_segmentation_exec(image_org, selfie_segmentation)

# image = cv2.flip(image, 1) # ミラー反転する

# fps計算し、画像内に埋め込む
fps = cv2.getTickFrequency() / (cv2.getTickCount() - tick)
cv2.putText(
image,
"FPS: " + str(int(fps)),
(image.shape[1] - 150, 40), # 画面右上あたり
cv2.FONT_HERSHEY_PLAIN,
2, # 文字の大きさ
(0, 0, 255), # 文字色(赤)
)

# PC画面に画像表示
cv2.imshow('MediaPipe Face Mesh', image)

#終了判定 ESCで終了する
if cv2.waitKey(5) & 0xFF == 27:
break

cap.release()

各機能のFPS比較

各機能において、ある条件下でどの程度のFPSが出るかを計測してみました。

検証環境:
 PC:M4 Macbook Pro
 Pythonバージョン:3.12.5
 OpenCVバージョン:4.11.0
 MediaPipeバージョン:0.10.21

計測条件:
 顔 設定可能な場合は1つ
 手 設定可能な場合は2つ
 ミラー反転無し

結果:

機能名平均FPS
Face Detection140
Face Mesh (TESSELATIONあり)100
Face Mesh (TESSELATIONなし)120
Hands40
Pose50
Holistic35
Segmentation47

 補足
 ミラー反転すると、FPSは落ちました。
 検出対象物が画面外にあったり、隠れていた場合でも、FPSに影響ありませんでした。
 Holisticでは顔+手+ポーズを検出していますが、どれか1つにした場合でもFPSに影響ありませんでした。

所感:
やはりと言うか、検出対象の数が多いほど負荷が高いことがわかりました。
特に、手があるかないかで大きく変わりますね。
Poseは全身をトラッキングしますが、それよりHandsのほうが負荷が高くなっています。Handsのほうが、手指の細かい動きまで捉える(検出対象物が多い)からだと考えられます。
Holisticでは顔や手の細かいトラッキングも可能ですが、全ての検出処理が行われるため、負荷が高くなります。なので、たとえば顔だけトラッキングしたい場合は、Face Meshを使用するほうが良いでしょう。

さいごに

アバターを作ろうとしたときには、総合的にはPoseで実装すると自由度が高そうです。
ただ、eightの場合は瞳のトラッキングもしたいのと、腕部や下半身のトラッキングは不要なので、Face MeshとHandsの組み合わせでやっていこうと思いました。

それでは、今回はここまで。
ありがとうございました😊

コメント

タイトルとURLをコピーしました