FaceAPIでかんたん顔認識
face-api.jsとは!?
face-api.jsは、JavaScriptで気軽に顔検出が出来るライブラリです。
ライブラリのダウンロード
face-api.jsのソースコードは、GitHubに公開されております。
次のサイトにアクセスし、1, 2の順番にクリックしてライブラリ1式をダウンロードしましょう。
必要なファイル
圧縮ファイルをダウンロードしたら、解凍して必要なファイルを取り出しましょう。
1, face-api.jsライブラリ本体
2, モデルデータ1式(学習済みデータです)
プロジェクトを作る
プロジェクトを作り、これらのファイルを配置します。
MyProject01/
├ assets/ (顔認識にかける画像データを格納します)
├ weights/ (モデルデータ1式です)
├ face-api.js (face-api.jsライブラリ本体です)
├ index.html (プログラムを起動するファイルです)
├ main.js (メインのプログラムを記述するファイルです)
HTMLファイルを用意する
では、作っていきましょう。
HTMLファイルを用意して、下記コードを記述します。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style type="text/css">body{margin:0; padding:0;}</style>
<script src="./face-api.js"></script>
<script src="./main.js"></script>
</head>
<body>
<canvas id="myCanvas" />
</body>
</html>
"canvas"には、顔認識に使った画像と、その結果を描画していきます。
JavaScriptファイルを用意する
次はメインのプログラムです。
face-api.jsライブラリを読み込む事で、"faceapi"を扱える様になります。
最初に、Promiseを利用して下記の3つの関数を実行しましょう。
引数には、それぞれ"モデルデータへのパス"を指定します。
"loadSsdMobilenetv1Model関数"で、オブジェクト検出モデルのロード、
"loadFaceLandmarkModel関数"で、ランドマーク検出モデルのロード、
"loadFaceRecognitionModel関数"で、顔認識モデルのロードを実行します。
Promise.all([
faceapi.loadSsdMobilenetv1Model(モデルデータへのパス),
faceapi.loadFaceLandmarkModel(モデルデータへのパス),
faceapi.loadFaceRecognitionModel(モデルデータへのパス)
]).then(ライブラリの準備が出来た時に実行される関数);
実装例は、次の様になります。
const FILE_URL = "./assets/sample_01.png";
const MODEL_URL = "./weights";
let img, canvas, context;
window.onload = (event)=>{
console.log("onload!");
loadModels();
}
async function loadModels(){
console.log("loadModels");
Promise.all([
faceapi.loadSsdMobilenetv1Model(MODEL_URL),
faceapi.loadFaceLandmarkModel(MODEL_URL),
faceapi.loadFaceRecognitionModel(MODEL_URL)
]).then(detectAllFaces);
}
async function detectAllFaces(){
console.log("detectAllFaces");
// ここで画像解析を行います。
}
画像を解析する
次はいよいよ画像解析です。
今回は、フリー素材を利用して顔認識をしてみようと思います。
次の画像を保存して、"assets"フォルダに格納しましょう。
Luff&Peace <- とても素敵なサイトです!!
次の順番で処理を記述していきます。
1, 画像の読み込み
"faceapi.fetchImage関数"に、解析対象である画像へのパスを指定します。
img = await faceapi.fetchImage(画像へのパス);
2, HTMLからキャンバスを取得し画像を描画
そして、HTMLからキャンバス要素を取り出し、先ほどの画像を描画します。
context.drawImage(img, 0, 0);// 画像の描画
3, 顔認識の実行と認識結果の取得
次は、顔認識の実行です。
"faceapi.detectAllFaces関数"に、画像データを渡します。
"withFaceLandmarks関数"は、ランドマークを取得する命令です。
返り値として、認識データを取得する事ができます。
const fData = await faceapi.detectAllFaces(画像データ).withFaceLandmarks();
4, 認識結果のリサイズ
最後に、"faceapi.resizeResults関数"で認識結果をリサイズします。
ここで、認識データ全体を目的の画角へリサイズする事が可能です。
リサイズ後のデータを"forEach"を使って取り出します。
const rData = await faceapi.resizeResults(fData, iSize);
rData.forEach(data=>{drawResult(data);});
認識結果データの"data.detection.box"には、顔の位置データが格納されています。
このデータを取り出して、そのままキャンバスに描画します。
キャンバスの描画については、CanvasAPIをご参照ください。
context.strokeRect(box.x, box.y, box.width, box.height);
ここまでのコードをまとめると、次の様になります。
// 省略
async function detectAllFaces(){
console.log("detectAllFaces");
// 1, 画像の読み込み
img = await faceapi.fetchImage(FILE_URL);
// 2, HTMLからキャンバスを取得し画像を描画
canvas = document.getElementById("myCanvas");
canvas.width = img.width;
canvas.height = img.height;
context = canvas.getContext("2d");
context.fillRect(0, 0, canvas.width, canvas.height);
context.drawImage(img, 0, 0);// 画像の描画
// 3, 顔認識の実行と認識結果の取得
const iSize = {width: img.width, height: img.height};
const fData = await faceapi.detectAllFaces(img).withFaceLandmarks();
// 4, 認識結果のリサイズ
const rData = await faceapi.resizeResults(fData, iSize);
rData.forEach(data=>{drawResult(data);});
}
function drawResult(data){
console.log("drawResult!!");
//console.log(data);
const box = data.detection.box;// 長方形のデータ
const mrks = data.landmarks.positions;
context.fillStyle = "red";
context.strokeStyle = "red";
context.lineWidth = 4;
context.strokeRect(box.x, box.y, box.width, box.height);// 長方形の描画
}
顔の上に長方形が描かれています。(皆さんとても爽やかですね!!)
ランドマークについて
次は、顔の輪郭や目や鼻、口の位置を取得してみます。
これらは、"data.landmarks.positions"で取得する事が出来ます。
このデータには、合計68箇所の座標データが含まれています。
これらのデータを使って、鼻メガネを描いてみましょう。(思いつきです!!)
キャンバスの描画については、CanvasAPIをご参照ください。
// 省略
function drawResult(data){
console.log("drawResult!!");
const det = data.detection;
const mrks = data.landmarks.positions;
const box = det.box;
context.fillStyle = "red";
context.strokeStyle = "red";
context.lineWidth = 4;
context.strokeRect(box.x, box.y, box.width, box.height);
drawNose(mrks);// 鼻を描きます
drawGrasses(mrks);// メガネを描きます
}
function drawNose(mrks){
context.strokeStyle = "black";
context.lineWidth = 2;
context.beginPath();
for(let i=27; i<35; i++){
let fX = mrks[i].x;
let fY = mrks[i].y;
let tX = mrks[i+1].x;
let tY = mrks[i+1].y;
context.moveTo(fX, fY);
context.lineTo(tX, tY);
}
context.stroke();
}
function drawGrasses(mrks){
context.strokeStyle = "black";
context.lineWidth = 2;
context.beginPath();
context.moveTo(mrks[39].x, mrks[39].y);
context.lineTo(mrks[42].x, mrks[42].y);
context.stroke();
// 左
const lX = (mrks[39].x + mrks[36].x)/2;
const lY = (mrks[41].y + mrks[38].y)/2;
const lR = (mrks[39].x - mrks[36].x);
context.beginPath();
context.arc(lX, lY, lR, 0, Math.PI*2);
context.stroke();
// 右
const rX = (mrks[45].x + mrks[42].x)/2;
const rY = (mrks[46].y + mrks[43].y)/2;
const rR = (mrks[45].x - mrks[42].x);
context.beginPath();
context.arc(rX, rY, rR, 0, Math.PI*2);
context.stroke();
}
素敵な鼻メガネを描く事が出来ました。(いろいろ御免なさい!!)
最後に、全体のコードを載せておきますね。
const FILE_URL = "./assets/sample_01.png";
const MODEL_URL = "./weights";
let img, canvas, context;
window.onload = (event)=>{
console.log("onload!");
loadModels();
}
async function loadModels(){
console.log("loadModels");
Promise.all([
faceapi.loadSsdMobilenetv1Model(MODEL_URL),
faceapi.loadFaceLandmarkModel(MODEL_URL),
faceapi.loadFaceRecognitionModel(MODEL_URL)
]).then(detectAllFaces);
}
async function detectAllFaces(){
console.log("detectAllFaces");
// Image
img = await faceapi.fetchImage(FILE_URL);
// Canvas, Context
canvas = document.getElementById("myCanvas");
canvas.width = img.width;
canvas.height = img.height;
context = canvas.getContext("2d");
context.fillRect(0, 0, canvas.width, canvas.height);
context.drawImage(img, 0, 0);
const iSize = {width: img.width, height: img.height};
const fData = await faceapi.detectAllFaces(img).withFaceLandmarks();
const rData = await faceapi.resizeResults(fData, iSize);
rData.forEach(data=>{drawResult(data);});
}
function drawResult(data){
console.log("drawResult!!");
const det = data.detection;
const mrks = data.landmarks.positions;
const box = det.box;
context.fillStyle = "red";
context.strokeStyle = "red";
context.lineWidth = 4;
context.strokeRect(box.x, box.y, box.width, box.height);
drawLandmarks(mrks);
drawNose(mrks);
drawGrasses(mrks);
}
function drawLandmarks(mrks){
for(let i=0; i<mrks.length; i++){
let x = mrks[i].x;
let y = mrks[i].y;
context.fillRect(x, y, 2, 2);
context.fillText(i, x, y, 18);
}
}
function drawNose(mrks){
context.strokeStyle = "black";
context.lineWidth = 2;
context.beginPath();
for(let i=27; i<35; i++){
let fX = mrks[i].x;
let fY = mrks[i].y;
let tX = mrks[i+1].x;
let tY = mrks[i+1].y;
context.moveTo(fX, fY);
context.lineTo(tX, tY);
}
context.stroke();
}
function drawGrasses(mrks){
context.strokeStyle = "black";
context.lineWidth = 2;
context.beginPath();
context.moveTo(mrks[39].x, mrks[39].y);
context.lineTo(mrks[42].x, mrks[42].y);
context.stroke();
// 左
const lX = (mrks[39].x + mrks[36].x)/2;
const lY = (mrks[41].y + mrks[38].y)/2;
const lR = (mrks[39].x - mrks[36].x);
context.beginPath();
context.arc(lX, lY, lR, 0, Math.PI*2);
context.stroke();
// 右
const rX = (mrks[45].x + mrks[42].x)/2;
const rY = (mrks[46].y + mrks[43].y)/2;
const rR = (mrks[45].x - mrks[42].x);
context.beginPath();
context.arc(rX, rY, rR, 0, Math.PI*2);
context.stroke();
}
最後に
face-api.jsを駆け足で説明させていただきましたが、いかがでしたでしょうか?
クライアントサイドだけでここまで出来てしまうなんて!!
驚いてしまいますよね。
他にも、検出できるデータとして"感情"等も取得する事ができます。(これも面白そうです!!)
ここまで読んでいただき有難うございました。
Discussion