はじめに
- こちらはスマートホーム(自作)アドベントカレンダー2020の3日目の記事です。
- Qiita初投稿のため、至らない点はご容赦下さい。
要旨
寝室で昼寝している子どもの起床を見守るための家庭内カメラを、OpenCV+HTML+JavaScriptで簡易に構成します
開発の背景
我が家にはもうすぐ1歳になる女の子がいます。お昼寝が欠かせないので、眠るたびに寝室で寝かせているのですが、親がずっと寝室で寄り添うことは難しいため、都度都度様子を覗きに行って対処しています。
ところが、様子を見に行ったときには子どもが既に起きていたり、場合によってはベッド上をハイハイで移動してベッドの端っこにいた、なんてこともありまして、これはもうベッド転落ニアミスです
ならば、子どもの様子を見守るカメラと、カメラ映像をお手軽に身に行ける家庭内システムを作ってしまおう!と思うに至りました。
システム検討
要件整理
- ユーザ(親)はスマートフォン、タブレットなどから気軽に様子を覗きたい
- 活用したいシーンでは、確実にユーザ(親)とターゲット(子ども)が同時に家にいるため、家庭内(LAN)で完結するシステムでOK
- 様子はせいぜい数秒間隔で観察できればOK
- ネットワークカメラではいおしまいにはしない!!(ただの趣味
)
システム検討結果
サーバにて一定間隔でカメラ画像を取得するとともに、それを埋め込んだHTMLを配信して、スマートフォン・タブレットから閲覧しにいくシステムとしました。
ハードウェア構成
将来的にはサーバを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
各部詳細
カメラ画像を保存するクラス
画像保存のインターバル、使用するカメラのインデックス番号、画像の保存先を設定できるようにします。
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
ローカルサーバを立ち上げるクラス
- こちらを参考にさせて頂きました。
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()
ユーザへの表示
<!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>
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
を使います。 - マルチスレッド処理中にプログラムを終了させる方法についてはこちらを参考にさせて頂きました。
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
おわりに
思ったよりも簡単に見守りカメラを作ることができました。コーディングが粗いところがあるのは、また修正していきたいと思います…!