LoginSignup
28
26

More than 1 year has passed since last update.

face-api.jsで顔認識

Last updated at Posted at 2022-05-27

この記事でやること

今回はface-api.jsで使ってみたかった「顔認識」を試してみます!

face-api.jsとは

face-api.jsはブラウザで顔検出(face detection:人の顔を自動的に見つける)と
顔認識(face recognition:個人を識別する)ができるJavaScript APIです。
機械学習用のライブラリTensorFlow.jsが利用されています。
face-api.js

・顔検出
・顔認識
・表情検出
・年齢性別推定
ができます。

顔認識(face recognition)とは

wikipediaには

カメラのデジタル画像から、人を自動的に識別するためのコンピュータシステムである。

と書かれています。
この記事では画像に映っている人が「誰か」を判断するものとして話を進めていきます。

顔認識のやり方

Face Recognition by Matching Descriptors

顔認識を実行するには、faceapi.FaceMatcherを使用して、face descriptors(顔記述子)を比較する。と書いてありました。
学習用の参照画像を用意して顔を検出し、検出された顔記述子と、顔認識を行いたい画像の顔記述子を照合するようです。

顔認識のチュートリアル

こちらを参考に進めていこうと思います。

モデルの読み込み

Promise.all([
   faceapi.loadSsdMobilenetv1Model('./models'),
   faceapi.loadFaceLandmarkModel('./models'),
   faceapi.loadFaceRecognitionModel('/models'), // 顔認識モデル
]).then(()=>{
   start();
});

必要な顔モデルを読み込みます。
顔認識を行うのでFaceRecognitionModelと
FaceRecognitionModelを使うために必要なSsdMobilenetv1Model、FaceLandmarkModelの3つのモデルを読み込みます。
すべてのモデルの読み込みが成功したら、startメソッドを呼ぶようにしています

認識を行う画像を表示

画像はフリー写真素材ぱくたそ からお借りしました。
image.png

ボタンを押したら、顔検出するようにしておきます。

document.getElementById('detect_btn').onclick = () => {
   detectFace();
};

async function detectFace(){
   const img = document.getElementById('result_image');
   const faceData = await faceapi.detectAllFaces(img);
   if(!faceData.length) return;
   faceapi.draw.drawDetections(resultCvs, faceData);
}

image.png
検出できています◎

参照データの読み込み

参照画像を用意します。
今回は1枚ずつですが、1人に対して複数枚の用意しておくと精度がアップするようです。
image.png
適当なラベル付で恐縮ですが、左から

const labels = ['taro', 'jiro', 'saburo'];

とさせていただきました。

function start(){
   document.getElementById('msg1').innerHTML = "顔モデルを読み込みました<br>参照顔データを読み込んでいます…";
   loadRefImage();
}

const labels = ['taro', 'jiro', 'saburo'];
const refFaceData = new Array(labels.length);

async function loadRefImage() {
   let msgStr = "";
   for (count = 0; count < labels.length; count++) {
      const img = new Image();
      img.src = `./src/${labels[count]}.jpg`;
      refFaceData[count] = await faceapi.detectSingleFace(img).withFaceLandmarks().withFaceDescriptor();
      if (!refFaceData[count]) {
         refFaceData[count] = 'no face';
         msgStr += `<li id="no-face">${labels[count]}さんの顔は見つかりませんでした</font>`;
      }
      else{
         msgStr += `<li>${labels[count]}さんの顔が見つかりました`;
      }
   }
   document.getElementById('msg1').innerHTML = `参照顔データを読み込みました<br>`;
   document.getElementById('result').innerHTML = msgStr;
}

躓きポイントですが、
実はtaroさんの参照画像は「顔が検出できなかった」んです。(目をつむってるからかな)

そのことに気が付くまで、なかなかチュートリアルのようにはいかず、???となっていました…
参照画像で顔が検出されない場合も想定したコードへ変更したところ動くようになりました。

image.png

これでjiroさんとsaburoさんの参照顔データ(顔記述子)の取得ができたので次のステップに進めます!

FaceMatcherの初期化

いよいよFaceMatcherを使います。

face-api.jsより引用
image.png

上記のように、そのままresultsを渡せばOKです。

・taroさんが認識できていない
・labelを任意の値にしたい
という事情があるので、今回は一工夫入れます

