obnizで作る顔検出扇風機

今回やること

iPhoneのカメラをつかってOpenCVで顔を検出し,その方向に扇風機を向けます。

必要なもの

IMG_0009.JPG

組み立て方

ハードウェアの組み立ては3ステップです。

  1. obnizとサーボモーターをつなぐ
  2. USBファンにUSB↔ピンヘッダ変換基板をつなぎ,さらにそれをobnizにつなぐ
  3. obnizを電源につなぐ

obnizが1Aまで出力できるので、モータードライバを別に用意せずにモーターをマイコンに直挿しすることができてとても楽です。

fan2.png

プログラム

htmlの機能として、PCやスマホのカメラを使うというのがあるので、こちらを使います。
カメラから取得したデータから、画像認識ライブラリOpenCV.jsを使って顔を検出して、その顔の方向にサーボを動かします。

iphoneFan.png

 

プログラムは少し複雑です。3ステップにわけて説明します。

  1. HTML5 MultiMediaをつかってカメラを取得する
  2. OpenCVにカメラデータを入れ,顔を検出する
  3. 顔位置の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>
スクリーンショット 2018-05-23 16.31.42.png

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

完成したプログラム