LoginSignup
11
9

More than 3 years have passed since last update.

定番の顔メッシュ貼り替えをWeb+facemeshで実装してみた

Last updated at Posted at 2020-06-01

顔メッシュ貼り替えといえばWWDC2017でAppleが発表したFace Trackingが有名ですが、今年の3月にGoogleが公開したfacemeshでも似たようなことができます。
WWDC2017から結構時間が経ったことで顔メッシュ貼り替えは枯れたネタではありますが、今回はThree.jsとfacemeshを組み合わせて顔メッシュ貼り替えを試しました。

昨夜作り始めて割とさくっとできました。
実装ではcanvasの画像から顔メッシュを自動生成しまして、カメラに写った人物の顔の座標に顔メッシュを適用しています。

Three.jsでの顔メッシュ生成

実装のコア部分となるThree.jsの顔メッシュですが、facemeshの推論の戻り値と、TRIANGULATIONの頂点を混ぜ合わせると顔のメッシュは作れます。

// facemeshの戻り値
var keypoints = prediction.scaledMesh;

var texture = new THREE.CanvasTexture(canvas);
texture.flipY = false;

mesh.geometry.vertices = new Array(keypoints.length);
for (let i = 0; i < keypoints.length; i++) {
    const [x,y,z] = keypoints[i];
    mesh.geometry.vertices[i] = new THREE.Vector3(x,y,z);
}

mesh.geometry.faces = new Array(TRIANGULATION.length / 3);
for (let i = 0; i < TRIANGULATION.length / 3; i++) {

    let id0 = TRIANGULATION[i*3+0];
    let id1 = TRIANGULATION[i*3+1];
    let id2 = TRIANGULATION[i*3+2];
    mesh.geometry.faces[i] = new THREE.Face3(id0,id1,id2);

    // uvはcanvasのサイズに合わせて正規化
    let uv = [
    new THREE.Vector2(keypoints[id0][0] / videoWidth, keypoints[id0][1] / videoHeight),
    new THREE.Vector2(keypoints[id1][0] / videoWidth, keypoints[id1][1] / videoHeight),
    new THREE.Vector2(keypoints[id2][0] / videoWidth, keypoints[id2][1] / videoHeight),
    ];
    mesh.geometry.faceVertexUvs[0][i] = uv;
}
mesh.material = new THREE.MeshBasicMaterial({ map: texture , 
                                                side: THREE.DoubleSide });

顔の部分を推論

face.png
画像のようにAIが推論した顔メッシュの座標を取得し、UV座標を抽出します。

オンラインデモ

コード

顔メッシュ生成をBufferGeometryに書き直す

冒頭のThree.jsのGeometryの結果をBufferGeometryに変換して読み解きます。
bufgeom = new THREE.BufferGeometry().fromGeometry(geom);
変換結果を見ると、facesとverticesをまとめてpositionに書くことが解りました。
何故そうなるのか調べたところ、こちらを読むと、positionはWebGLでの頂点形式とのこと。
また、Geometryは、処理時にBufferGeometryに置き換えてから実行しているそうです。
facesとverticesを分けて書くのはユーザーフレンドリーなGeometryの仕様と覚えておくと良さそうです。


let pos = new Float32Array(
  Array.prototype.concat.apply([],TRIANGULATION.map(index => keypoints[index])));

let uv = new Float32Array(
  Array.prototype.concat.apply([],
    TRIANGULATION.map(index => 
      [keypoints[index][0] / modelWidth, keypoints[index][1] / modelHeight])));

mesh.geometry.setAttribute('position', new THREE.BufferAttribute(pos, 3));
mesh.geometry.setAttribute('uv', new THREE.BufferAttribute(uv, 2));

mesh.geometry.attributes.position.needsUpdate = true;

その他

よろしければこちらも合わせてご覧ください。
TensorFlow.jsのfacemeshで顔向き推定を試してみた その2

References

11
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9