顔メッシュ貼り替えといえばWWDC2017でAppleが発表したFace Trackingが有名ですが、今年の3月にGoogleが公開したfacemeshでも似たようなことができます。
WWDC2017から結構時間が経ったことで顔メッシュ貼り替えは枯れたネタではありますが、今回はThree.jsとfacemeshを組み合わせて顔メッシュ貼り替えを試しました。
昨夜作り始めて割とさくっとできました。
実装ではcanvasの画像から顔メッシュを自動生成しまして、カメラに写った人物の顔の座標に顔メッシュを適用しています。
誰得ではありますがイラスト屋さんの顔になれます。https://t.co/BNmTQ1wa5O#facemesh pic.twitter.com/wyTayzUfO7
— o2 (@mb_otsu) June 1, 2020
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 });
顔の部分を推論
画像のように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