Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

はじめに

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:なので、自由研究の工作にいかかでしょうか。

Brutus
DevOpsとAIの二刀流を目指す凡人。Python、RaspberryPi、Linux、Docker、k8s、セキュリティ、Oracle Cloud、Terraform、Ansible等について発信しています。登壇、執筆等あれば、Twitterよりメッセージお願いします。またはPuulsでも受け付けてます。https://puuls.jp/p/1iz5MPU1tgk
https://brutus.ml/
gauss
株式会社GAUSSは、AIソフトウェアを組み込んだサーバの提供、AIサービス構築のコンサルティング、AIのエンジニア育成をセットにしてサービス提供を展開するスタートアップ企業です。
https://gauss-ai.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした