Google Chromeの拡張を作ってみたい③ 顔認識

顔認識

外部ライブラリとか、WebAPIとか、無くても、ブラウザだけあれば顔認識ができるみたいなので試してみる。

Shape Detection API

flags

試験的な機能なようで、有効化しないと使えない。

chrome://flags/#enable-experimental-web-platform-features

に行って、Experimental Web Platform featuresをEnabledにする。

contentScript.js

動作確認

ていうかそもそも拡張機能内で使えるんだろうかと、ふと頭をよぎったのでお試しをしてみる。

https://dalomo.net/blog/files/face/index.html

こんなんを作りまして、codepenのコードを参考に

debugger;
var image = document.getElementsByTagName('img');

try {
    if (window.FaceDetector == undefined) {
        console.error('Face Detection not supported');
        throw new Error();
    }

    console.log('ignition!');
    var faceDetector = new FaceDetector();
    faceDetector.detect(image[0])
        .then(faces => {
            console.log('yes');
        });

} catch (error) {

}

うーんと、imgタグをgetElementsByTagNameで取得。facedetecterが有効化されてるかチェック。元はreturnだったけど、returnだとなんかエラーになるのでtry-catchでexit代わり。そしてnewして、faceDetector.detect(image[0])で検出。image[0]の部分は

It takes an image object (either a CanvasImageSource, Blob, ImageData or an <img> element)

が対応してるみたい。thenだから非同期処理なんだなこれ。そんで、動かしてみますと

あっ!なんかいい感じな気がする!!!!!やっぱりlandmark(目・鼻・口)の位置はまだ取れないみたい。そしたらcanvasのあれやこれやを追加してみる。

canvasを追加

debugger;
var image = document.getElementsByTagName('img');
var ccan = document.createElement('canvas');
document.body.insertBefore(ccan, image[0].nextSibling);

ccan.width = image[0].width;
ccan.height = image[0].height;

var canvas = document.getElementsByTagName('canvas');

var ctx = canvas[0].getContext("2d");
ctx.drawImage(image[0],
    0, 0, image[0].width, image[0].height,
    0, 0, ccan.width, ccan.height);

const scale = ccan.width / image[0].width;

try {
    if (window.FaceDetector == undefined) {
        console.error('Face Detection not supported');
        throw new Error();
    }

    console.log('ignition!');
    var faceDetector = new FaceDetector();
    faceDetector.detect(image[0])
        .then(faces => {
            console.log('yes');

            ctx.lineWidth = 2;
            for (var i = 0; i < faces.length; i++) {
                const face = faces[i].boundingBox;
                ctx.beginPath();
                ctx.strokeStyle = "red";
                ctx.rect(Math.floor(face.x * scale),
                    Math.floor(face.y * scale),
                    Math.floor(face.width * scale),
                    Math.floor(face.height * scale));
                ctx.stroke();
            }
        });

} catch (error) {

}

愚直に書いた。えーとまず、canvasタグは自分の方には書いてなかったのでcreateElementでcanvasを作る。body直下、子っていうらしい、にimgタグがあるのでimgタグのすぐ下にcanvasが作られるようdocument.body.insertBefore(ccan, image[0].nextSibling)てやる。canvasのサイズと画像サイズを同じにして、getContext(“2d”)で描画機能を有効にする。で、drawImageで元画像をcanas上に描画する。コピーみたいな感じか。サイズの比率をとっとく。これはあんま使わなかった。lineWidthで線の太さを2にして、facesの中の顔の数だけ描画してく。faces.boundingBoxには画像内の顔を囲む四角の座標が入ってる。x, yが四角の左上の座標、width, heightが四角の幅と高さ、top, right, bottom, leftがそれぞれ画像内での、上辺のy座標・右辺のx座標・底辺のy座標・左辺のx座標と思われる。そしたら描画に入ってく。beginPathでリセット…初期化みたいなもんかな、して、strokeStyleで色を設定。rectで矩形の設定、左上の座標と幅高をさっきのboundingBoxの値を利用する。Math.floorで切り捨て整数にする。strokeで描画を実行しますと、結果は

