obnizで作る顔検出扇風機
コンテンツ
今回やること
iPhoneのカメラをつかってOpenCVで顔を検出し,その方向に扇風機を向けます。
必要なもの
- スマートフォン(この例ではiPhoneを使いますが,Androidでも大丈夫です)
- obniz Board
- サーボモーター
- 小型USBファン
- USB↔ピンヘッダ変換基板
- ピンヘッダ
- ケーブル
- USBケーブル
- USBアダプタ(もしくはモバイルバッテリー)
組み立て方
ハードウェアの組み立ては3ステップです。
- obnizとサーボモーターをつなぐ
- USBファンにUSB↔ピンヘッダ変換基板をつなぎ,さらにそれをobnizにつなぐ
- obnizを電源につなぐ
obnizが1Aまで出力できるので、モータードライバを別に用意せずにモーターをマイコンに直挿しすることができてとても楽です。
プログラム
htmlの機能として、PCやスマホのカメラを使うというのがあるので、こちらを使います。
カメラから取得したデータから、画像認識ライブラリOpenCV.jsを使って顔を検出して、その顔の方向にサーボを動かします。
プログラムは少し複雑です。3ステップにわけて説明します。
- HTML5 MultiMediaをつかってカメラを取得する
- OpenCVにカメラデータを入れ,顔を検出する
- 顔位置のX座標を元にサーボモータを回す
1. HTML5 MultiMediaをつかってカメラを取得する
videoタグがあるので、それを使用します。navigator.getUserMedia
関数を使って、カメラの許可をユーザーに求め、許可されればコールバックでstream
がもらえますので、videoタグのプロパティに設定します。
これで,ボタンを押したらカメラが起動し、HTML上に表示されます。
表示されない場合は、HTTPで通信していないか確認して下さい、
セキュリティの関係上、HTTPSのサイトでしかカメラが取れない ようです。
<video id="videoInput" autoplay playsinline width=320 height=240> <button id="startAndStop">Start</button> <script> let videoInput = document.getElementById('videoInput'); let startAndStop = document.getElementById('startAndStop'); startAndStop.addEventListener('click', () => { if (!streaming) { navigator.mediaDevices = navigator.mediaDevices || ((navigator.mozGetUserMedia || navigator.webkitGetUserMedia) ? { getUserMedia: function (c) { return new Promise(function (y, n) { (navigator.mozGetUserMedia || navigator.webkitGetUserMedia).call(navigator, c, y, n); }); } } : null); if (!navigator.mediaDevices) { console.log("getUserMedia() not supported."); return; } const medias = { audio: false, video: { facingMode: "user" } }; navigator.mediaDevices.getUserMedia(medias) .then(function (stream) { streaming = true; var video = document.getElementById("videoInput"); video.src = window.URL.createObjectURL(stream); video.onloadedmetadata = function (e) { video.play(); onVideoStarted(); }; }) .catch(function (err) { console.error('mediaDevice.getUserMedia() error:' + (error.message || error)); }); } else { utils.stopCamera(); onVideoStopped(); } }); function onVideoStarted() { startAndStop.innerText = 'Stop'; // ... } function onVideoStopped() { startAndStop.innerText = 'Start'; // ... } </script>
2. OpenCVにカメラデータを入れ,顔を検出する
OpenCVのページにサンプルがあるので、ほぼそれを流用します。haarcascade_frontalface_default.xml
が顔の情報が入っているデータで、コレを使うことで顔を認識しています。
出力はcanvasで、以下のように顔が赤く枠で囲われた写真が表示されます。
<canvas id="canvasOutput" width=320 height=240 style="-webkit-font-smoothing:none"> <script src="https://docs.opencv.org/3.4/opencv.js"></script> <script src="https://webrtc.github.io/adapter/adapter-5.0.4.js" type="text/javascript"></script> <script src="https://docs.opencv.org/3.4/utils.js" type="text/javascript"></script> <script> let streaming = false; function onVideoStopped() { streaming = false; canvasContext.clearRect(0, 0, canvasOutput.width, canvasOutput.height); startAndStop.innerText = 'Start'; } let utils = new Utils('errorMessage'); let faceCascadeFile = 'haarcascade_frontalface_default.xml'; utils.createFileFromUrl(faceCascadeFile, 'https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_default.xml', () => { startAndStop.removeAttribute('disabled'); }); async function start() { let video = document.getElementById('videoInput'); let src = new cv.Mat(video.height, video.width, cv.CV_8UC4); let dst = new cv.Mat(video.height, video.width, cv.CV_8UC4); let gray = new cv.Mat(); let cap = new cv.VideoCapture(video); let faces = new cv.RectVector(); let classifier = new cv.CascadeClassifier(); let result = classifier.load("haarcascade_frontalface_default.xml"); const FPS = 30; function processVideo() { try { if (!streaming) { // clean and stop. src.delete(); dst.delete(); gray.delete(); faces.delete(); classifier.delete(); return; } let begin = Date.now(); // start processing. cap.read(src); src.copyTo(dst); cv.cvtColor(dst, gray, cv.COLOR_RGBA2GRAY, 0); // detect faces. classifier.detectMultiScale(gray, faces, 1.1, 3, 0); // draw faces. for (let i = 0; i < faces.size(); ++i) { let face = faces.get(i); let point1 = new cv.Point(face.x, face.y); let point2 = new cv.Point(face.x + face.width, face.y + face.height); cv.rectangle(dst, point1, point2, [255, 0, 0, 255]); } cv.imshow('canvasOutput', dst); // schedule the next one. let delay = 1000 / FPS - (Date.now() - begin); setTimeout(processVideo, delay); } catch (err) { console.error(err); } }; // schedule the first one. setTimeout(processVideo, 0); } </script>
3. 顔位置のX座標を元にサーボモータを回す
一番電子工作らしいところですね。
ここのプログラムはそれほど長くありません。
USBとサーボモーターをどこに繋いだか、サーボモーターの角度をどれ位にするか しか殆ど書いてないです。
new Obniz("OBNIZ_ID_HERE");
とあるのが自分の手持ちのobnizとの接続部分です。
OBNIZ_ID_HEREのところに自分のobniz ID(8ケタの数字)を入れて接続します。
let obniz = new Obniz("OBNIZ_ID_HERE"); let servo; obniz.onconnect = async () => { obniz.display.print("ready") var usb = obniz.wired("USB" , {gnd:11, vcc:8} ); usb.on(); servo = obniz.wired("ServoMotor", {signal:0,vcc:1, gnd:2}); } if(/* when detect face */){ servo.angle(xPos * 180 / 320); }