育児×家事×IoT Raspberry Piで最強の防犯カメラを作ってみる(動画記録・配信、動体検知・Line通知、顔検知・顔認証、Alexa搭載)[2/6]

やりたいこと(再掲)

最終的にやりたいことは、以下の5つの機能を持つ「最強の防犯カメラ」を作ることです。機能①〜機能③は、市販されている多くの防犯カメラでも持っている機能ですが、機能④や機能⑤の顔認証機能を持つ監視カメラはまだ多くないと思います。

 機能①.動画を24時間撮影し、カメラ本体に動画で記録する
 機能②.動画をWebブラウザや他の機器から参照できるようにライブ配信する
 機能③.動体を検知したら、静止画をLineに通知する
 機能④.家族の顔を認証したら、静止画をLineに通知する
 機能⑤.家族の顔を認証したら、◯◯さんおかえり!と喋る

図1.png

家に帰ると、顔を見て「○○さん、おかえり!」と言ってくれる辺りが、スマートハウスに一歩づ近づいている気がします。

実現に向けた連載

最強の「防犯カメラ」を作成するために、以下のように少しずつに記事を書いていきます(予定)。
 1回目:カメラの設定と動画記録
 2回目:カメラ映像のライブ配信   ←この記事
 3回目:動体検知機能とLineへの通知
 4回目:顔認証機能とLineへの通知
 5回目:Raspberry PiへのAlexaの搭載
 6回目:顔認証後にAlexaで音声通知

2回目:カメラ映像のライブ配信

前回はRaspberry Piのカメラの設定を行って、カメラ映像を動画ファイルに記録することをやってみました。今回は、python用のWebサーバソフトのFlaskを用いて、カメラ映像をライブ配信してPCやスマホのブラウザから見えるようにしたいと思います。

1.ライブ配信プログラムの作成

防犯カメラの映像をライブ配信するために、以下の4つのファイルを作っていきます。

/home/pi/camera/
├── Camera.py        <--前回のCamera.pyを更新
├── base_camera.py   <--カメラ映像を別スレッドで取得するライブラリ
├── cameraServer.py  <--Flaskから呼び出されるメインアプリ
└── templates
    └── index.html   <--画面表示用のHTMLファイル

1.base_camera.pyのダウンロード

カメラ映像を簡単にライブ配信するために、先人の知恵を活用させていただきます。今回はmiguelgrinbergさんが公開しているflask-video-streamingの「base_camera.py」のモジュールを活用させていただきます。以下のように、wgetコマンドで「base_camera.py」をダウンロードしましょう。

root@raspberrypi:/home/pi/camera# wget https://raw.githubusercontent.com/miguelgrinberg/flask-video-streaming/master/base_camera.py

2.base_camera.pyの変更

オリジナルのbase_camera.pyは、クライアントからのリクエストに応じてスレッドを生成し、クライアントとの接続が無くなるとスレッドを停止するようにできています。しかし、このまま利用してしまうと、クライアントからのリクエストが無い時に動画ファイルへの記録が停止してしまうので、Flaskの起動時にスレッドを起動し、Flaskの終了時にスレッドを停止するように、次の2箇所を書き換えます。

1箇所目:66行目と67行目の間に「BaseCamera.thread.daemon = True」を追加

//home/pi/camera/base_camera.py
BaseCamera.thread = threading.Thread(target=self._thread)
BaseCamera.thread.daemon = True   #<--この行を追加
BaseCamera.thread.start()

2箇所目:98行目辺りの「if time.time()・・・・」から4行をコメントアウトする。

//home/pi/camera/base_camera.py
# if there hasn't been any clients asking for frames in
# the last 10 seconds then stop the thread
#if time.time() - BaseCamera.last_access > 10:       #<--コメントアウトする
#    frames_iterator.close()                 #<--コメントアウトする
#    print('Stopping camera thread due to inactivity.') #<--コメントアウトする
#    break                            #<--コメントアウトする

1箇所目「BaseCamera.thread.daemon = True」によって、親スレッドが終了した場合に子スレッドも終了するようになります。また、2箇所目の変更によって、クライアントの接続がなくなってもスレッドが生き続けるようになります。

