この記事でやること
今回はface-api.jsで使ってみたかった「顔認識」を試してみます!
face-api.jsとは
face-api.jsはブラウザで顔検出(face detection:人の顔を自動的に見つける)と
顔認識(face recognition:個人を識別する)ができるJavaScript APIです。
機械学習用のライブラリTensorFlow.jsが利用されています。
face-api.js
・顔検出
・顔認識
・表情検出
・年齢性別推定
ができます。
顔認識(face recognition)とは
カメラのデジタル画像から、人を自動的に識別するためのコンピュータシステムである。
と書かれています。
この記事では画像に映っている人が「誰か」を判断するものとして話を進めていきます。
顔認識のやり方
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メソッドを呼ぶようにしています
認識を行う画像を表示
画像はフリー写真素材ぱくたそ からお借りしました。
ボタンを押したら、顔検出するようにしておきます。
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);
}
参照データの読み込み
参照画像を用意します。
今回は1枚ずつですが、1人に対して複数枚の用意しておくと精度がアップするようです。
適当なラベル付で恐縮ですが、左から
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さんの参照画像は「顔が検出できなかった」んです。(目をつむってるからかな)
そのことに気が付くまで、なかなかチュートリアルのようにはいかず、???となっていました…
参照画像で顔が検出されない場合も想定したコードへ変更したところ動くようになりました。
これでjiroさんとsaburoさんの参照顔データ(顔記述子)の取得ができたので次のステップに進めます!
FaceMatcherの初期化
いよいよFaceMatcherを使います。
face-api.jsより引用
上記のように、そのまま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を表示するための処理になります。
以下、実行結果です。
顔モデルの読み込みや参照データの読み込みには少し時間がかかります。
サービスで利用するときは、ローディング中が分かる表示があるといいかと思います。
今回は簡易的にメッセージだけ表示しています。
参照データの読み込みが終わったので、「顔認識」ボタンを押します。
saburoさんなんて学習データ正面顔
なのに、ほぼ横顔を検出するなんて…すごい。
ばっちりです◎
時間はどのくらい?
モデルの読み込み:1300~2250ms
参照データの計算:1600~2700ms :1400×933の画像3枚
顔認識:150~270ms :2人分のデータについて1400*933の画像1枚
このくらいの時間がかかっていました。
モデルの読み込みや参照データの計算は1度行えばOKですが、
顔認識は毎回実行するのでinputソースが動画になるとちょっと心配です。
今後やりたいこと
・小さいサイズのモデルに変更
・MTCNN顔検出器を使用したリアルタイムJavaScript顔追跡と顔認識
・表情検出
・他の顔検出、顔認識系の技術の調査