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