目次
JavaScriptライブラリ「pico.js」を使う準備
ブラウザ上で高速に顔認識ができるJavaScriptライブラリを試してみます。
以下のサイトにあります。
上記のリンク(GitHub)から「ZIP」でダウンロードし、適当なところに解凍します。
中に。
- pico.js
- lploc.js
という2つのスクリプトファイルが含まれています。
今回は「pico.js」のみ使います。
lploc.jsは「瞳孔認識」をするライブラリです。
目の動きでお絵描きするとか面白いそうなんですけど、今回は使いません。
HTMLファイルを置くフォルダに「js」というサブフォルダを作って、その下に「pico.js」をコピーします。
pico.jsを使う
こんな感じに画面上に表示した画像に対して、ボタンを押したら顔認識して、結果を表示する感じにしてみました。
pico.jsを使う:HTML側のポイント
まず。
<head></head>部で、上記でコピーした「pico.js」を読み込みます。
<script src="js/pico.js"></script>
顔認識の対象とする画像をimgタグで読み込みます。
<img id="image" width="512" height="341" src="images/people-512x341.jpg" />
顔認識結果画像の表示エリアは「canvas」タグで指定します。
<canvas id="canvas" width="512" height="341" ></canvas>
画像認識をスタートさせるボタンを配置します。
<input type="button" value="検出開始" onclick="fnc_callback(341, 512)">
今回は顔認識処理の本体は「s/pico_sample.js」に書くので、それをロードします。、
<script src="js/pico_sample.js"></script>
HTML側のポイントはこれだけです。
pico.jsを使う:JavaScript側処理の流れ
pico.jsで顔認識を行う手順は。
事前準備として以下をやっておき
- 顔認識カスケードダウンロード
- 認識対象の画像の準備
検出開始ボタンが押されたとき
- パラメータ設定
- 顔認識の実行
- 認識した顔の範囲を囲む(今回は円<arc>)
を実行する・・という感じです。
pico.jsを使う:準備1.顔認識カスケードダウンロード
顔認識カスケードダウンロードで作成済の顔認識用の決定木をダウンロードします。
pico.jsの顔認識は、450もの決定木を25段階に連結しているので「カスケード」(同じものがいくつも数珠つなぎに連結された構造や、連鎖的あるいは段階的に物事が生じる様子)と呼びます。
それを行う部分が以下です。
var facefinder_classify_region = function(r, c, s, pixels, ldim) {return -1.0;}; var cascadeurl = 'https://raw.githubusercontent.com/nenadmarkus/pico/c2e81f9d23cc11d1a612fd21e4f9de0921a5d0d9/rnt/cascades/facefinder'; fetch(cascadeurl).then(function(response) { response.arrayBuffer().then(function(buffer) { var bytes = new Int8Array(buffer); facefinder_classify_region = pico.unpack_cascade(bytes); console.log('* cascade loaded'); }) })
ここは、サンプルのまま使うしかないです。
pico.jsを使う:準備2. 顔認識対象の画像の準備
次に、認識対象の画像の準備で、Canvasに変換対象の画像をロードします。
var ctx = document.getElementById('canvas').getContext('2d'); var img = document.getElementById('image'); img.onload = () => ctx.drawImage(img, 0, 0);
事前準備は以上です。
pico.jsを使う:認識処理_コールバック関数 パラメータ設定
ここからは「検出開始」ボタンを押された後(つまり、fnc_callback関数の中身)の処理になります。
pico.jsで顔認識するメソッドに渡すパラメータを準備します。
- image
- params
の2つです。
imageパラメータの「pixels」には、顔認識対象画像データの生のピクセル値(赤、緑、青+アルファ形式を取得して、グレースケール変換したものをセットします。
あとのパラメータは、とりあえずサンプル(デフォルト)のままでやるのが無難です。
ctx.drawImage(img, 0, 0); var rgba = ctx.getImageData(0, 0, ncol, nrow).data; image = { "pixels": rgba_to_grayscale(rgba, nrow, ncol), "nrows": nrow, "ncols": ncol, "ldim": ncol } params = { "shiftfactor": 0.1, "minsize": 20, "maxsize": 1000, "scalefactor": 1.1 }
グレースケール変換しているのは「rgba_to_grayscale」メソッドです。
function rgba_to_grayscale(rgba, nrows, ncols) { var gray = new Uint8Array(nrows*ncols); for(var r=0; r<nrows; ++r) for(var c=0; c<ncols; ++c) gray[r*ncols + c] = (2*rgba[r*4*ncols+4*c+0]+7*rgba[r*4*ncols+4*c+1]+1*rgba[r*4*ncols+4*c+2])/10; return gray; }
画像データのWidthとHeightの指定が必要です。
pico.jsを使う:認識処理_コールバック関数:顔認識の実行
顔認識の実行は以下のようにします。
2行でワンセットと考えたほうがいいです。
dets = pico.run_cascade(image, facefinder_classify_region, params); dets = pico.cluster_detections(dets, 0.2);
上の1行だけだと、同じ顔を少しずつ違った座標で何回も認識してしまいます。
それを整理して、一番有効なひとつに絞り込むのが2行目・・って感じです。
pico.jsを使う:認識処理_コールバック関数:認識した顔を囲む
認識した顔の範囲は上記の「dets」にはいってます。
複数認識しても、全部はいってます。
なので、ループでまわして、認識した顔のまわりを線で囲います。
qthresh = 5.0 for(i=0; i<dets.length; ++i){ if(dets[i][3]>qthresh) { ctx.beginPath(); ctx.arc(dets[i][1], dets[i][0], dets[i][2]/2, 0, 2*Math.PI, false); ctx.lineWidth = 3; ctx.strokeStyle = 'red'; ctx.stroke(); } }
beginPathから始まる一連の処理で「赤い円」を描いてます。
このへんは、第二回目で詳しくやるので、今回は簡単に流します。
html5のcanvasの処理なので、興味があれば、こちらをどうぞ。
Webサーバーを立ち上げて実行
WEBサーバーたちあげて「http://localhost」で実行します。
HTMLをダブルクリックで動かそうとしてもエラーになります。
Chromeだとこんな感じのメッセージ。
Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data
これは「 Chrome では情報漏洩対策ですべてのローカルファイルが別オリジンだとみなされる」という仕様によるものなので、回避不可能です。
横着しないで、ローカルでもWEBサーバーをちゃんと立てましょうということですね。
実行結果の例です
こういう写真に対して「検出開始」ボタンを押すと。
結果はこんな感じ。
よしよし。
とりあえず、いけてます。
JavaScriptのソース全体
HTMLは上記を参考にして、適当にレイアウトして作ってください。
顔写真は適当にネットからひろってきて、リサイズしてください。
横を向いた顔だと認識されないときがあったり、あまり、サイズの小さい顔はスルーされたりもしますので、何枚かためして、感触をつかむ必要はあるかもです。
var facefinder_classify_region = function(r, c, s, pixels, ldim) {return -1.0;}; var cascadeurl = 'https://raw.githubusercontent.com/nenadmarkus/pico/c2e81f9d23cc11d1a612fd21e4f9de0921a5d0d9/rnt/cascades/facefinder'; fetch(cascadeurl).then(function(response) { response.arrayBuffer().then(function(buffer) { var bytes = new Int8Array(buffer); facefinder_classify_region = pico.unpack_cascade(bytes); console.log('* cascade loaded'); }) }) //var nrow = 341; //var ncol = 512; var ctx = document.getElementById('canvas').getContext('2d'); var img = document.getElementById('image'); img.onload = () => ctx.drawImage(img, 0, 0); function rgba_to_grayscale(rgba, nrows, ncols) { var gray = new Uint8Array(nrows*ncols); for(var r=0; r<nrows; ++r) for(var c=0; c<ncols; ++c) gray[r*ncols + c] = (2*rgba[r*4*ncols+4*c+0]+7*rgba[r*4*ncols+4*c+1]+1*rgba[r*4*ncols+4*c+2])/10; return gray; } function fnc_callback(nrow, ncol) { ctx.drawImage(img, 0, 0); var rgba = ctx.getImageData(0, 0, ncol, nrow).data; image = { "pixels": rgba_to_grayscale(rgba, nrow, ncol), "nrows": nrow, "ncols": ncol, "ldim": ncol } params = { "shiftfactor": 0.1, "minsize": 20, "maxsize": 1000, "scalefactor": 1.1 } dets = pico.run_cascade(image, facefinder_classify_region, params); dets = pico.cluster_detections(dets, 0.2); qthresh = 5.0 for(i=0; i<dets.length; ++i){ if(dets[i][3]>qthresh) { ctx.beginPath(); ctx.arc(dets[i][1], dets[i][0], dets[i][2]/2, 0, 2*Math.PI, false); ctx.lineWidth = 3; ctx.strokeStyle = 'red'; ctx.stroke(); } } }
ではでは。