3
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

自宅内見守りカメラをOpenCV+HTML+JavaScriptで簡易に構成!

はじめに

  • こちらはスマートホーム(自作)アドベントカレンダー2020の3日目の記事です。
  • Qiita初投稿のため、至らない点はご容赦下さい。

要旨

寝室で昼寝している子どもの起床を見守るための家庭内カメラを、OpenCV+HTML+JavaScriptで簡易に構成します :camera:

開発の背景

我が家にはもうすぐ1歳になる女の子がいます。お昼寝が欠かせないので、眠るたびに寝室で寝かせているのですが、親がずっと寝室で寄り添うことは難しいため、都度都度様子を覗きに行って対処しています。
ところが、様子を見に行ったときには子どもが既に起きていたり、場合によってはベッド上をハイハイで移動してベッドの端っこにいた、なんてこともありまして、これはもうベッド転落ニアミスです :scream:
ならば、子どもの様子を見守るカメラと、カメラ映像をお手軽に身に行ける家庭内システムを作ってしまおう!と思うに至りました。

システム検討

要件整理

  • ユーザ(親)はスマートフォン、タブレットなどから気軽に様子を覗きたい
  • 活用したいシーンでは、確実にユーザ(親)とターゲット(子ども)が同時に家にいるため、家庭内(LAN)で完結するシステムでOK
  • 様子はせいぜい数秒間隔で観察できればOK
  • ネットワークカメラではいおしまいにはしない!!(ただの趣味 :innocent: )

システム検討結果

サーバにて一定間隔でカメラ画像を取得するとともに、それを埋め込んだHTMLを配信して、スマートフォン・タブレットから閲覧しにいくシステムとしました。

system_structure.png

ハードウェア構成

将来的にはサーバをRasberry Pi + USBカメラとして省スペース化したいですが、本記事投稿時点ではハードウェアが手元になかったため、カメラ付きノートPCを用いてテストすることとします。

ソフト実装

ラズパイでの実装を見すえ、ラズパイで利用しやすいPython主体で実装することにします。

全体構成

main.py
models
   ├ camera.py
   └ localserver.py
views
   ├ index.html
   └ main.js
assets
   └ img.png
ファイル 機能
camera.py ./assets/img.pngを一定サンプリングで保存し続ける
localserver.py ローカルサーバーを立てる
index.html ユーザへの表示画面
main.js 一定サンプリングでimg.pngを更新して表示する

環境

  • python: 3.6.10
  • opencv-python: 4.2.0.34

各部詳細

カメラ画像を保存するクラス

画像保存のインターバル、使用するカメラのインデックス番号、画像の保存先を設定できるようにします。

camera.py
import os
import time
import cv2


class HomeCamera(object):
    ''' home camera object '''
    def __init__(self, interval_sec, port=0, img_path='./assets/img.png'):
        ''' constructor '''
        self.cap = cv2.VideoCapture(port)
        if not self.cap.isOpened():
            raise Exception('Could not open video capture')
        self.interval_sec = interval_sec
        self.img_path = self.parse_img_path(img_path)

    def __del__(self):
        ''' destructor '''
        self.cap.release()

    def parse_img_path(self, path):
        '''
        保存先として指定した画像ファイルパスを解析する
        保存先フォルダが無ければユーザ許可を得て作成する
        '''
        if not isinstance(path, str):
            raise Exception('Invalid img path')

        path = os.path.abspath(path)
        dirname, filename = os.path.split(path)

        # ディレクトリが存在しない場合はユーザ許可を得てディレクトリを作る(不許可時はエラー終了)
        if not os.path.exists(dirname):
            print("The save directory doesn't exist")
            print(f"Create?: {dirname}")
            print('yes / no')
            confirm = input()

            if confirm=='yes':
                os.makedirs(dirname)
            else:
                raise Exception('Save directory does not exist')

        return os.path.join(dirname, filename)

    def capture_frame(self):
        ''' save a frame by the constant interval'''
        while True:
            time.sleep(self.interval_sec)
            ret, frame = self.cap.read()
            if ret:
                cv2.imwrite(self.img_path, frame)
            else:
                print('Could not capture frame')
        return

ローカルサーバを立ち上げるクラス

  • こちらを参考にさせて頂きました。
localserver.py
import http.server
import socketserver


class LocalServer(object):
    ''' local server module '''
    def __init__(self, port):
        '''constructor '''
        self.port = port
        self.handler = http.server.SimpleHTTPRequestHandler
    def start(self):
        ''' start http server '''
        with socketserver.TCPServer(('', self.port), self.handler) as httpd:
            httpd.serve_forever()

ユーザへの表示

index.html
<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="UTF-8">
        <title>HomeCamera</title>
    </head>
    <body>
        <script type="text/javascript" src="main.js"></script>
        <img id="home_camera" src="../assets/img.png">
    </body>
</html>
main.js
window.onload = function() {
    var image = document.getElementById("home_camera");

    function updateImage() {
        image.src = image.src.split('?')[0] + "?" + Math.random();
    }

    setInterval(updateImage, 1000);
}
  • updateImageの中では、画像を更新・読み込みするために、適当なクエリパラメータを付けてあげています。
  • こちらを参考にさせて頂きました。
  • Python側で画像を保存するフォルダを変更したら、こちらも連動させたいところですが、そのあたりの細かいこだわり実装は今回端折りました。

メイン

  • 画像保存とローカルサーバを並行で走らせるためthreadingを使います。
  • マルチスレッド処理中にプログラムを終了させる方法についてはこちらを参考にさせて頂きました。
main.py
import threading
import sys

import models.camera
import models.localserver


def main():
    ''' main function '''
    local_server = models.localserver.LocalServer(8080)
    home_camera = models.camera.HomeCamera(interval_sec=2, img_path='assets/img.png')
    thread_server = threading.Thread(target=local_server.start)
    thread_camera = threading.Thread(target=home_camera.capture_frame)

    thread_server.setDaemon(True)
    thread_camera.setDaemon(True)
    thread_server.start()
    thread_camera.start()
    terminator()

def terminator():
    ''' terminate multi thread program '''
    while True:
        user_input = input()
        if user_input=='e':
            sys.exit()

if __name__=='__main__':
    main()

結果確認

サーバPCと同じLANに接続したPCから、サーバPCのローカルアドレス:ポートにアクセスすれば画像が表示されるはず…!
⇒ できた!

http://xxx.xxx.xx.xx:8080/views

おわりに

思ったよりも簡単に見守りカメラを作ることができました。コーディングが粗いところがあるのは、また修正していきたいと思います…!

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
Sign upLogin
3
Help us understand the problem. What are the problem?