face-api.jsを使って「トラニナル」

#はじめに
昨年の年末に「うちの会社っぽい年賀状なんかない？」ということで
「”顔”になんやかんやできる年賀状」を作ってみよう！と、face-api.jsを使ってみました

#face-api.jsってなぁに
ブラウザで顔検出と顔認識ができるJavaScript APIです
機械学習用のライブラリTensorFlow.jsが利用されています
[face-api.js](https://justadudewhohacks.github.io/face-api.js/docs/index.html)

* 顔検出：face detection、人の顔を自動的に見つける
* 顔認識：face recognition、個人を識別する

です
今回は「顔検出」の範囲になります

#face-api.jsを使う準備
<b>Getting Started</b>の記述を参考に作業します

* dist /face-api.jsの最新のスクリプトを含める
または、npm経由でインストール

と記載があったので、前者を選択しました
[GitHub](https://github.com/justadudewhohacks/face-api.js) からリポジトリをクローンしてdistディレクトリを自分のプロジェクトにコピーしました
分かりやすいようにディレクトリ名はdist⇒jsに変更しました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/913784/cfbece1e-2de9-4ecc-8777-84218f74ced0.png)


* モデルの読み込み

顔検出や顔認識を行うためには、「顔モデル」を読み込む必要があります
モデルのロードに必要なmanifestファイルとshardsファイルが提供されているので
こちらもクローンしてきたリポジトリ内からweightsディレクトリを自分のプロジェクトにコピーしました
分かりやすいようにディレクトリ名はweights⇒modelsに変更しました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/913784/c95f1234-80d4-ba83-8029-e980d9f6a2fb.png)

#使ってみる

* まず、顔モデルを読み込みます。

今回「顔を検出」して「顔のパーツを識別して」その上に「画像を乗っけ」よう、と考えたので
以下の2つのモデルを使いました
顔を検出：faceapi.nets.ssdMobilenetv1
顔パーツの検出：faceapi.nets.faceLandmark68Net

```javascript
faceapi.nets.ssdMobilenetv1.load('./models'),
faceapi.nets.faceLandmark68Net.load('./models'),
```

* 次に、顔パーツの検出を実行します

顔のパーツを認識するために、顔ランドマークと呼ばれる顔の特徴点を検出して利用します
（画像は[face-api.js](https://justadudewhohacks.github.io/face-api.js/docs/index.html)から引用）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/913784/b5f4b574-6302-c675-03b6-5a9b4677712a.png)

今回は、「映っている中で最も信頼度の高い顔を検出する」detectSingleFaceを使用しました

```javascript
const detection = await faceapi.detectSingleFace(input)
```

顔検出後に、withFaceLandmarksを実行することで検出された各顔の顔のランドマークを予測できます

```javascript
const detectionWithLandmarks = await faceapi.detectSingleFace(input).withFaceLandmarks()
```

顔ランドマークの座標を取得できます

```javascript
const landmarkPositions = landmarks.positions

const nose = landmarks.getNose()
const mouth = landmarks.getMouth()
const leftEye = landmarks.getLeftEye()
const rightEye = landmarks.getRightEye()
const leftEyeBbrow = landmarks.getLeftEyeBrow()
const rightEyeBrow = landmarks.getRightEyeBrow()
```

landmarkPositionsには68個の（x,y)座標が含まれています
getnose()
getMouth()
等をつかうと、パーツの座標が取れますが
今回は上に画像を重畳するのが目的なので使用せず、
「右頬（向かって左頬）の特徴点を起点に頬画像パーツを乗せる」
というような計算をしました

```javascript
function drawStamp(){
   ctx.clearRect(0, 0, cvs.width, cvs.height);
   let positions = detectionWithLandmarks.landmarks.positions;

   // スタンプの計算例です
   let cheekRight = 2;
   let cheekLeft = 14;
   let faceWidth = positions[cheekLeft].x - positions[cheekRight].x;
   let dw = stampCheek.width * faceWidth / stampCheek.width;
   let dh = stampCheek.height * faceWidth / stampCheek.width;
   let dx = positions[cheekRight].x;
   let dy = positions[cheekRight].y - dh / 2;
   ctx.drawImage(stampCheek, dx, dy, dw, dh);
}
```

#トラになった！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/913784/0fbaf6ed-3b9c-2721-30e8-0520978f29be.png)
色んなパーツを乗せてみました
これは静止画ですが、顔検出のソースをvideoにすることもできます

#おわりに
こんなんできるんじゃ？とプロトタイプを実装したのち
デザイナーさんに素敵なデザインをつけてもらいAR年賀状としてリリースしました

https://m-gild.com/ar/nenga/2022/

* 顔検出が動くまでが遅い（重い）
* １人しかトラになれない
* 横画面非対応

等…
まだまだ課題はありますが
face-api.jsの顔モデルを変更したら改善できる部分もあるのかな？
と思っています

時間を見つけて試していきたいです