はじめに
Raspberry Piとカメラモジュール等の部品を組み合わせて、監視カメラを作りました。
焦電型赤外線センサーで検知を行い、赤外線検知時に画像を保存します。また、Flaskを使用してブラウザからモニタリングができます。
焦電型赤外線センサーは、周囲の人や動物から発せられる赤外線(熱)を検知します。また、動きのある熱源に反応して出力を行います。最大検知距離は気温等の環境条件により、8mほどです。
本記事は、Raspberry Piとカメラモジュール並びに焦電型赤外線センサーを使用したレシピです。
コンセプトは手間をかけることなく全て、Pythonで作ることをテーマに作りました。
本プログラムは全てPythonでできています。
htmlファイルもPythonで生成しています。
全体概要
フロントはFlaskを使用して、表示するhtmlファイルはPythonで生成しています。
焦電型赤外線センサーで検知時に、画像はFlaskの静的ファイルディレクトリであるstaticディレクトリに保存します。また、合わせてファイル名をSQLiteのデータベースに保存し、htmlファイル内では最新20件のファイル名を取り出してテーブル表示させています。
ポイントは、保存した画像のファイル名を取り出すときにURLを指定してアクセスできるように実装しました。本プログラムは100行未満で、生成されるhtmlファイルもわずか約4KB程度です。
ブラウザよりファイル名を選択してクリックすると、http:///static/にアクセスすることでFlaskのstaticディレクトリ以下の画像を参照します。
構成
必要な部品は以下になります。
- 部品一覧
| 部品 | 数量 |
|:---------|:-----------|:-----------|
|Raspberry Pi PiNoir Camera V2 |1個 |
|焦電型赤外線センサー |1個 |
|ジャンパー線(メス-メス) |3本 |
プログラム
Flaskを起動するプログラム(Flask.py)を実行するディレクトリに、templatesとstaticディレクトリを作成します。
デーベース処理は、以前書いたRaspberry Piで計測したデータをグラフ化 全部のせPython(SQLite + Bokeh + Flask)で作るグラフアプリより流用しました。
camera.py
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
import picamera
import time
import RPi.GPIO as GPIO
import sqlite3
import datetime
PICTURE_WIDTH = 800
PICTURE_HEIGHT = 600
SAVEDIR = "/home/pi/python/static/"
INTAVAL = 10
SLEEPTIME = 5
SENSOR_PIN = 9
GPIO.cleanup()
GPIO.setmode(GPIO.BCM)
GPIO.setup(SENSOR_PIN, GPIO.IN)
cam = picamera.PiCamera()
cam.resolution = (PICTURE_WIDTH,PICTURE_HEIGHT)
st = time.time() - INTAVAL
# データベースに書き込み
def sqlite_insert():
dbname = '/home/pi/python/monitoring.db'
con = sqlite3.connect(dbname)
cur = con.cursor()
output_time = datetime.datetime.now()
output_time = "{0:%Y-%m-%dT%H:%M:%SZ}".format(output_time)
data = (output_time, (filename))
cur.execute('insert into monitoring (date, filename) values (?,?)', (data))
con.commit()
con.close()
# データベースから取り出し
def sqlite_select():
dbname = '/home/pi/python/monitoring.db'
con = sqlite3.connect(dbname)
cur = con.cursor()
cur.execute('SELECT filename FROM monitoring order by date desc limit 20')
global fn
fn = [(y[0]) for y in cur.fetchall()]
con.close()
# htmlファイル生成
def html_create():
url = "http://<IP Address>:5000/static/"
with open("/home/pi/python/templates/monitoring.html", "w") as file:
file.write("<!doctype html>")
file.write("<html lang=\"en\">")
file.write("<head>")
file.write("<meta charset=\"utf-8\">")
file.write("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">")
file.write("<link rel=\"stylesheet\" href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css\" integrity=\"sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO\" crossorigin=\"anonymous\">")
file.write("<title>Monitoring system</title>")
file.write("</head>")
file.write("<body>")
file.write("<h1 class=\"display-4\">Monitoring system</h1>")
file.write("<div class=\"alert alert-warning\" role=\"alert\"><input type=\"button\" value=\"Reload\" class=\"btn btn-info\" onclick=\"window.location.reload(true);\"> A simple warning alert—check it out!</div>")
file.write("<table class=\"table table-striped\">")
file.write("<thead class=\"thead-dark\">")
file.write("<tr>")
file.write("<th scope=\"col\">Filename</th>")
file.write("</tr>")
file.write("</thead>")
file.write("<tbody>")
for i in fn:
file.write("<tr>")
file.write("<th scope=\"row\"><a href=" + url + i + ">" + i + "</a></th>")
file.write("</tr>")
file.write("</tbody>")
file.write("</table>")
file.write("<script src=\"https://code.jquery.com/jquery-3.3.1.slim.min.js\" integrity=\"sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo\" crossorigin=\"anonymous\"></script>")
file.write("<script src=\"https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js\" integrity=\"sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49\" crossorigin=\"anonymous\"></script>")
file.write("<script src=\"https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js\" integrity=\"sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy\" crossorigin=\"anonymous\"></script>")
file.write("</body>")
file.write("</html>")
while True:
if (GPIO.input(SENSOR_PIN) == GPIO.HIGH) and (st + INTAVAL < time.time()):
st = time.time()
filename = time.strftime("%Y%m%d-%H:%M:%S") + ".jpg"
save_file = SAVEDIR + filename
cam.capture(save_file)
sqlite_insert()
sqlite_select()
html_create()
time.sleep(SLEEPTIME)
Flask.py
#! /usr/bin/env python3
# _*_ coding: utf-8 _*_
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def index():
return render_template('monitoring.html')
app.run(host='0.0.0.0', debug=True)
ナレッジ
-
Pythonによるhtmlの生成
本プログラム作成にあたり、htmlを生成するライブラリを探しました。パーサーなどはみつかりましたが、使えそうなhtmlを生成するライブラリは見つからなかったので、with openで書いて生成しました。 -
データベースにおける画像の保存形式
画像をデータベースに保存するときの考慮点が、画像データを直接保存するか、パスで保存するかだと思いますが、本プログラムは実装を簡単にするため、ファイル名で保存しています。セキュリテイ的にはデータベースに画像を保存した方がいいと考えますが、ケースバイケースだと思うので、アプリに合わせた保存形式の選択(※)を。
(※)画像をデータベースに保存すると容量を消費するので、AWS等クラウドを使用した開発を行うなら、画像データはデータベースに保存しない方が、ウェブサイトのレスポンスを大きく改善し、ウェブアプリケーションのスケールに対して役立ちます。
-
焦電型センサーの耐久性
焦電型センサーは消耗品です。壊れると人がいないのに誤検知するので、動きがおかしいと思ったときはセンサーを確認しましょう。(※)
(※)GPIOの接続を間違えると、センサーが熱くなり簡単に壊れます。
-
Bootstrap
本プログラムのデザインはBootstrapを使用しています。CDNで読み込むことで、ファイルのダウンロード不要、ブラウザキャッシュの高速化、何より実装が楽です。最低限、たったの3行書けば利用できます。
さいごに
Raspberry Pi使えばネットワークカメラ不要です。
応用として、外部からアクセスできるようにすればインターネットカメラとして利用もできますが、インターネット接続をする場合はセキュリテイ対策を行いましょう。
監視カメラは、介護、ペット、防犯等利用用途はたくさんあります。また、Messaging APIなどのAPIと組み合わせれば、通知する仕組みも簡単にできます。
夏休みシーズンなので、自由研究の工作にいかかでしょうか。