前回の手オブジェクトに続き、今日はポーズ・データをBlenderに取り込んでアーマチュアを作ります。
mediaPipeの頂点番号
オリジナル0〜32に33以降を追加(図の緑の部分)vertsCalcZdepth(h, w, lm)で作成
mp_to_blend.pyにポーズ検知のパートを追加
# mp_to_blend.py
# ========================================================================
# 準備
# ========================================================================
# bpyを外部環境でインポートする時のメッセージを防ぐ
import os, ssl
if not os.path.exists("/run/user/1000/gvfs"):
os.mkdir("/run/user/1000/gvfs")
# Blender内部でmediapipeをインポートする時[SSL: CERTIFICATE_VERIFY_FAILED]を回避する
ssl._create_default_https_context = ssl._create_unverified_context
# ...................................................................
# 必要なモジュールを読み込む
import cv2
import mediapipe as mp
import bpy
# パス、ファイル名
PrjDir = "/Path/to/Project/"
imgName = "Input.webp" # png, jpegも可
imgIn = PrjDir + imgName
# ...................................................................
# MediaPipeのオブジェクト
mp_face_mesh = mp.solutions.face_mesh
mp_hands = mp.solutions.hands
mp_pose = mp.solutions.pose
# 顔の各部を形成する頂点セットが予め用意されている
Face_Dict = { "CONTOURS":mp_face_mesh.FACEMESH_CONTOURS,
"LEFT_EYE":mp_face_mesh.FACEMESH_LEFT_EYE,
"RIGHT_EYE":mp_face_mesh.FACEMESH_RIGHT_EYE,
"LIPS":mp_face_mesh.FACEMESH_LIPS,
"LEFT_EYEBROW":mp_face_mesh.FACEMESH_LEFT_EYEBROW,
"RIGHT_EYEBROW":mp_face_mesh.FACEMESH_RIGHT_EYEBROW,
"TESSELATION":mp_face_mesh.FACEMESH_TESSELATION,
"FACE_OVAL":mp_face_mesh.FACEMESH_FACE_OVAL,
"IRISES":mp_face_mesh.FACEMESH_IRISES,
"LEFT_IRIS":mp_face_mesh.FACEMESH_LEFT_IRIS,
"RIGHT_IRIS":mp_face_mesh.FACEMESH_RIGHT_IRIS,
"NUM_LANDMARKS":mp_face_mesh.FACEMESH_NUM_LANDMARKS,
"NUM_LANDMARKS_WITH_IRISES":mp_face_mesh.FACEMESH_NUM_LANDMARKS_WITH_IRISES
}
hand_conn = mp_hands.HAND_CONNECTIONS
# pose_conn = mp_pose.POSE_CONNECTIONS # Blender用コネクションリストを別途作るので要らない
# ...................................................................
# 画像オブジェクト
img = cv2.imread(imgIn)
h = img.shape[0]
w = img.shape[1]
# ========================================================================
# サブ
# ========================================================================
def vertsCalc(h, w, lm):
'''
画像の高さ・幅とMediaPipeのランドマークデータから
各頂点の座標を求める
'''
vList = []
for i in lm:
V = str(i).split("\n")
Vx = float(V[0].split(": ")[1]) * w * 0.001
Vy = float(V[1].split(": ")[1]) * h * 0.001
Vz = float(V[2].split(": ")[1]) * w * 0.001
#vList.append((Vx, Vy, Vz))
vList.append((Vx, Vz, Vy*-1)) # Blenderではzが上、-yが前
return vList
def vertsCalcZdepth(h, w, lm):
'''
MediaPipeのデータに33以降を追加
Pose用頂点計算: 1) 奥行きに定数を乗算して浅くする
2) 既存の線分の中点を計算し、体の中心線が引けるようにする
'''
z_depth = 0.15
vList, vAdd = [], []
Counter = 0
for i in lm:
V = str(i).split("\n")
Vx = float(V[0].split(": ")[1]) * w * 0.001
Vy = float(V[1].split(": ")[1]) * h * 0.001
Vz = float(V[2].split(": ")[1]) * w * 0.001 * z_depth
vList.append((Vx, Vz, Vy*-1)) # zが上、-yが前
if Counter in [9,10,11,12,23,24]:
vAdd.append((Vx,Vy,Vz))
Counter += 1
# 33〜35追加
# 33=23,24の中点 34=11,12の中点 35=9,10の中点
V33x = (vAdd[4][0] + vAdd[5][0]) / 2
V33y = (vAdd[4][1] + vAdd[5][1]) / 2
V33z = (vAdd[4][2] + vAdd[5][2]) / 2
V33 = (V33x, V33z, V33y*-1)
V34x = (vAdd[2][0] + vAdd[3][0]) / 2
V34y = (vAdd[2][1] + vAdd[3][1]) / 2
V34z = (vAdd[2][2] + vAdd[3][2]) / 2
V34 = (V34x, V34z, V34y*-1)
V35x = (vAdd[0][0] + vAdd[1][0]) / 2
V35y = (vAdd[0][1] + vAdd[1][1]) / 2
V35z = (vAdd[0][2] + vAdd[1][2]) / 2
V35 = (V35x, V35z, V35y*-1)
vList.append(V33)
vList.append(V34)
vList.append(V35)
# 36, 37 (Stomach, Chest)
V36x = (V33x + V34x) / 2
V36y = (V33y + V34y) / 2
V36z = (V33z + V34z) / 2
V36 = (V36x, V36z, V36y*-1)
vList.append(V36)
V37x = (V33x + V36x) / 2
V37y = (V33y + V36y) / 2
V37z = (V33z + V36z) / 2
V37 = (V37x, V37z, V37y*-1)
vList.append(V37)
return vList
def edgeData(mpConn):
'''
MediaPipeで用意されているコネクションの頂点ペア
'''
pairList = []
for i in mpConn:
pairList.append(i)
return pairList
def edgeDataPose():
'''
PoseランドマークからArmatureを作るための頂点ペア
'''
Pelvis = (33, 37)
Stomach = (37, 36)
Chest = (36, 34)
Neck = (34, 35)
Head = (35, 0)
Clavicle_L = (34, 11)
Arm_L = (11, 13)
Forearm_L = (13, 15)
Hand_L = (15, 19)
Clavicle_R = (34, 12)
Arm_R = (12, 14)
Forearm_R = (14, 16)
Hand_R = (16, 20)
P_L = (33, 23)
Thigh_L = (23, 25)
Calf_L = (25, 27)
Foot_L = (27, 31)
P_R = (33, 24)
Thigh_R = (24, 26)
Calf_R = (26, 28)
Foot_R = (28, 32)
pairList = [Pelvis, Stomach, Chest, Neck, Head,
Clavicle_L, Arm_L, Forearm_L, Hand_L,
Clavicle_R, Arm_R, Forearm_R, Hand_R,
P_L, Thigh_L, Calf_L, Foot_L,
P_R, Thigh_R, Calf_R, Foot_R]
return pairList
# ========================================================================
# メイン
# ========================================================================
# 顔 ................................................................
with mp_face_mesh.FaceMesh(
static_image_mode = True,
max_num_faces = 1,
refine_landmarks = True,
min_detection_confidence = 0.5 ) as Face:
results = Face.process(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
lm_detect = bool(results.multi_face_landmarks)
if lm_detect:
Face_verts = vertsCalc(h, w, results.multi_face_landmarks[0].landmark)
Face_edges = edgeData(Face_Dict["TESSELATION"])
FaceMesh = bpy.data.meshes.new("face")
FaceMesh.from_pydata(Face_verts, Face_edges, [])
FaceMesh.validate()
FaceMesh.update()
ob = bpy.data.objects.new("Face", FaceMesh)
scene = bpy.context.scene
scene.collection.objects.link(ob)
# 手 ................................................................
with mp_hands.Hands(
static_image_mode = True,
max_num_hands = 2,
min_detection_confidence = 0.5) as Hands:
results = Hands.process(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
lm_detect = bool(results.multi_hand_landmarks)
if lm_detect:
# 両手を認識した場合、オブジェクトを2つ作成する(手0・手1)
for hand_No, hand_landmarks in enumerate(results.multi_hand_landmarks):
Hand_verts = vertsCalc(h, w, results.multi_hand_landmarks[hand_No].landmark)
Hand_edges = edgeData(hand_conn)
HandMesh = bpy.data.meshes.new("hand"+str(hand_No))
HandMesh.from_pydata(Hand_verts, Hand_edges, [])
HandMesh.validate()
HandMesh.update()
ob = bpy.data.objects.new("Hand"+str(hand_No), HandMesh)
scene = bpy.context.scene
scene.collection.objects.link(ob)
# 全身 ..............................................................
with mp_pose.Pose(
static_image_mode = True,
model_complexity = 2,
min_detection_confidence = 0.5) as Pose:
results = Pose.process(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
lm_detect = bool(results.pose_landmarks)
if lm_detect:
# メッシュデータ
Pose_verts = vertsCalcZdepth(h, w, results.pose_landmarks.landmark)
Pose_edges = edgeDataPose()
PoseMesh = bpy.data.meshes.new("pose")
PoseMesh.from_pydata(Pose_verts, Pose_edges, [])
PoseMesh.validate()
PoseMesh.update()
# オブジェクトに収納
ob = bpy.data.objects.new("Pose", PoseMesh)
scene = bpy.context.scene
scene.collection.objects.link(ob)
# PoseオブジェクトからSkinモディファイア経由でArmatureを作る
ob.select_set(True)
bpy.context.view_layer.objects.active = ob
bpy.ops.object.editmode_toggle()
bpy.ops.mesh.select_all(action="DESELECT")
# Pelvisの始点をルートに指定
import bmesh
bm = bmesh.from_edit_mesh(ob.data)
bm.verts.ensure_lookup_table()
bm.verts[33].select = True
bpy.ops.object.skin_root_mark()
bpy.ops.object.editmode_toggle()
# Skinモディファイアを適用 → Armatureを作成
bpy.ops.object.modifier_add(type='SKIN')
bpy.ops.object.skin_armature_create(modifier="Skin")
# 変形したPoseオブジェクトを削除
bpy.data.objects.remove(ob)
# Pelvis(Bone.00)から奥に向かってできたボーン('Bone')と
# P_L, P_R('Bone.13','Bone.17')を削除
Arm = bpy.data.objects['Armature']
bpy.ops.object.editmode_toggle()
bpy.ops.armature.select_all(action='DESELECT')
Arm.data.edit_bones.remove(Arm.data.edit_bones['Bone'])
Arm.data.edit_bones.remove(Arm.data.edit_bones['Bone.13'])
Arm.data.edit_bones.remove(Arm.data.edit_bones['Bone.17'])
# Thigh_L, Thigh_R(Bone.14, Bone.18)をPelvis(Bone.00)の子に設定
Arm.data.edit_bones['Bone.14'].parent = Arm.data.edit_bones['Bone.00']
Arm.data.edit_bones['Bone.18'].parent = Arm.data.edit_bones['Bone.00']
# 各ボーンの名前を番号からわかり易いものに変える
# (CascadeurのQUICK RIGGING TOOLに合わせて名前付け)
Arm.data.edit_bones['Bone.00'].name = 'Pelvis'
Arm.data.edit_bones['Bone.01'].name = 'Stomach'
Arm.data.edit_bones['Bone.02'].name = 'Chest'
Arm.data.edit_bones['Bone.03'].name = 'Neck'
Arm.data.edit_bones['Bone.04'].name = 'Head'
Arm.data.edit_bones['Bone.05'].name = 'Clavicle_L'
Arm.data.edit_bones['Bone.06'].name = 'Arm_L'
Arm.data.edit_bones['Bone.07'].name = 'Forearm_L'
Arm.data.edit_bones['Bone.08'].name = 'Hand_L'
Arm.data.edit_bones['Bone.09'].name = 'Clavicle_R'
Arm.data.edit_bones['Bone.10'].name = 'Arm_R'
Arm.data.edit_bones['Bone.11'].name = 'Forearm_R'
Arm.data.edit_bones['Bone.12'].name = 'Hand_R'
Arm.data.edit_bones['Bone.14'].name = 'Thigh_L'
Arm.data.edit_bones['Bone.15'].name = 'Calf_L'
Arm.data.edit_bones['Bone.16'].name = 'Foot_L'
Arm.data.edit_bones['Bone.18'].name = 'Thigh_R'
Arm.data.edit_bones['Bone.19'].name = 'Calf_R'
Arm.data.edit_bones['Bone.20'].name = 'Foot_R'
# オブジェクト・モードに戻る
bpy.ops.object.editmode_toggle()
# ボーンの形状を八面体へ
bpy.context.object.data.display_type = 'OCTAHEDRAL'
# 原点をジオメトリに移動
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS')
確認
Pelvisがルートボーンになっているアーマチュアができた。
次回は難関アニメーションの取り込みです。この記事を書いている時点(2023.02.25)ではオイラー角やクォータニオン、Blenderの骨の動く仕組みがよくわかっていないので、完成は遠いです…。
でも悪戦苦闘を記録しておくのも何かの足しになると思うので、出来上がっているところまでのコードと動作結果を載せたいと思います。