6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【忙しい現代人のために】表情で扇風機を操作するシステムを作ったよ

Last updated at Posted at 2020-08-19

猛暑です。

机で仕事、勉強するのに、扇風機はかかせません。

しかしながら、扇風機のスイッチのオンオフには、仕事や勉強を一時中断しないといけないという問題があります。

そんな1分1秒でも惜しい現代人へ、
顔の表情で扇風機を操作するシステムを作りました。

##作ったもの


**暑くてつらそうな顔**をしてるときに、モーターが回ります。

なぜか笑顔で消えます。


##必要なもの

obniz board
DCモーター
・プロペラ

##使用技術

###システム構成図
system.jpg

###顔認証について
face-api.js というTensorFlow.jsで学習済みの顔認識の機械学習モデルが使えるライブラリを利用しました。
本件では、顔の検出、顔のランドマーク付、表情分析で活用しました。

なお、face-api.jsは、サービスアカウント不要、アクセストークン・シークレットキー不要、無料で手軽に利用できます。


1.導入手順
・ 以下のリンクからZIPファイルをダウンロード (Cloneでも可)
https://github.com/justadudewhohacks/face-api.js/
・ distフォルダにある face-api.js を自分のアプリと同じディレクトリに移動する
・ weightsフォルダの名前を modelsに変更し、自分のアプリと同じディレクトリに移動する

models
face-api.js
index.html
script.js

2.判定方法
映像から以下の7つの表情の度合を返してくれます。
angry(怒り)、disgusted(うんざり)、fearful(恐れ)、happy(幸せ)、neutral(ニュートラル)、sad(悲しみ)、surprised(驚き)

いろいろ試した結果、neutral(ニュートラル)の数値が0.9以下で判定するのが、今のところベターでした。
扇風機の電源を消すには、なんでもよかったのですが、ハッピーな顔(happy指数が0.3超)というところに落ち着きました。

###obniz

obniz公式のモーターを動かすサンプルを使いました。
https://obniz.io/ja/sdk/parts/DCMotor/README.md

##コード
###パソコン側(顔認証)
ほとんどこちらのチュートリアルどおりで、obnizへPOST通信する処理を付け加えました。
https://github.com/WebDevSimplified/Face-Detection-JavaScript

index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>表情で扇風機をまわすシステム</title>
  <script defer src="face-api.js"></script>
  <script defer src="script.js"></script>
  <style>
    body {
      margin: 0;
      padding: 0;
      width: 100vw;
      height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
    }
    canvas {
      position: absolute;
    }
  </style>
</head>
<body>
  <video id="video" width="720" height="560" autoplay muted></video>
</body>
</html>
script.js
const video = document.getElementById('video')

Promise.all([
  faceapi.nets.tinyFaceDetector.loadFromUri('/models'),
  faceapi.nets.faceLandmark68Net.loadFromUri('/models'),
  faceapi.nets.faceRecognitionNet.loadFromUri('/models'),
  faceapi.nets.faceExpressionNet.loadFromUri('/models')
]).then(startVideo)

function startVideo() {
  navigator.getUserMedia(
    { video: {} },
    stream => video.srcObject = stream,
    err => console.error(err)
  )
}

video.addEventListener('play', () => {
  const canvas = faceapi.createCanvasFromMedia(video)
  document.body.append(canvas)
  const displaySize = { width: video.width, height: video.height }
  faceapi.matchDimensions(canvas, displaySize)

  fan_status = 0
  send_obniz = 0

  setInterval(async () => {
    //1つの顔だけなのでfaceapi.detectAllFacesではなくて detectSingleFaceでよいはずが、本件はdetectAllFacesを使った。
    const detections = await faceapi.detectAllFaces(video, new faceapi.TinyFaceDetectorOptions()).withFaceLandmarks().withFaceExpressions()
    
    const resizedDetections = faceapi.resizeResults(detections, displaySize)
    canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height)
    faceapi.draw.drawDetections(canvas, resizedDetections)
    faceapi.draw.drawFaceExpressions(canvas, resizedDetections)

    console.debug("resizedDetections",resizedDetections)
    if (resizedDetections[0] != null) {
       let happy = resizedDetections[0].expressions.happy
       let neutral = resizedDetections[0].expressions.neutral
       
       console.debug("neutral",neutral,"happy",happy)

       //表情がニュートラルじゃないときに扇風機をつける
       if (fan_status == 0 && neutral < 0.9) {
         fan_status = 1
         send_obniz = 1
        }
        //表情がハッピーのときに扇風機を消す
        if (fan_status == 1 && happy > 0.3) {
          fan_status = 0
          send_obniz = 1
         }

         //obnizクラウドへPOST
         if (send_obniz == 1){
          send_obniz = 0
          let value=[{"value":fan_status}];
          const url="https://obniz.com/events/0000/XXXXXXXXXXXXXX/run"; //ここにobnizのURLを入力
 
          Promise.all(post(value,url))   
           .then((result) => {})
           .catch((result) => {})       
         }  
    }
  
  }, 3000)

})

//POST通信  ここのを丸写し https://www.it-swarm.dev/ja/javascript/%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%81%AA%E3%81%97%E3%81%A7post%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E9%80%81%E4%BF%A1%E3%81%99%E3%82%8B%E7%B4%94%E7%B2%8B%E3%81%AAjavascript/972618857/
function post(value,url) {
  let xhr = new XMLHttpRequest();
  xhr.open("POST", url, true);
  xhr.setRequestHeader('Content-Type', 'application/json');
  xhr.send(JSON.stringify({
    value: value
  }));
}

###oznizクラウド側

index.html
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
    />
    <link rel="stylesheet" href="/css/starter-sample.css" />
    <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
    <script
      src="https://unpkg.com/obniz@3.7.1/obniz.js"
      crossorigin="anonymous"
    ></script>
  </head>
  <body>
    
    <script>
      const obniz = new Obniz("XXXX-YYYY");//obnizのID

      obniz.onconnect = async function() {

        let motor = obniz.wired("DCMotor", {forward:0, back:1});

        if (req.body.value[0].value=="1") {
          obniz.display.clear();
          obniz.display.print("ON");
          motor.forward();
        }
        if (req.body.value[0].value=="0") {
          obniz.display.clear();
          obniz.display.print("OFF");
          motor.stop();
        }

      }

    </script>
  </body>
</html>


##余談

当初は、以下の写真のように、実際の卓上扇風機を分解してobnizと接続しました。
obniz 5V・170mA、卓上扇風機5V・350mA、扇風機を動かすには電力不足なので、単3乾電池を2本つなげました。


動いたからヨシ!と思ったら、

Twitterで、ある方から

と、ご指摘いただきました。
ご親切にありがとうございました。

動いたら ヨシ!ではなくて、
動いたからといって良くないことが分かりました。

また、別の方からFacebookで、理屈とブレッドボードの実装方法について書かれているリンク先を教えていただきました。

ありがとうございました。
いろいろな方が私のようなド素人に教えていただき、SNSって本当にいいものですね。

ただ、トランジスタが手元にないので、とりあえず今回はモーターと簡易なプロペラにしました。

卓上扇風機の制御については、トランジスタを入手して、もし成功できたら、記事にしたいと思います。


6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?