asoview! TECH BLOG

アソビュー株式会社のテックブログ

canvasを使って動体感知。"あの"犯人(犬)を捕まえた!

犬アレルギーだけど犬が大好きな相原(@raihara3)です。

実家で定期的に起こる、ワンコのとある事件の犯人を捕まえるべく
canvasを活用して簡単につくった監視ツールの紹介です。

その名も「トイレ警察24時
名前の通り、トイレの話です。
汚い表現は極力控えていますが、苦手な方はご注意ください。


実家には現在3匹のワンコがいます。
そこで起こるワンコの事件というのは、
犬あるある「他の子の排泄物を口にしてしまう子」がうちにもいるんです...
母性本能で良い事なんですが、みんな成犬なのでやめて欲しい...

犯人はもう分かっています。(なぜ分かったかは省略)
でも、いつも人間の目を盗んで犯行を行う為証拠もなく、注意できずにいます。

そこで思いついたのが「トイレ警察24時」

概要

ざっくり。

f:id:raihara3a:20200113193334p:plain

(3)で差分がなければ(1)(2)を繰り返し、
差分がある場合は、被疑者が近づいては危ないので(4)(5)のような流れになります。
(差分 = 排泄物あり)

仕組み

※この先折り紙を丸めたものが排泄物として登場します。本物ではありません。

通常時の情報を取得

f:id:raihara3a:20200113193611p:plain
監視スタートして、15秒後に現在の状態を基準として取得します

setTimeout(() => {
  standardStatus = document.getElementById('toiret-canvas').getContext('2d').getImageData(0, 0, canvasSize.width, canvasSize.height).data;
}, 15000)

モーション検出

Diffy.jsというライブラリを使用しました。
フレームを少しずらしたWebカメラのスナップショットを取得し、
ハイコントラストの差分があれば"モーション検出"される仕組みです。

f:id:raihara3a:20200113193714p:plain
トイレに末っ子がやってきました。

import { create } from 'diffy.min.js';

const resolution = {
  x: 20,
  y: 15
}

let diffy = create({
  resolution: { x: resolution.x, y: resolution.y },
  sensitivity: 0.2,
  threshold: 21,
  debug: true,
  containerClassName: 'diffy-container',
  sourceDimensions: { w: 250, h: 200},
  onFrame:(matrix) => {
    matrix.forEach((row) => {
      const notWhiteCount = row.filter((color) => {return color !== 255}).length;
      if(notWhiteCount > resolution.x / 2.5) {
        // モーション検出
      }
    });
  }
});

window.diffy = diffy;

今回だとresolutionで指定している20 x 15に映像を分割し、
matrixに白〜黒のカラーコードが配列で渡されます。差分がなければ白が入ります。

モーション検出の条件に設定している割合は、
カメラからの距離や映り込む被写体の大きさによって前後すると思うので要調整です。

差分検出

動きがなくなった5秒後に最初の状態と変わりないか確認します。

const currentStatus = document.getElementById('toiret-canvas').getContext('2d').getImageData(0, 0, canvasSize.width, canvasSize.height).data;

for(let i = 0; i < currentStatus.length; i++) {
  if(standardStatus[i] - currentStatus[i] < -30 || 30 < standardStatus[i] - currentStatus[i]) {
    new Audio('chime.mp3').play();
    hasAbnormal = true;
    return;
  }
}

dataプロパティで取得したRGBAから+-30を許容値としました。
これは白のトイレシートと排泄物の明暗差からざっくり決めました。

f:id:raihara3a:20200113193824p:plain

差分が検出されると人間に向けた通知音を流します。

この段階で近くに人間がいて片付けができればいいのですが、必ずいるとも限らないので...

再度モーション検出

今が取り締まる瞬間です!
hasAbnormal = trueの状態でモーション検出すると、音と共にcanvasから写真のダウンロードリンクを生成します。
音といっても、あまりビックリさせても良くないので知らない犬の鳴き声にしてみました。
(これでもかなり反応する)

new Audio('dog.mp3').play();

const canvas = document.getElementById('toiret-canvas');
const aTag = document.createElement('a');
aTag.innerText = new Date();
aTag.download = new Date();
aTag.href = canvas.toDataURL("image/jpeg");
document.getElementById('evidence-box').appendChild(aTag);

この音に気を引かれてやめてくれるか、人間が駆けつけて止めるか、です。
被疑者の長女がやってきました。

f:id:raihara3a:20200113194043p:plain

しっかり音のする方を気にしてくれてます。

もし止めることができなくても、リンクから画像をダウンロードすればしっかり証拠が手に入ります。

f:id:raihara3a:20200113194216p:plain

排泄物がある状態ではモーションを検出している間、5秒おきに写真を撮り続けます。
その為ただトイレをしにきただけだった、という場合もちゃんと確認できます。冤罪防止。

しかし、いくら折り紙を丸めたと言ってもちょっとリアルだったかな...

さいごに

ワンコに後から注意することはできませんが、
これで「人間は見てないはずなのにバレる違和感」を感じてもらえればと思います...笑

毎回人間の目を盗んで犯行を行うなんて賢いけど、しっかり撮ってるからね...

f:id:raihara3a:20200113194311p:plain

これから取り締まっていこうと思います。