3.Camera.pyの変更

次に、前回作成した「Camera.py」を以下のように変更します。変更点は次の5箇所です。

1箇所目:BaseCameraをインポートするように「from base_camera import BaseCamera」を追加
2箇所目:CameraクラスはBaseCameraを継承するようにカッコ内に「BaseCamera」を追加
3箇所目:クライアントに画像を返すように「yield cv2.imencode('.jpg', stream.array)[1].tobytes() 」を追加
4箇所目:動画の画面表示は不要となるのでコメントアウト
5箇所目:Camera.pyの単独起動も不要なのでコメントアウト

//home/pi/camera/Camera.py
# -*- coding: utf-8 -*-
import picamera
import picamera.array
import cv2
import time
import datetime
from base_camera import BaseCamera  #<--1箇所目

###################################################
## 定数定義
###################################################
#動画の格納パス
videopath='/home/pi/camera'

class Camera(BaseCamera):           #<--2箇所目
    ###################################################
    ## カメラ処理のメインメソッド
    ###################################################
    @staticmethod
    def frames():
         # カメラ初期化
         with picamera.PiCamera() as camera:
            #カメラ画像を左右左右逆転させる
            camera.vflip = True
            camera.hflip = True

            # 解像度の設定
            camera.resolution = (640, 480)

            # カメラの画像をリアルタイムで取得するための処理
            with picamera.array.PiRGBArray(camera) as stream:
                #記録用の動画ファイルを開く(時間ごと)
                curstr=datetime.datetime.now().strftime("%Y%m%d_%H")
                fourcc = cv2.VideoWriter_fourcc(*'XVID')
                out = cv2.VideoWriter(str(videopath)+'/video_'+curstr+'.avi',fourcc, 20.0, (640,480))

                #カメラ映像が落ち着くまで待つ
                time.sleep(2) 

                while True: #カメラから画像を取得してファイルに書き込むことを繰り返す
                    # カメラから映像を取得
                    camera.capture(stream, 'bgr', use_video_port=True)

                    #動画を記録
                    nowstr=datetime.datetime.now().strftime("%Y%m%d_%H")

                    #次の時間になったら新たな動画ファイルを切り替え
                    if curstr != nowstr:
                        curstr=nowstr
                        out.release()
                        out = cv2.VideoWriter(str(videopath)+'/video_'+curstr+'.avi',fourcc, 20.0, (640,480))

                    #動画を記録
                    out.write(stream.array)

                    #ライブ配信用に画像を返す
                    yield cv2.imencode('.jpg', stream.array)[1].tobytes() #<--3箇所目

                    # 結果の画像を表示する
                    #cv2.imshow('camera', stream.array) #<--4箇所目

                    #キーが押されたら終了
                    if cv2.waitKey(1) < 255:
                        break

                    # カメラから読み込んだ映像を破棄する
                    stream.seek(0)
                    stream.truncate()

                # 表示したウィンドウを閉じる
                out.release()
                cv.destroyAllWindows()

#単独起動用
#Camera.frames() #<--5箇所目

この変更点の特筆すべきところは、先ほどダウンロードしたBaseCameraクラスを継承する点と、ライブ配信用に画像を返す部分です。特に、画像を返す部分では「cv2.imencode()」を使って画像をJPEG形式に変換して渡しています。

4.cameraServer.pyの作成

次に、Flaskから呼ばれるメインアプリケーションを以下のように作成します。

//home/pi/camera/cameraServer.py
# -*- coding: utf-8 -*-
from flask import Flask, render_template, Response
from Camera import Camera

app = Flask(__name__)

#index.htmlを返す
@app.route('/')
def index():
    return render_template('index.html')

#カメラ映像を配信する
@app.route('/video')
def video():
    return Response(gen(Camera()),
                    mimetype='multipart/x-mixed-replace; boundary=frame')

