節約プログラマー雑記

Raspberry Pi でカメラのストリーミング配信(コーディング)

以前、実家の犬の監視を兼ねて、カメラのストリーミング配信をRaspberry Piでできるようにしていました。
その時は「mjpeg-streamer」を使って動画の取得を行っていたのですが、自分で動画の取得部分もある程度行えるようにしたいと思い、Pythonでアプリを作ってみましたので、備忘を兼ねて記録を残しておきたいと思います。



1. 概要

今回作る「カメラ機能の常駐アプリ」と「Web画面」の2つになります。
元々は、Web画面側でカメラ機能も取り込もうとしていたのですが、WebのベースがDjangoだと処理が重いのか、画像処理が大分遅かったため、カメラ機能を外だしして常駐アプリとするようにしました。
画面側は、クライアント側から呼び出されたタイミングで、常駐アプリにアクセスして、映像を返し続けるような仕組みになっています。

2. カメラ機能のインストール

何はともあれ、まずはRaspberry Piにカメラをインストールするところからスタートです。
Amazonなどで、「Raspberry Pi カメラ」と検索すると、¥1,000~¥3,000と色々なものが出てきますが、単純に撮影するだけであれば、¥1,000ぐらいのものでも、特に問題ありません。(貧乏性で、あまり高いものを使ったことがないのですが、、、高いのは画素数以外、何が違うのかが良くわかりません。。)

カメラを購入したら、下の図の赤枠部分ににRaspberry Piにカメラを装着します。
rasp_camera.jpg

装着が完了したら、下の画像のように「Raspberry Piの設定 > インターフェース」からカメラを有効にすれば、カメラモジュールが利用できるようになります。
rasp_camera_setting.png

カメラが有効になったかの確認として、ターミナルなどでvcgencmd get_cameraとコマンドを打って、「supported=1 detected=1」と表示されれば下準備完了です。

3. カメラ機能(常駐アプリ)の作成

必要ライブラリのインストール

まず、カメラから配信用の映像を取得するにあたり、常駐アプリを作成するのですが、その際、Open CV2と呼ばれる画像関連のライブラリが必要になりますので、それをインストールするところから開始になります。
今回利用している言語は、Python3なのですが、Python3ではOpen CV2のインストール方法がapt-getだけではうまくいかないみたいなので、apt-getでOpen CV2に必要なライブラリをインストールしたのち、pipにてPythonのパッケージをインストールする作業を行います。
インストール作業の際、下記のサイトが参考になりましたので、こちらを参考にしていただくと良いかもしれません。
(参考)Raspberry Pi + Python 3 に OpenCV 3 をなるべく簡単にインストールする


コーディング

実際のアプリのコーディングは下記のようになります。

ファイル名:camra.py

import picamera
import picamera.array
import cv2
import time
import datetime
import os
import sys
import socket

videopath='/tmp/ram/camera.sock'

class Camera():
    def __init__(self):
        self.socket_path = videopath
        self.camera = picamera.PiCamera()
        self.camera.vflip = True      #自宅のカメラが上下左右逆転してるので、Trueにしてます。
        self.camera.hflip = True      #同上
        self.camera.resolution = (480,320) #カメラの解像度を480×320に指定。
        self.stream = picamera.array.PiRGBArray(self.camera)
        self.camera.framerate = 32

    def __del__(self):
        self.camera.close()
        self.stream.close()

    #フレームの取得
    def get_frames(self):
        self.camera.capture(self.stream,'bgr', use_video_port=True)
        img =  cv2.imencode('.jpg',self.stream.array)[1].tobytes()
        self.stream.seek(0)
        self.stream.truncate()
        return img

    #プロセスのスタート
    def start(self):
        try:
            os.remove(self.socket_path)
        except:
            pass

        s = self.socket = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
        s.bind(self.socket_path)
        s.listen(1)
        try:
            while True:
                connection, adress = s.accept()
                img = self.get_frames()
                connection.send(img)
        finally:
            #pass
            os.remove(self.socket_path)


