著者:永田 歩
今回は、米Google社が提供するメディアデータ向けの機械学習ライブラリ「MediaPiPe」を用いて、動画内にある人の顔を検出し、そのデータを基に3D CGアニメーションを生成するプログラムを紹介します。AIを3D CGに利用する試みの一つとして参考にしてください。
シェルスクリプトマガジン Vol.81は以下のリンク先でご購入できます。
図1 動画をフレームに分割するプログラム「video2img.py」のコード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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」のメイン部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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()関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
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()関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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()関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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」のメイン部分
1 2 3 4 5 6 7 8 9 |
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()関数の定義コード
1 2 3 4 5 6 7 8 9 |
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()関数の定義コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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 |