#カメラオブジェクトから静止画を取得する
def gen(camera):
    while True:
        frame = camera.get_frame()
        yield (b'--frame\r\n'
               b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')

#カメラスレッドを生成してFlaskを起動する
if __name__ == '__main__':
    threaded=True
    video()
    app.run(host="0.0.0.0",port=80)

このプログラムでは、まずFlaskオブジェクトを作成した上で、「@app.route('/')」からの3行で「/」にアクセスした時に「index.html」を返すようにし、「@app.route('/video')」からの4行で「/Video」にアクセスした時に、カメラから取得した画像を返すようにしています。次の「def gen(camera)」関数の部分は、カメラからの画像データをWhileループで継続的に取得し、Context-Typeを設定してvideo()に返却しています。

プログラムの最後の部分は、自身がメインスレッドの場合(つまりflask runで実行されない場合)に、スレッドを有効にして、上記の「video()」呼び出しカメラから動画を取得するスレッドを開始します。その後「app.run()」を実行してflaskを起動しています。app.runの2つ目の引数は、flaskを立ち上げるポート番号ですので、既に80番ポートをNginxやApacheなどで利用している場合は書き換えてください。

5.index.htmlの作成

次に「templates」フォルダを作成して、index.htmlを作成します。
※index.htmlは必ず「templates」フォルダを作成して配置する必要があります。

/home/pi/camera/templates/index.html
<html>
  <head>
    <title>防犯カメラ</title>
  </head>
  <body>
    <img src="{{ url_for('video') }}">
  </body>
</html>

index.htmlの特筆すべきところは、imgタグの中で「/video」を呼び出しているところぐらいかと思います。

以上で、カメラ映像をライブ配信するプログラムの作成は完了です。

2.ライブ配信のテスト

それでは、作成したプログラムを実行して、ちゃんと映像が配信されるかテストしてみましょう。

1.プログラムの実行

プログラムの起動は、rootユーザで以下のコマンドを実行します。今回は、画面に表示しないのでVNCでログインしたコンソールで実行する必要はありませんよ。

プログラムが正常に起動すると、以下のようにカメラスレッドが立ち上がり、続いてFlaskが起動します。

root@raspberrypi:/home/pi/camera# python cameraServer.py
Starting camera thread.
 * Serving Flask app "cameraServer" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)

終了させる時は、画面にも表示されていますが「CTRL+C」キーを押します。

2.ブラウザから確認

それでは、PCのブラウザを立ち上げ「http://<Raspberry PiのIPアドレス>/」にアクセスしてみましょう。
以下のように、防犯カメラの映像がライブ配信されると思います!!私の環境では、0.2秒程度の遅延はあるものの、ほぼリアルタイムでカメラの映像が見えています。
図1.png

3.Raspberry Pi起動時に自動起動する

最後に、Raspberry Piの起動後にSSHでログインしてプログラムを手動で実行するのは面倒なので、systemdにサービス登録して、Raspberry Piの起動時に自動的に監視カメラのプログラムが実行されるようにします。

1.cameraServer.shの作成

systemdに登録するシェルスクリプトを以下のように作成します。

home/pi/camera/cameraServer.sh
#!/bin/bash
export SHELL=/bin/bash
export LANGUAGE=ja_JP.UTF-8
export LANG=ja_JP.UTF-8
export LC_ALL=ja_JP.UTF-8
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/games:/usr/games

python /home/pi/camera/cameraServer.py

Systemdからの起動の場合環境変数が読み込まれないので、環境変数をいろいろ記述(不要なのもあるかもしれません)して、先ほど作成した「cameraServer.py」をフルパスで記述して実行できるようにします。

2.cameraServer.shの確認

シェルスクリプトを作成したら、実行権限を付与して、ちゃんと起動できるか確認してみます。

root@raspberrypi:/home/pi/camera# chmod 755 cameraServer.sh
root@raspberrypi:/home/pi/camera# ./cameraServer.sh 
Starting camera thread.
 * Serving Flask app "cameraServer" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)

ちゃんと起動できているようです。

3.flask.serviceの確認

上で作成したシェルスクリプトを呼び出すSystemd用のファイルを「/etc/systemd/system/flask.service」に作成します。

