著者:永田 歩
今回は、米Google社が提供するメディアデータ向けの機械学習ライブラリ「MediaPiPe」を用いて、動画内にある人の顔を検出し、そのデータを基に3D CGアニメーションを生成するプログラムを紹介します。AIを3D CGに利用する試みの一つとして参考にしてください。
シェルスクリプトマガジン Vol.81は以下のリンク先でご購入できます。![]()
![]()
図1 動画をフレームに分割するプログラム「video2img.py」のコード
import cv2 as cv
import os
video_path = './video/test_video.mp4'
dir_path = './image'
basename = 'img_frame'
ext = 'jpg'
# 動画を読み込んで存在しなければ終了
cap = cv.VideoCapture(video_path)
if not cap.isOpened():
exit()
os.makedirs(dir_path, exist_ok=True)
base_path = os.path.join(dir_path, basename)
# 画像保存名のためにフレームの桁数をカウント
digit = len(str(int(cap.get(cv.CAP_PROP_FRAME_COUNT))))
n = 0
while True:
ret, frame = cap.read()
if ret:
# 画像の保存
cv.imwrite('{}_{}.{}'.format(base_path, str(n).zfill(digit), ext), frame)
n += 1
else:
exit()
図2 顔のランドマーク座標を出力するコード「images2npy.py」のメイン部分
import cv2 as cv
import mediapipe as mp
import os
import glob
import numpy as np
# 使用する画像のパス名を取得してリスト化
resource_dir = r'./image'
file_list = glob.glob(os.path.join(resource_dir, "*.jpg"))
xyzval = []
for file_path in file_list:
# 画像を読み込む
image = cv.imread(file_path)
xyzval.append(get_landmark(image, file_path))
# 3次元numpy配列のバイナリファイルとして保存
np.save('result/np_save', xyzval)
図3 get_landmark()関数の定義コード
def get_landmark(image, file_path):
# 顔のランドマークを取得する準備
mp_face_mesh = mp.solutions.face_mesh
face_mesh = mp_face_mesh.FaceMesh(
static_image_mode=True,
max_num_faces=1,
min_detection_confidence=0.5)
rgb_image = cv.cvtColor(image, cv.COLOR_BGR2RGB)
# ランドマーク検出
results = face_mesh.process(rgb_image)
# 顔が検出されなければ終了
if not results.multi_face_landmarks:
exit()
face_landmarks = results.multi_face_landmarks[0]
file_path = os.path.splitext(file_path)[0]
file_name = file_path.split('/')[-1]
# 保存先のフォルダ作成
result_txt_dir = 'result/txt'
os.makedirs(result_txt_dir, exist_ok=True)
txtfile_path = result_txt_dir+'/'+file_name+'.txt'
# テキストファイルにRawデータを書き込む
f = open(txtfile_path, 'w')
f.write(str(face_landmarks))
f.close()
make_landmarkimg(image, face_landmarks,
file_name, mp_face_mesh)
face_mesh.close()
return get_original_scalexyz(image, txtfile_path)
図5 get_original_scalexyz()関数の定義コード
def get_original_scalexyz(image, txtfile_path):
height, width = image.shape[:2]
vers = []
i = 0
with open(txtfile_path) as ft:
lines = ft.read()
for l in lines.split("\n"):
if i%5 == 0 or i%5 == 4:
i += 1
continue
elif i%5 == 1:
tmp = l.split()
x = f'{float(tmp[1]) *width:.5f}'
elif i%5 == 2:
tmp = l.split()
y = f'{float(tmp[1]) *height:.5f}'
else:
tmp = l.split()
z = f'{float(tmp[1]) *width:.5f}'
vers.append([float(x), float(y), float(z)])
i += 1
return vers
図6 make_landmarkimg()関数の定義コード
def make_landmarkimg(image, face_landmarks, file_name, mp_face_mesh):
result_image_dir = 'result/image'
os.makedirs(result_image_dir, exist_ok=True)
# 顔のランドマークを画像上に出力する準備
mp_drawing = mp.solutions.drawing_utils
drawing_spec = mp_drawing.DrawingSpec(thickness=1, circle_radius=1)
# 元画像のコピーを作成
annotated_image = image.copy()
# 顔のランドマークを描画した画像を生成
mp_drawing.draw_landmarks(
image=annotated_image,
landmark_list=face_landmarks,
connections=mp_face_mesh.FACEMESH_TESSELATION,
landmark_drawing_spec=drawing_spec,
connection_drawing_spec=drawing_spec)
cv.imwrite(result_image_dir+'/'+file_name+'.png', annotated_image)
図8 3D CGアニメーションを作成するコード「npy2blender.py」のメイン部分
import numpy as np
import bpy
filepath = r'3次元numpy配列のバイナリファイルのパス名'
# 例 → r'C:/Users/user1/python/shellmag/result/np_save.npy'
xyzval = np.load(filepath)
ver_count = len(xyzval[0])
frame_count = len(xyzval)
generate_ver(ver_count)
move_ver_anyframe(xyzval, frame_count)
図9 generate_ver()関数の定義コード
def generate_ver(ver_count):
# 点群オブジェクトの生成
bpy.data.meshes.new(name='shellmag_Mesh')
bpy.data.objects.new(name='shellmag_Obj',
object_data=bpy.data.meshes['shellmag_Mesh'])
bpy.context.scene.collection.objects.link(bpy.data.objects['shellmag_Obj'])
Obj = bpy.data.objects['shellmag_Obj'].data
Obj.vertices.add(ver_count)
return
図10 move_ver_anyframe()関数の定義コード
def move_ver_anyframe(xyzval, frame_count):
ob = bpy.data.objects['shellmag_Obj'].data
for frame_num in range(frame_count):
i = 0
# XYZ座標を各点ごとに定義してフレームとして保存
for p in xyzval[frame_num]:
X = p[0]
Y = p[1]
Z = p[2]
ver = ob.vertices[i]
ver.co.x = X
ver.co.y = Y
ver.co.z = Z
i += 1
ver.keyframe_insert('co',index = -1,frame = frame_num)
return