Python
Flask
SQLite3
RaspberryPi
IoT

Pythonで作る簡単監視カメラ Raspberry Pi + カメラモジュール + 焦電型赤外線センサー

はじめに

Raspberry Piとカメラモジュール等の部品を組み合わせて、監視カメラ:camera_with_flash:を作りました。
焦電型赤外線センサーで検知を行い、赤外線検知時に画像を保存します。また、Flaskを使用してブラウザからモニタリングができます。

焦電型赤外線センサーは、周囲の人や動物から発せられる赤外線(熱)を検知します。また、動きのある熱源に反応して出力を行います。最大検知距離は気温等の環境条件により、8mほどです。

本記事は、Raspberry Piとカメラモジュール並びに焦電型赤外線センサーを使用したレシピです。

コンセプトは手間をかけることなく全て、Pythonで作ることをテーマに作りました。

本プログラムは全てPythonでできています。
htmlファイルもPythonで生成しています。

スクリーンショット 2018-08-05 15.24.48.png

全体概要

フロントはFlaskを使用して、表示するhtmlファイルはPythonで生成しています。
焦電型赤外線センサーで検知時に、画像はFlaskの静的ファイルディレクトリであるstaticディレクトリに保存します。また、合わせてファイル名をSQLiteのデータベースに保存し、htmlファイル内では最新20件のファイル名を取り出してテーブル表示させています。

ポイントは、保存した画像のファイル名を取り出すときにURLを指定してアクセスできるように実装しました。本プログラムは100行未満で、生成されるhtmlファイルもわずか約4KB程度です。

監視システム.jpg

ブラウザよりファイル名を選択してクリックすると、http:///static/にアクセスすることでFlaskのstaticディレクトリ以下の画像を参照します。

スクリーンショット 2018-08-05 15.24.23.png

構成

必要な部品は以下になります。

  • 部品一覧
部品 数量
Raspberry Pi PiNoir Camera V2   1個 
焦電型赤外線センサー    1個 
ジャンパー線(メス-メス)    3本 

写真 2018-08-05 14 04 47.jpg

プログラム

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と組み合わせれば、通知する仕組みも簡単にできます。

夏休みシーズン:sunflower:なので、自由研究の工作にいかかでしょうか。