やりたいこと
ラズパイ・Webカメラ・hubot・Slackを組み合わせることで、Slackからの指示でラズパイに写真を撮らせてSlackに投稿することまではできた。詳細は前回までのQiitaを参照
今回はこの仕組みに動体検知を加えることで、Slackに写真撮影の指示を出さなくても、ラズパイ側で動体検知をトリガーにして写真を撮影し、撮影した写真をSlackに通知させたい。
また、ラズパイからSlackへの動体検知の通知はhubotから行わせたいという点もポイントである。
ハードウェア
- Raspberry Pi 2 Model B
- Logicool Webcam C270
ソフトウェア
- hubot
- motion
前提
- ラズパイにWebカメラを接続して写真を撮影できること
- ラズパイにhubotをインストールしてセットアップが済んでいること
- Slackにラズパイのhubotを連携させていること
- Slackをトリガーにしてラズパイで写真を撮影するところまで実現できていること(前回までの流れを参照)
ラズパイの環境構築
motionのインストール
以下のコマンドでラズパイにmotionというソフトウェアをインストールする。
sudo apt-get install -y motion
motionの設定
motionのインストールが終わったら、motionの設定ファイル(/etc/motion/motion.conf)を書き換える。
※管理者ユーザでないと上書きできないのでsudoで開く
$ sudo nano /etc/motion/motion.conf
motionの自動起動OFF
デフォルトでは自動起動がONになっている場合もあるので、OFFになっているか確認。
daemon off
動画撮影機能OFF
動画の撮影はしないので、動画出力はOFFにしておく。
ffmpeg_output_movies off
ファイル書き出し先の指定
motionで撮影した写真の保存先を指定する。
※環境によって異なると思うので好きな場所を指定
target_dir /home/pi/Pictures/motion
動作確認
motionの起動
motionを起動して写真が撮影されるか確認する。
$ sudo motion -c /etc/motion/motion.conf
撮影された写真
カメラが監視してる画像に変化が起きる度に撮影しているので間違った動作ではないが、これでは人が通りがかるだけで何枚も写真が撮られて、Slackに激しく通知されそう。。。
motionの停止
Ctrl+C
で停止する。
改善その1:撮影する写真の数を抑えたい
上記の動作確認を踏まえて、motion.confの設定を改善してみる。
まずは動体検知をしたときに大量の写真が撮影されてしまうのをどうにかする。
ファイル書き出し先の変更
motionの設定によってはものすごい数の画像ファイルが作成されるので、画像の出力先がSDカードのままだとSDカードが劣化してしまう恐れがあるらしい。
なのでラズパイのtmpメモリ領域に出力するように変更する。
target_dir /tmp/motion
写真撮影の設定を変更
写真撮影はデフォルトではon
になっているが、これでは動体検知をした全フレームで写真を保存されてしまう。
この設定をbest
に変更すると最も大きく変化したフレームのみ画像ファイルとして保存してくれるようになる。
output_pictures best
ただし、この設定を変えただけだと画像は大量に保存されなくなるが、画像の保存まで妙に時間がかかるようになる。
そのため、event_gap
でイベントの間隔を狭める必要がある。
イベントの間隔を設定
動体検知が起きてから、次の動体検知をするまでの間隔を設定する。
デフォルトでは60になっているが、このままだと60秒に1枚しか写真が撮影されない。
(動体を検知してから、60秒間で一番大きく変化したフレームを画像ファイルとして保存するので、動体検知から画像保存まで60秒は時間がかかる)
それほどリアルタイムでなくてもいいのであれば60のままでもいいし、もう少し早く画像を保存してほしいのであればもう少し時間を縮めてもよいと思う。
今回は10秒に設定してみた。
event_gap 10
改善その2:写真を撮影したらhubotに通知してほしい
動体検知をしたら写真を撮影するところまでできたが、これだけだと画像が保存されるだけでSlackには通知できない。
なので、次は写真撮影が済んだらラズパイに常駐させているhubotに通知するようにする。
ラズパイにhubotを常駐させる方法は過去の投稿を参照。
motionには動体検知で画像を保存したときに任意のシェルスクリプトを実行できるオプションon_picture_save
が用意されているので、これを利用する。
hubotでPOST-APIを提供するスクリプトを作成
hubotにはHTTPリクエストを受け付けてチャットに発言する機能があるので、この機能を利用して動体検知で撮影した写真をSlackにアップロードしてもらう。
hubotを通さず、シェルスクリプトを利用してSlack APIで直接画像をアップロードする方法もあるが、せっかくラズパイにhubotを常駐させているのならhubotから通知してもらう方がスマートだと思う。
hubotはrobot.router.post
を使用することでPOSTリクエストを受け付けることができるのでこれを利用する。
request = require "request";
fs = require "fs";
path = require "path";
module.exports = (robot) ->
robot.router.post "/api/v1/upload-photo", (req, res) ->
# リクエストが空なら何もせずにレスポンスを返す
if !req.body
res.status(500).send("エラーが発生しました")
else
roomName = req.body.room_name
filePath = req.body.file_path
# リクエストで指定された写真をSlackへアップロードする
api_url = "https://slack.com/api/"
options = {
token: process.env.HUBOT_SLACK_TOKEN,
filename: path.basename("#{filePath}"),
file: fs.createReadStream("#{filePath}"),
channels: roomName
}
request.post {url:api_url + 'files.upload', formData: options}, (error, response, body) ->
if !error && response.statusCode == 200
res.status(200).send("写真投稿完了")
else
res.status(500).send("エラーが発生しました")
hubotに向けてPOSTリクエストを実行するシェルスクリプトの作成
motionから直接POSTリクエストを出すことはできないので、hubotにPOSTリクエストを送るシェルスクリプトを作成する。
curlコマンドでPOSTリクエストを送るにはいろいろな書き方があるようだが、以下のページを参考にさせていただいた。
以下の情報は再利用性を高めるためにシェルスクリプトには書かず、引数で渡すようにしている。
- Slackのチャンネル名(ルーム名)
- Slackにアップロードするファイルのフルパス
- hubotのPOST-APIのURL
#!/bin/bash
room_name_val=${1}
file_path_val=${2}
url=${3}
echo "room_name: "${room_name_val}
echo "file_path: "${file_path_val}
echo "url: "${url}
curl --data-urlencode "room_name=${room_name_val}" --data-urlencode "file_path=${file_path_val}" $url
motion.confの設定を変更
on_picture_save
に画像保存時に実行するシェルスクリプトの名前を指定する。
このとき、%f
には保存した画像ファイルのフルパス名が入っているので、シェルスクリプトに引数として渡す。
シェルスクリプトは以下の3つを引き数として受け取るようにしていたので、ここで実際の値を指定しておく。
- Slackのチャンネル名(ルーム名)
- Slackにアップロードするファイルのフルパス(
%f
を指定) - hubotのPOST-APIのURL
# Command to be executed when a picture (.ppm|.jpg) is saved (default: none)
# To give the filename as an argument to a command append it with %f
; on_picture_save value
on_picture_save sh /home/pi/hubot/pibot/sh/post_photo.sh [Slackのチャンネル名] %f [hubotのPOST-APIのURL]
動作確認
ラズパイでmotionを起動し、カメラの前に手をかざすなどして動体を検知させる。
動体検知に合わせて、シェルスクリプトが実行されること(シェルスクリプト内にecho
を仕込んでおけばわかりやすい)、さらにhubotからSlackに画像の投稿が行われることを確認する。