Tensorflow.jsとスマホのカメラで、リアルタイム物体検出

JavaScript

説明

Tensorflow.jsとそれの物体検出モデルのCoco-ssdとスマホ(Android)のカメラを使ってリアルタイム物体検出の方法を紹介していきます。Coco-ssdモデルを使用すると80のカテゴリーを識別できるそうです。
※結構力業で作ったものなので、コンテンツとしては微妙かもしれません。

開発環境

・Webサーバ環境(HTML, JavaScript, CSSが動作する環境)
・スマホ(Android)でGoogleChrome

ファイル構成

index.htmlとmain.jsとstyle.cssの三種類のファイルを作っていきます。main.jsはjsフォルダの中、style.cssはcssフォルダの中に入れてあります。

↓↓ ファイル構成
|-index.html
|-js/main.js
|-css/style.css

ファイルの作成

①. HTMLファイルの作成

<!DOCTYPE html>
<html lang="en">
<head>
	<title>tensorflow</title>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"> </script>
	<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/coco-ssd"></script>
	<script src="./js/main.js"></script>
	<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
	<div id="container">
		<div id="wrapper">
			<video id="camera"></video>
			<canvas id="canvas"></canvas>
			<canvas id="camera_canvas"></canvas>
		</div>
	</div>
</body>
</html>

TensorflowとCoco-ssdモデルの呼び出し
・cameraの映像を表示するvideoタグ(id=camera),
・ 物体検出した範囲を表示するcanvasタグ(canvas)
カメラ映像を描きだして、物体検出するためのcanvasタグ(id=camera_canvas)。こちらは、あまり綺麗に表示されないので、cssのz-indexで人には見せないようにしました

②. main.jsの作成

window.onload = () => {
    var container = document.getElementById("container");
    var video  = document.getElementById("camera");
    video.style.maxWidth = container.clientWidth + "px";
    video.style.maxHeight = container.clientHeight + "px";
    var canvas, camera_canvas = null;
    var context, camera_context = null;

    const constraints = {
        audio: false,
        video: { facingMode: { exact: "environment" }
        }
    };

    navigator.mediaDevices.getUserMedia(constraints)
    .then( (stream) => {
        video.srcObject = stream;
        video.onloadedmetadata = (e) => {
        video.play();
        // canvas
        canvas = document.getElementById("canvas");
        canvas.style.width = video.clientWidth + "px";
        canvas.style.height = video.clientHeight + "px";
        context = canvas.getContext("2d");
        camera_canvas = document.getElementById("camera_canvas");
        camera_canvas.style.width = video.clientWidth + "px";
        camera_canvas.style.height = video.clientHeight + "px";
        camera_context = camera_canvas.getContext("2d");
        };
    })
    .catch( (err) => {
        console.log(err.name + ": " + err.message);
    });

    cocoSsd.load().then(model => {
        setInterval(function(){detect(model, video, canvas, context)}, 250);
    });
    
    const detect = (model, video, canvas, context) => {
        context.clearRect(0, 0, canvas.width, canvas.height);
        camera_context.clearRect(0, 0, camera_canvas.width, camera_canvas.height);
        // video.pause();
        camera_context.drawImage(video, 0, 0, camera_canvas.width, camera_canvas.height);
        model.detect(camera_canvas).then(res => {
            // video.play();
            if(res.length == 0) return;
            for (var i = 0; i < res.length; i++) {
                var score = parseInt(res[i]["score"] * 100 ,10);
                drawRect(context, res[i].bbox[0], res[i].bbox[1], res[i].bbox[2], res[i].bbox[3], score)
                drawName(context, res[i]["class"], res[i].bbox[0], res[i].bbox[1], score);
            }
        });
    }
};
function drawRect(ctx, x, y, w, h, score) {
    ctx.beginPath();
    ctx.rect(parseInt(x, 10), parseInt(y, 10), parseInt(w, 10), parseInt(h, 10));
    ctx.lineWidth = 7.5;
    ctx.strokeStyle =  score < 75 ? "rgb(255, 255, 0)" : "rgb(50, 240, 60)";
    ctx.stroke();
    ctx.closePath();
}

function drawName(ctx, text, x, y, score) {
    ctx.beginPath();
    ctx.fillText(text + " : " + score + "%", parseInt(x, 10), parseInt(y, 10));
    ctx.fillStyle = "red";
    ctx.font = "10px serif";
    ctx.closePath();
}

・navigator.mediaDevices.getUserMedia(19行目付近)で、スマホのカメラを起動させます。
cocoSsd.load()(39行目)で、物体のモデル情報を獲得し、250ミリ秒毎に物体の検出処理をするよう、setIntervalしています。
・camera_context.drawImage(47行目)でカメラの情報を一旦camera_context(id=camera_canvas)に描きこみを行い、model.detect(camera_canvas)(48行目)で、物体検出の処理をしていますmode.detect関数で物体検出をするためには、パラメータにcanvasやimageが必要です。
・物体検出ができた場合、drawRectとdrawNameで、canvas(id=canvas)に物体検出した場所に四角の枠と名前などを表示します。

③. style.cssの作成

#container {
    width:100vw;
    min-height:100vh
}

#wrapper {
    position: relative;
    width: 100%;
    height: 100%;
}

#camera {
    position: absolute;
    z-index: 2;
}

#canvas {
    position: absolute;
    left: 0px;
    top: 0px;
    z-index: 3;
}

#camera_canvas {
    position: absolute;
    left: 0px;
    top: 0px;
    z-index: 1;
}

・画面いっぱいにコンテンツが見れるように設定しています。
z-indexで、上から#canvas⇒#camera(videoタグ)⇒camera_canvas(上2つに覆いかぶさって、人の目では見えない、物体検出用のcanvas)になっています。

実行

スマホで、GoogleChromeを使ってアクセスすると、以下のようになります。

以上です。

コメント

タイトルとURLをコピーしました