うおー!できた!けどあれ?なんか2人いる…。なんでかなーと思ってcodepenの例見たらhtmlのいmgタグにhidden属性が付けられてた。ただこの状態からelem.style.visibility = “hidden”するとそこだけぽっかり空くみたいな感じになるみたい。なので、image[0].style.display = “none”ってした。

1人になった!よっしゃ、そしたら全部の画像にやってみよう。

サンプルページ内の画像全てに適用

debugger;
var image = document.getElementsByTagName('img');
var ctx = [];
for (var j = 0; j < image.length; j++) {
    var ccan = document.createElement('canvas');
    document.body.insertBefore(ccan, image[j].nextSibling);

    ccan.width = image[j].width;
    ccan.height = image[j].height;

    var canvas = document.getElementsByTagName('canvas');

    ctx[j] = canvas[j].getContext("2d");
    ctx[j].drawImage(image[j],
        0, 0, image[j].width, image[j].height,
        0, 0, ccan.width, ccan.height);

    const scale = ccan.width / image[j].width;

    try {
        if (window.FaceDetector == undefined) {
            console.error('Face Detection not supported');
            throw new Error();
        }
        image[j].style.display = "none";
        console.log('ignition!');
        var faceDetector = new FaceDetector();

        var n = 0;
        faceDetector.detect(image[j])
            .then(faces => {
                console.log('yes');
                
                ctx[n].lineWidth = 2;
                for (var i = 0; i < faces.length; i++) {
                    const face = faces[i].boundingBox;
                    ctx[n].beginPath();
                    ctx[n].strokeStyle = "red";
                    ctx[n].rect(Math.floor(face.x * scale),
                        Math.floor(face.y * scale),
                        Math.floor(face.width * scale),
                        Math.floor(face.height * scale));
                    ctx[n].stroke();
                }
                n++;
            });
            
    } catch (error) {

    }
}

おあー…、動くことを目指して一生懸命書いてたらすっげぇ汚ねぇコードになった…。forで回せばなんとかなんだろと思って書き始めたけど、forのブロック内に非同期処理があると、非同期処理側の処理の完了を待たずに処理が進んでしまうので、思ったとおりに動かない。非同期処理なんだから当たり前なんだけど。どうしよっかなーとデバッガでステップインしながら辻褄が合うように直してったらこうなった。ステップインしながら思ったんだけど、for内のカウントが全部終了してから非同期処理の中をやってくみたいな順番なのね。それはそういうもんなのか、それとも自分の書き方がこうだからそうなったのか、調べる気力は湧きませんでした。

赤線の所まで行ったらforの最初に戻って繰り返し。条件式満たしたら、then以下が実行されてった。うーん、どうすればキレイに書けるんだろう、検索語句も分からん。あ、で、結果

できた…!精度的には真正面だとまぁ認識されて、逆さ顔とか、横顔とか画像がちっちゃかったりとかだとあんま認識されないみたい。まぁサンプル数=1だからなんとなくだけど。

参考

https://wicg.github.io/shape-detection-api/

https://codepen.io/miguelao/pen/PmJWro

https://codepen.io/oliverjam/pen/jzBWNB

https://paul.kinlan.me/face-detection/

https://qiita.com/daisu_yamazaki/items/c782f0154fcb784d4406

https://www.mitsue.co.jp/knowledge/blog/frontend/201810/29_1635.html

https://lealog.hateblo.jp/entry/2018/08/24/153251

https://teratail.com/questions/209176

https://developer.mozilla.org/ja/docs/Web/API/Document/createElement

https://qiita.com/kouh/items/dfc14d25ccb4e50afe89

https://pcmanabu.com/javascript.html

https://stackoverflow.com/questions/15318357/show-hide-image-with-javascript

カテゴリー: したい タグ: , , , パーマリンク

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です