loadRefImageメソッド内でラベル付きの参照記述子を作成し、処理終了時に返すようにします。

+let refLabeledDescriptors;
function start() {
   document.getElementById('msg1').innerHTML = "顔モデルを読み込みました<br>参照顔データを読み込んでいます…";
-   loadRefImage();
+   loadRefImage().then((result) => {
+      refLabeledFaceDescriptors = result;
+   });
}

const labels = ['taro', 'jiro', 'saburo'];
const refFaceData = new Array(labels.length);

async function loadRefImage() {
   let msgStr = "";
+ const labeledDescriptors = [];
   for (count = 0; count < labels.length; count++) {
      const img = new Image();
      img.src = `./src/${labels[count]}.jpg`;
      refFaceData[count] = await faceapi.detectSingleFace(img).withFaceLandmarks().withFaceDescriptor();
      if (!refFaceData[count]) {
         refFaceData[count] = 'no face';
         msgStr += `<li id="no-face">${labels[count]}さんの顔は見つかりませんでした</font>`;
      }
      else{
         msgStr += `<li>${labels[count]}さんの顔が見つかりました`;
+        labeledDescriptors.push(
+           new faceapi.LabeledFaceDescriptors(
+              labels[count],
+              [refFaceData[count].descriptor]
+        ));
      }
   }
   document.getElementById('msg1').innerHTML = `参照顔データを読み込みました<br>`;
   document.getElementById('result').innerHTML = msgStr;
+  return labeledDescriptors;
}

FaceMatcherは「顔認識」ボタンが押されたとき、すなわちdetectFaceメソッドの中で初期化しました。

async function detectFace(){
   const img = document.getElementById('result_image');
   const faceData = await faceapi.detectAllFaces(img);
   if(!faceData.length) return;
   faceapi.draw.drawDetections(resultCvs, faceData);
+  const faceMatcher = new faceapi.FaceMatcher(refLabeledFaceDescriptors, 0.6);
}

FaceMatcherのコンストラクタの第2引数は、チュートリアルに倣って0.6としました。

顔記述子の比較

いよいよ比較していきます。

async function detectFace() {
   const img = document.getElementById('result_image');
-  const faceData = await faceapi.detectAllFaces(img);
+  const faceData = await faceapi.detectAllFaces(img).withFaceLandmarks().withFaceDescriptors();
   if (!faceData.length) return;
-  faceapi.draw.drawDetections(resultCvs, faceData);
   const faceMatcher = new faceapi.FaceMatcher(refLabeledFaceDescriptors, 0.6);

+  faceData.forEach(fd => {
+     const bestMatch = faceMatcher.findBestMatch(fd.descriptor);
+     const box = fd.detection.box;
+     const drawBox = new faceapi.draw.DrawBox(box, { label: bestMatch.toString()});
+     drawBox.draw(resultCvs);
+  });
}

顔認識を行う画像に対して、FaceDescriptors(顔記述子)を取得します。
そして、faceMatcher.findBestMatchで比較します。
参照データで複数のdescriptorを登録していた場合、もっとも精度の良い結果を選んでくれるとのことです。

const box以降の行は、結果画像にlabelを表示するための処理になります。
以下、実行結果です。
image.png
image.png
顔モデルの読み込みや参照データの読み込みには少し時間がかかります。
サービスで利用するときは、ローディング中が分かる表示があるといいかと思います。
今回は簡易的にメッセージだけ表示しています。
image.png
参照データの読み込みが終わったので、「顔認識」ボタンを押します。
image.png

できた!
image.png
labelもきちんと表示されています。

saburoさんなんて学習データ正面顔
image.png
なのに、ほぼ横顔を検出するなんて…すごい。

顔認識させる画像を変えてみました。
image.png

ばっちりです◎

時間はどのくらい?

モデルの読み込み:1300~2250ms
参照データの計算:1600~2700ms  :1400×933の画像3枚
顔認識:150~270ms        :2人分のデータについて1400*933の画像1枚

このくらいの時間がかかっていました。
モデルの読み込みや参照データの計算は1度行えばOKですが、
顔認識は毎回実行するのでinputソースが動画になるとちょっと心配です。

今後やりたいこと

・小さいサイズのモデルに変更
MTCNN顔検出器を使用したリアルタイムJavaScript顔追跡と顔認識
・表情検出
・他の顔検出、顔認識系の技術の調査

28
26
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
28
26