/etc/systemd/system/flask.service
[Unit]
Description = Flask for CameraServer
[Service]
Restart = always
WorkingDirectory=/home/pi/camera/
ExecStart = /home/pi/camera/cameraServer.sh
ExecReload = /bin/kill -s HUP ${MAINPID}
ExecStop=/bin/kill -s TERM ${MAINPID}
[Install]
WantedBy = multi-user.target

4.サービスの起動確認

それでは、Systemctlコマンドを用いて、以下のようにサービスを起動してみてください。サービスがちゃんと起動したかは、Systemctlコマンドに「status」オプションを付けると確認できます。

root@raspberrypi:/home/pi/camera# systemctl start flask.service
root@raspberrypi:/home/pi/camera# systemctl status flask.service
● flask.service - Flask for CameraServer
   Loaded: loaded (/etc/systemd/system/flask.service; disabled; vendor preset: enabled)
   Active: active (running) since Thu 2020-01-30 13:07:30 JST; 11s ago
 Main PID: 15784 (cameraServer.sh)
    Tasks: 11 (limit: 2077)
   Memory: 37.9M
   CGroup: /system.slice/flask.service
           ├─15784 /bin/bash /home/pi/camera/cameraServer.sh
           └─15785 python /home/pi/camera/cameraServer.py

 1月 30 13:07:30 raspberrypi systemd[1]: Started Flask for CameraServer.
 1月 30 13:07:41 raspberrypi cameraServer.sh[15784]: Starting camera thread.
 1月 30 13:07:41 raspberrypi cameraServer.sh[15784]:  * Serving Flask app "cameraServer" (lazy loading)
 1月 30 13:07:41 raspberrypi cameraServer.sh[15784]:  * Environment: production
 1月 30 13:07:41 raspberrypi cameraServer.sh[15784]:    WARNING: Do not use the development server in a production environment.
 1月 30 13:07:41 raspberrypi cameraServer.sh[15784]:    Use a production WSGI server instead.
 1月 30 13:07:41 raspberrypi cameraServer.sh[15784]:  * Debug mode: off
 1月 30 13:07:41 raspberrypi cameraServer.sh[15784]:  * Running on http://0.0.0.0:80/ (Press CTRL+C to quit)

ちなみに、サービスを停止させるときは「systemctl stop flask.service」で停止できます。

5.サービスの自動起動設定

最後に、Raspberry Pi起動時に自動的にサービスを起動させるために、Systemctlコマンドに「enable」オプションをつけて実行します。

root@raspberrypi:/home/pi/camera# systemctl enable flask.service
Created symlink /etc/systemd/system/multi-user.target.wants/flask.service → /etc/systemd/system/flask.service.

6.再起動して確認

それでは、Raspberry Piを再起動して、防犯カメラのサービスが自動的に起動するか確認してみてください。また、ブラウザからアクセスして映像が見えるかも確認してくださいね。

おわりに

今回はカメラで撮影した動画をライブ配信することをやってみました。次回は、防犯カメラに動体検知機能を追加し、動体を検知したらLineに通知がくるようにしてみたいと思います。

連載記事

 1回目:カメラの設定と動画記録
 2回目:カメラ映像のライブ配信   ←この記事
 3回目:動体検知機能とLineへの通知
 4回目:顔認証機能とLineへの通知
 5回目:Raspberry PiへのAlexaの搭載
 6回目:顔認証後にAlexaで音声通知

育児×IoTスマートホームの関連記事

Alexaをしゃべらせる(remote2 v3.3編)
自分のGoogleカレンダーにJWTを使って予定を追加してみる
Alexaに通知を送ってみる(Node-red編)
Node-redでスマートハウス-LG製テレビを操作してみる
Node-redでスマートハウス-Gravioで子供用プリキュアボタン
育児×IoT Gravioでミルク&オムツ交換記録をGoogleカレンダにつけてみる
育児×IoT HomeAssistantでスマートホームコントローラを作ってみる

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
Sign up for free and join this conversation.
If you already have a Qiita account