シェルスクリプトマガジン

香川大学SLPからお届け!(Vol.81掲載)

著者:永田 歩

今回は、米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