"BOKU"のITな日常

62歳・文系システムエンジニアの”BOKU”は日々勉強を楽しんでます

検出した顔に四角くマーク&画像でマスク/JavaScriptで顔を検出「pico.js」(2)

JavaScriptのみで高速に顔検出ができる「pico.js」の応用をやってみます。

囲む枠を丸から四角に変更してみたり、パンダのイラストを重ねてみたり。

f:id:arakan_no_boku:20210215234003p:plain

 

pico.jsの使い方は前回を参考にしてください

 

JavaScriptのみでブラウザ上で顔検出ができる「pico.js」の使い方の2回目です。

github.com

 

前回に書いた基本的な使い方をふまえて、今回はちょっとだけ応用編をやってみます。

まずは、検出した顔を示すマークのやり方を変更します。

デフォルトだと、以下のように赤い丸なので、それを四角い枠にしたり、別の画像をかぶせてみるとかしてみます。。

f:id:arakan_no_boku:20210217225114p:plain 

デフォルトの赤い丸を描いている部分はこうです。

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();

ここだけ見る限りCanvasの普通の構文です。

顔の座標は「dets[i]」にはいっているようですから、それを使って描いていきます。

 

まずは四角形にしてみよう

 

デフォルトの円を四角で囲うようにしてみます。

dets[i]にはいっている検出した顔の位置をしめす座標の基本は円なので。

  • dets[i][0]: 中心のy座標
  • dets[i][1]: 中心のx座標
  • dets[i][2]: 直径

となっています。

これで四角形を描くには、左上の視点のx,y座標に置き換えてやる必要があります。

すなわち。

四角形の左上のx,y座標にするには、中心から直径の1/2分ずつ、左上にずらして、こんな感じにしてやればいいわけです。

  • 四角形のX座標:dets[i][1]-(dets[i][2]/2)
  • 四角形のY座標:dets[i][0]-(dets[i][2]/2)
  • 高さと幅:直径なので、dets[i][2] のまま。

これで四角形を描くとするとこういう感じになります。

せっかくなので、色も黄色にしてみましょう。

ctx.beginPath();
ctx.rect(dets[i][1]-(dets[i][2]/2), dets[i][0]-(dets[i][2]/2), dets[i][2], dets[i][2]);
ctx.lineWidth = 3;
ctx.strokeStyle = 'yellow';
ctx.stroke();

デフォルトを置き換えてみると。

f:id:arakan_no_boku:20210219200502p:plain

おー・・。いけてるいけてる。

 

画像をかぶせてみよう

 

左上の座標が算出できるなら、画像をかぶせることもできます。

まず。

HTML側でかぶせる画像を指定しておきます。

画面にださなくてもいいので、いったん「display:none」で、

<div style="display:none;">
   <img id="mask" src="images/kitsune.png">
</div>

こいつをJavaScript側で読み込んで準備します。

var msk_img = document.getElementById('mask');

描画するのは、こう。

座標のもとめかたは、四角を描くときと同じです。

ctx.beginPath();
ctx.drawImage(msk_img, dets[i][1]-(dets[i][2]/2), dets[i][0]-(dets[i][2]/2), dets[i][2], dets[i][2])

画像を何パターンか差し替えてやってみました。 

まずは、ふつうに狐の顔。

f:id:arakan_no_boku:20210219222802p:plain

ふむふむ。

今度はパンダだけど、透過PNGを使ってみたやつ。

f:id:arakan_no_boku:20210219222949p:plain

なんか、画像を工夫したら、面白いかもです。

 

最後はグラデーションかけてみよう

 

顔の部分を塗りつぶして隠す・・という手もあります。

でも、それだと、いまいち面白くないので、グラデーションをつかって、顔の部分に光があたって、ちょっと隠れている感じをやってみます。

描画する部分を、こんな感じにしてみると。

 ctx.beginPath();
var grad  = ctx.createRadialGradient(dets[i][1], dets[i][0],0,dets[i][1], dets[i][0],dets[i][2]/2);
grad.addColorStop(0,'rgba(255,255,255,1)');
grad.addColorStop(1,'rgba(255,255,255,0)');
ctx.arc(dets[i][1], dets[i][0], dets[i][2]/2, 0, 2*Math.PI, false);
ctx.fillStyle = grad;
ctx.fill();

結果はこんな風になりました。

f:id:arakan_no_boku:20210219230210p:plain

まあ・・雰囲気はいけてますかね。

こういう感じで顔に何等かのマスクをかける処理をWEBカメラつかってリアルタイムでやったら、なんとなく面白そうです。

今回は、長くなったので、それは次回の記事にします。

 

最後にJavaScript部分だけソース全文です

 

何パターンかやったうち、四角を描くパターンだけのせておきます。

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');
var msk_img = document.getElementById('mask');
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.rect(dets[i][1]-(dets[i][2]/2), dets[i][0]-(dets[i][2]/2), dets[i][2], dets[i][2]);
            ctx.lineWidth = 3;
            ctx.strokeStyle = 'yellow';
            ctx.stroke();
        }
    }
}

ではでは。