daikichi blog

本、技術、その他

Webカメラ+画像認識で生活をルーティン化する

なんかIoTってやつで生活を豊かにしたいのだが、7畳のワンルームでは全てのスイッチやリモコンは3歩歩けば届く距離にあり、解決すべき課題が出てこないなーとスマートスピーカーなどにも手が出ないでいた。

ところが自宅でテレビ会議してる際に自室全体をカメラで写せるということに気づき、画像による監視でなにかできないかなと思い立つ。

そこで、掲題の実装を試す。

流れ

  • マシンに接続したカメラで写真を撮影する
  • 画像認識で写真に写っているアイテムを判別する
  • ルーティンをスケジュールする
  • ルーティン時に必要なものが写っていなかったらアラート

マシンに接続したカメラで写真を撮影する

(個人的に)手っ取り早いのでNode.jsで。

www.npmjs.com

var NodeWebcam = require( "node-webcam" );

// NodeWebcam.create().list() で調べられる
const CAMERA_NAME = 'FaceTime HDカメラ(内蔵)';
const TEMP_PHOTO_FILE = 'temppicture';

var opts = {
    width: 1280,
    height: 720,
    quality: 100,
    frames: 60,
    delay: 2, // 変更
    saveShots: true,
    output: "jpeg",
    device: CAMERA_NAME,
    callbackReturn: "location",
    verbose: false
};


const camera = NodeWebcam.create(opts)

function capturePhoto(){
  camera.capture(TEMP_PHOTO_FILE, (err, data)=>{
    if(err) return console.error(err);
    console.log(data); // 'temppicture.jpg'
  });
}

delayのところはデフォルトの0から2に変更している。 0だと撮影された結果が真っ暗だった。FaceTimeカメラの明度の自動調整が完了していなかった?

画像認識で写真に写っているアイテムを判別する

これもサービス・ライブラリは何でも良いと思う。 以前利用したことのあるのでGCPvision APIを利用する。 cloud.google.com

// 環境変数GOOGLE_APPLICATION_CREDENTIALは設定済みとする
const vision = require('@google-cloud/vision');
const client = new vision.ImageAnnotatorClient();

async  function capturePhotoAndDetectLabels(){
  return new Promise((resolve, reject)=>{
    camera.capture(TEMP_PHOTO_FILE, (err, data)=>{
      if(err) return reject(err);
      client.labelDetection(`./${data}`, (err, result)=>{
        if(err) return reject(err);
        resolve(result.labelAnnotations);
      });
    });
  })
}

たとえばこんな感じの写真が撮影できると

f:id:ringo-mogire-beeeeeeeeeeam:20200521225134j:plain
左に写ってる青いのはyogibo MAXです。オススメです。
labelAnnotationsのdescriptioは

Vacuum cleaner
Leg
Floor
Flooring

となる。

「毎朝掃除機をかける」とかであれば、このVacuum cleanerを検知してあげる

ルーティンをスケジュールする

node scheduleでググって一番上に出てきたのを使う。

www.npmjs.com

10分間、1分毎にチェックする。 写真の撮影の問題もあるので頻度はもう少し多めにしたほうが良いと思う。

const schedule = require('node-schedule');
const async = require('async');

// todo: ルーティンを増やしたらconfig化する
schedule.scheduleJob('0 9 * * *',()=>{
  async.retry({times:10, interval: 60000}, cb =>{
    capturePhotoAndDetectLabels().then(labels=>{
      if(labels.some(label=>label.description==='Vacuum cleaner')) return cb(null, true);
      cb(new Error('retry'));
    });
  }, (err, result)=>{
    if(result) return;
    // 10分間の間に作業が撮影できてないのでアラート
  });
});

ルーティン時に必要なものが写っていなかったらアラート

アラートというか、早い話

マシンからけたたましい音が鳴るとかが手っ取り早くていいと思う。

自分は部屋の照明にPhilips Hueを導入しているので、せっかくなのでIoTっぽくこれをいじってみる。

API調べるのめんどくさいのでZapierで設定しちゃう f:id:ringo-mogire-beeeeeeeeeeam:20200522004908p:plain

schedule.scheduleJob('0 9 * * *',()=>{
  async.retry({times:10, interval: 60000}, cb =>{
    capturePhotoAndDetectLabels().then((labels:Array<{description:string}>)=>{
      if(labels.some(label=>label.description==='Vacuum cleaner')) return cb(null, true);
      cb(new Error('retry'));
    });
  }, (err, results)=>{
    if(results) return;
    // アラート
    request('https://hooks.zapier.com/hooks/catch/xxxxxxxxx/yyyyyyy/');
  });
});

わかりづらいが、部屋の中が赤くなる

以上で完了。 多分撮影間隔とかは精度とVision APIの課金とバランスをとりつつ調整が必要か。

あとラズパイとかでスタンドアロンで動くようにしたいのと、罰のバリエーションをもたせてPavlok shock clockとか使ってみたい

gigazine.net