if __name__== '__main__':
    test = Camera()
    test.start()

ファイル名:/etc/systemd/system/camera.service

[Unit]
Description=PiCamera_daemon

[install]
WantedBy=multi-user.target

[Service]
ExecStart = /usr/bin/python /home/scripts/python/camera.py
Restart = always
User=pi


補足説明

常駐アプリのcamra.pyに関して少し補足すると、PiCameraのインスタンスを作成し、ソケット通信してきたクライアントに対して、jpeg形式で画像を返却するようなアプリになっています。ただ、これだけですと、単純にjpegファイルを返すだけになってしまうのですが、Web画面側で画像を連続してクライアントに返却するような形式をとることで、動画としています。

また、camera.serviceはアプリをサービス化した方が、色々管理が楽なので、上記のような形で定義ファイルを作成して、Systemdにサービス登録を行いました。
これを行えば、systemctlコマンドでcamara.pyの起動・停止などが簡単にできるようになるので、とても楽です。

4. Web画面の作成

コーディング

常駐アプリ側が完成したら、次はWeb側の機能の実装します。
Web側のアプリはDjangoで実装していますが、常駐アプリからの映像の取得を行うview.py、urlの設定を行うurls.py、画面を表示するhome_camera.htmlは下記のようになっています。

ファイル名:view.py

from django.shortcuts import render,redirect
from django.views.generic import TemplateView
from django.http import HttpResponse,StreamingHttpResponse
from datetime import datetime, timedelta,date
import socket
from django.db import connection, transaction

#カメラ映像ストリーミング部分
def camera_view(self):
    return StreamingHttpResponse(get_streams(self),content_type='multipart/x-mixed-replace; boundary=boundary')

#内部関数
def get_streams(self):
    socket_path = '/tmp/ram/camera.sock'
    while True:
        s= self.socket = socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
        s.connect(socket_path)
        img = s.recv(23859296)
        yield b'--boundary\r\n' + b'Content-Type: \"image/jpeg\"\r\n\r\n' + img + b'\r\n\r\n'
        s.close()


#htmlに埋め込み
class CameraView(TemplateView):
    template_name = 'home_app/home_camera.html'
    img = b''
    t_cnt = 0
    last_access = datetime.now().strftime('%y%m%d%H%M%S')



ファイル名:urls.py

from django.conf.urls import url
from django.urls import path

from home_app.views import(
        camera_view,
        CameraView,
)

app_name = 'home_app'

urlpatterns = [
        path('camera_view',camera_view,name='camera_view'),
        path('camera_site',CameraView.as_view(),name='camera_site'),
]

ファイル名:home_camera.html

<html>
  <head>
    <title>ホームカメラ</title>
  </head>
  <body>
    <img src="{% url 'home_app:camera_view' %}">
  </body>
</html>


補足説明

実装している内容としては、camera_view関数からget_streams関数を呼び出し、先ほど常駐アプリで作成したソケットに対して通信を行い、StreamingHttpResponseメソッドにて、取得した画像を返し続けるような動きになっています。
後は、CameraViewクラスにて、Templateとひもづけを行って、htmlにストリーミング画像をimg形式で表示しているような作りになっています。

実際に、全てコーディングが完了すると下のような感じで、Web画面からストリーミング映像が見れるようになります。
※静止画像で恐縮ですが。。。
camera_streaming.png


(補足)アプリの外部公開

需要があるかわかりませんが、作成したカメラを外(インターネット)から見たい場合、グローバルIPが提供されるプロバイダと契約をしていただいた上で、raspberry piが繋がっている元のルーターに対して、ポートの開放を行う必要があります。

下記のサイトはバッファローでの設定ですが、下記のような手順でルーターのポートを解放すると、グローバルIPと解放したポートを指定することでインターネットからも、カメラの映像を確認できるようになります。
ただ、ポートの開放はセキュリティ上のリスクも増えますので、実施する際は十分に注意してください。

(参考)バッファローでのポート解放