TadaoYamaokaの日記

山岡忠夫 Home で公開しているプログラムの開発ネタを中心に書いていきます。

3Dモデルから学習データを生成する

以前よりDCNNによるマンガキャラクターの顔パーツ検出に取り組んでいるが、学習データを手作業で準備するのが大変なので自動化できる方法を模索している。

写真を線画化するする方法や、Free-Form Deformationを使う方法を試しているが、ここでは3Dモデルから2Dの学習データを生成する方法を試してみる。

3Dモデルを使用して、ランダムに顔の向きに回転を加えて2D画像を生成するれば、1つの3Dモデルから異なる学習データを大量に生成できる。
顔パーツの検出点も手動で入力しなくても回転元の座標から計算で求めることができる。

マンガの顔の表現は3Dモデルの回転とは表現が異なるため、Free-Form Deformationによる方法の方がよりマンガに適した変形を加えることができると思うが、デフォーマーを手動で作成するのは大変である。
一旦3Dモデルで顔の向きを変えてそこからデフォーマーを学習するとデフォーマーを手動で作成しなくてよくなると考えている。

使用する3Dモデルデータ

元となる3Dモデルを一から作成するにはモデリングのスキルが全くないため、フリーで公開されている3Dモデルを使用することにする。
そこで、MMD初音ミクのモデルを使用することにした。

モデリングソフト

3Dモデルに回転を加えて2D画像を生成する過程を自動的に行いたいので、Pythonによるコントロールが可能なBlenderを使用して生成を行う。

BlenderMMDのモデルを取り込むプラグイン(mmd_tools)が公開されているのでそれを使用してBlenderMMD初音ミクのモデルを取り込んだ。

さて、そこから2D画像を生成したいのだが、Blenderは元より3Dモデリングの知識がほとんどないため、以下のことを順番に模索しながら行っていきたいと思う。

  • カメラを顔の中心に向ける
  • ライトを正面向きの平行光源にする
  • トゥーンレンダリング(セルシェーディング)を行う
  • 顔の向きを変える
  • レンダリングした画像を保存する

BlenderPythonを使用する

BlenderPythonを使用するには画面上部のScreen LayoutをScriptingに変更する。
コンソールが表示され、Pythonを対話形式で使用可能になる。

f:id:TadaoYamaoka:20170325225512p:plain

カメラを顔の中心に向ける

MMD初音ミクのモデルを読み込んだ後、顔の中心にカメラを向ける。
読み込んだ初音ミクのモデルは、足元が原点となっており、正面が負のY軸方法で、頭の方向が正のZ軸方向となっている。

メッシュが体のパーツごとにグループ分けされており、顔の中心はグループ「目.L」と「目.R」の中心から求めることができる。

カメラは正面から顔に向けるので、x, y座標は0で、z座標を目の中心の位置に合わせる。

目の中心のz座標は以下のようにして求める。

eyes_index = [bpy.data.objects['初音ミク_mesh'].vertex_groups['目.L'].index, bpy.data.objects['初音ミク_mesh'].vertex_groups['目.R'].index]

eyes_z = []
for v in bpy.data.objects['初音ミク_mesh'].data.vertices:
    for g in v.groups:
        if g.group in eyes_index:
            eyes_z.append(v.co.z)

eyes_center_z = min(eyes_z) + (max(eyes_z) - min(eyes_z)) / 2

上記コードはモデルの全メッシュの頂点に振られたグループ番号が目のグループ番号と一致するものを抜き出し、そのZ座標の中間の値を求めている。


カメラの位置を上記の目の中心のZ座標に配置して、顔の正面に向けるには、

camera = bpy.data.objects['Camera']
camera.location = [0, -1.5, eyes_center_z]
camera.rotation_euler = [pi/2, 0, 0]

camera.locationで、カメラの座標を指定して、camera.rotation_eulerでカメラの向きを指定している。

カメラの座標は、顔の目の高さに合わせて、少しY軸方向に退いた位置に配置している。

カメラの向きは、オイラー角をラジアン単位で指定する。
カメラの向きは、[0, 0, 0]の時、負のZ軸方向で、スクリーンの上側が正のY軸方向を向いているので、X軸方向に90°回転することで、顔の正面に向けている。

f:id:TadaoYamaoka:20170325222138p:plain

ライトを正面向きの平行光源にする

ライトを正面向き(正のY軸方向)に向けて、光源の種類を平行光源にする。

lamp_obj = bpy.data.objects['Lamp']
lamp_obj.location = [0, -8, 0]

lamp_obj.rotation_euler = [pi/2, 0, 0]

lamp = bpy.data.lamps['Lamp']
lamp.type = 'SUN'

lamp_obj.locationでライトの座標を正面から少し退いた位置に配置している。
lamp_obj.rotation_eulerで、向きを顔の正面に向けている。
lamp.typeで、光源の種類を平行光源(SUN)に指定している。

レンダリングを行う

一旦ここで、レンダリングの結果を確かめてみる。
学習データとして顔の領域を100px×100pxに切り出すので、それよりも少し広い150px×150pxの解像度でレンダリングを行う。
レンダリングの解像度は、Python側でのコードが見つけられなかったのでBlenderのUIで設定を行った。
※2017/3/30追記
Pythonで設定するには以下のように実行する。

scene = bpy.data.scenes['Scene']
scene.render.resolution_x = 150
scene.render.resolution_y = 150

F12キーを押して、レンダリングを行うと以下のようになる。
f:id:TadaoYamaoka:20170325223328p:plain

陰影が付いており、そのままではマンガの学習データとしては適していない。
次回以降で、トゥーンレンダリング(セルシェーディング)を行い、マンガの学習データとして使用できる方法を試す予定。