Raspberrypiを用いた監視カメラを実用検討し、一旦の完成を迎えたのでまとめます。
<やりたいこと>
(1) 日中常時監視
(2) 全録画だと容量不足となるため動体検知した時のみの動画を残す
(3) 動体検知のイベントはLINEですぐに通知する
(4) 残した動画は定期的にまとめてGoogleDriveにアップロードする
<フォルダ構成>
各ファイル置き場を以下としました。
/home/pi
・GoogleDriveUpload.py
・client_secrets.json
・settings.yaml
/home/pi/Pi_video
・raspivid_always.py
・motion_detect_LINE_notify.py
<実施手段>
(1) 日中常時監視
60秒で1つの動画ファイル(H264形式)を撮影・保存し続けるPythonスクリプトです。
室内から外を監視する事と照明が設置できていないことから日中限定の常時監視に留まっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#coding: utf-8 import datetime import subprocess RecordTime = 60 #録画時間 filelist = '/home/pi/Pi_video/filelist.txt' #filelist with open(filelist,mode='w') as f: f.write('test \n') while True: #時刻取得 before = datetime.datetime.now() strtime = before.strftime('%Y%m%d_%H:%M:%S') FileNameH = strtime + ".h264" #時間設定 at6am = before.replace(hour=6,minute=0,second=0,microsecond=0) at6pm = before.replace(hour=18,minute=0,second=0,microsecond=0) if((before < at6am) or (before > at6pm)): pass else: cmd1 = 'raspivid -hf -vf -t ' +str(RecordTime*1000) + ' -w 640 -h 480 -fps 30 -o '+FileNameH #video capture print("録画開始 %s" %(strtime)) ps1 = subprocess.run(cmd1.split(),stdout=subprocess.PIPE) print("録画終了") with open(filelist,mode='a') as f: f.write('%s \n' %(FileNameH)) |
5行目:録画時間を60秒とします
7行目:動体検知を別スクリプトで実施する際に参照するためのファイル名のリストを作成します。
21行目:日中限定ということで午前6時〜午後6時までの撮影に限定しました。
(2) 全録画だと容量不足となるため動体検知した時のみの動画を残す
(3) 動体検知のイベントはLINEですぐに通知する
ファイルリストから(1)で保存されたH264形式の動画を読み込み、動体検知を行います。動体検知がある一定以上の大きさだったら静止画をLINE通知します。動体検知が一定以下の場合は動画を削除します。
結果、(1)で撮り続けた動画の内、動体検知したものだけ残るということになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
#coding: utf-8 import numpy as np import os import cv2 import subprocess import time import requests moment_th = 100 #動体検知の閾値 moment = [] xmin,xmax =120,460 ymin,ymax = 100,460 i = 0 <span class="crayon-k ">token</span> <span class="crayon-o">=</span> <span class="crayon-s">'abcdefghijklmnopqrstuvwxyz123456789----------'</span> while True: try: #ファイル名取得 with open('filelist.txt',"r") as f: target_line = f.readline() f.close() filename = target_line.rstrip(' \n') imagename = filename.replace('.h264','.jpg') print("ファイル名:%s" %(filename)) #H264読み込み cap = cv2.VideoCapture(filename) if not cap.isOpened(): print("VideoCapture Error") #読み込んだファイル名を削除 with open('filelist.txt', encoding='utf-8') as file: lines = file.readlines() delline = [] for j in lines: if filename not in j: delline.append(j) with open('filelist.txt', 'w', encoding='utf-8') as f: for r in delline: f.write(r) #背景差分の設定 fgbg = cv2.bgsegm.createBackgroundSubtractorMOG() # 背景オブジェクトを作成 #動体検知 while True: ret, frame = cap.read() #フレームが取得できない場合はループを抜ける if not ret: break if i%30 == 0: detframe = frame[ymin:ymax,xmin:xmax] fgmask = fgbg.apply(detframe) # 前景領域のマスクを取得する moment.append(cv2.countNonZero(fgmask)) if(moment[-1]>moment_th): cv2.imwrite(imagename,frame) i+=1 # 撮影用オブジェクトとウィンドウの解放 cap.release() i = 0 #降順で5番目の動体検知値を使用 list.sort(moment,reverse=True) moment_judge = moment[4] print("動体検知:%d\n"%(moment_judge)) moment.clear() #判定 閾値を越えたらvideo保存 if(moment_judge > moment_th): print("-----Captued!-----") #cmd1 = 'raspistill -hf -vf -w 640 -h 480 -o ' + imagename #ps1 = subprocess.run(cmd1.split(),stdout=subprocess.PIPE) payload = {'message': imagename + '動体検知しました'} # 送信メッセージ url = 'https://notify-api.line.me/api/notify' headers = {'Authorization': 'Bearer ' + token} files={"imageFile":open(imagename,"rb")} res = requests.post(url, data=payload, headers=headers,files=files,) # LINE NotifyへPOST print(res) else: os.remove(filename) #os.remove(imagename) #読み込んだファイル名を削除 with open('filelist.txt', encoding='utf-8') as file: lines = file.readlines() delline = [] for j in lines: if filename not in j: delline.append(j) with open('filelist.txt', 'w', encoding='utf-8') as f: for r in delline: f.write(r) except: print("file not exist!") time.sleep(60) |
14行目:LINE通知するためのトークンです。設定は以下の記事をご参照ください。
18行目:(1)で作成したファイルリストから動画ファイル名を取得します。
28行目:動画データを読み込みます。
30〜43行目は動画が読み込めなかった時、つまりはファイルリストに記載されているファイル名の動画が存在しなかった時はファイル名を削除します。
45〜65行目:OpenCVを用いた動体検知処理を行います。動画データは30fps、60秒のため計1800枚の静止画から成っていますがそのままだと処理時間が長くなるため、54行目で30枚間引きしています。そのため計60枚の静止画から動体検知を行なっています。momentという名前のListに動体検知値を格納しています。
67〜71行目:momentの中から5番目の値を動体検知値と定義しています。
5番目としたのでは0,1番目だと目視した動画で何もないように見えた時も動体検知値が大きく出る場合があったのでそこから少し余裕を見て設定しています。
73〜88行目:動体検知値から一定値を超えたらLINE通知しています。
一方、一定値以下だった場合は動画を削除します。
91〜102行目:ファイルリストから動画名を削除し、次の動画処理に移れるようにします。
104〜106行目:例外処理
(4) 残した動画は定期的にまとめてGoogleDriveにアップロードする
・GoogleDriveアップロード
上記までの処理でラズパイ上に動体検知した動画が残ります。都度アクセスして取りにいくのは煩わしいため、定期的にGoogleDriveにアップロードします。
今回は毎日定刻にアップロードする仕組みを構築します。
今回はまずGoogleDriveにアップロードするだけのPythonスクリプトを作成し、cronという定期処理する機能を用いてやりたいことを実現しました。
※自分の環境起因の気がしますが、(1)(2)(3)のスクリプトは/home/pi/Pi_videoというフォルダで実行しており、下記のGoogleDriveアップロードのスクリプトも同上で実施できず(pydriveがimportされてないとエラー表示)、止む無く/home/piで実行しました。
GoogleDriveにアップロードするPythonスクリプト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
#coding: utf-8 import datetime import glob from pydrive.auth import GoogleAuth from pydrive.drive import GoogleDrive gauth = GoogleAuth() gauth.CommandLineAuth(GoogleAuth()) drive = GoogleDrive(gauth) folder_id = 'ZZZZYYYYXXXXX88887777333311110000' #時刻取得 before = datetime.datetime.now() strtime = before.strftime('%Y%m%d') #アップロード対象ファイルの選択 list = glob.glob('/home/pi/Pi_video/' + strtime + '*h264') print(list) #googleドライブへアップロード for i in list: print('-----upload開始-----') print(i) f = drive.CreateFile({'title': i, 'mimeType': 'video/H264', 'parents': [{'kind': 'drive#fileLink','id': folder_id}]}) f.SetContentFile(i) f.Upload() print('-----upload終了-----') |
OAuth認証等の事前準備は以下記事をご参照ください。
10行目:folder_idに保存先のGoogleDriveを指定します。
仮で'ZZZZYYYYXXXXX88887777333311110000'とします。
14〜15行目:年月日をstrtimeという変数に入れます。動画ファイル名に付与した年月日からアップロードする動画を選別します。
18行目:globという条件を満たすパス名一覧を取得してくれる便利な機能を使って、当日の年月日が含まれたパス名一覧をlistに格納します。
22〜31行目:for文を用いてlistの要素数分のGoogleドライブへのアップロードを行います。
・cronによる定期処理
cronを設定する定期処理したい内容を書き込めばOKです。
ラズパイのターミナルで処理します。
最初にエディタを選択して$crontab -l でcronを設定するファイルを開きます。
設定箇所は39行目の
$05 21 * * * /usr/local/bin/python3 /home/pi/GoogleDriveUpload.py
だけです。
21時05分に/usr/local/bin/python3を使って、/home/pi/GoogleDriveUpload.pyというファイルを実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
pi@raspberrypi:~ $ crontab -e no crontab for pi - using an empty one Select an editor. To change later, run 'select-editor'. 1. /bin/nano <---- easiest 2. /usr/bin/vim.tiny 3. /bin/ed Choose 1-3 [1]: 1 crontab: installing new crontab pi@raspberrypi:~ $ which python3 /usr/local/bin/python3 pi@raspberrypi:~ $ crontab -l # Edit this file to introduce tasks to be run by cron. # # Each task to run has to be defined through a single line # indicating with different fields when the task will be run # and what command to run for the task # # To define the time you can provide concrete values for # minute (m), hour (h), day of month (dom), month (mon), # and day of week (dow) or use '*' in these fields (for 'any'). # # Notice that tasks will be started based on the cron's system # daemon's notion of time and timezones. # # Output of the crontab jobs (including errors) is sent through # email to the user the crontab file belongs to (unless redirected). # # For example, you can run a backup of all your user accounts # at 5 a.m every week with: # 0 5 * * 1 tar -zcf /var/backups/home.tgz /home/ # # For more information see the manual pages of crontab(5) and cron(8) # # m h dom mon dow command 05 21 * * * /usr/local/bin/python3 /home/pi/GoogleDriveUpload.py pi@raspberrypi:~ $ sudo /etc/init.d/cron start [ ok ] Starting cron (via systemctl): cron.service. pi@raspberrypi:~ $ sudo /etc/init.d/cron status ● cron.service - Regular background program processing daemon Loaded: loaded (/lib/systemd/system/cron.service; enabled; vendor preset: enabled) Active: active (running) since Sun 2020-01-12 09:06:28 JST; 11h ago Docs: man:cron(8) Main PID: 319 (cron) Tasks: 1 (limit: 2077) Memory: 2.4M CGroup: /system.slice/cron.service └─319 /usr/sbin/cron -f 1月 12 19:17:01 raspberrypi CRON[5091]: (root) CMD ( cd / && run-parts …rly) 1月 12 19:17:01 raspberrypi CRON[5087]: pam_unix(cron:session): session c…root 1月 12 20:17:01 raspberrypi CRON[5997]: pam_unix(cron:session): session o…d=0) 1月 12 20:17:01 raspberrypi CRON[6001]: (root) CMD ( cd / && run-parts …rly) 1月 12 20:17:01 raspberrypi CRON[5997]: pam_unix(cron:session): session c…root 1月 12 21:00:01 raspberrypi CRON[6313]: pam_unix(cron:session): session o…d=0) 1月 12 21:00:01 raspberrypi CRON[6317]: (pi) CMD (/usr/bin/python3 /home/….py) 1月 12 21:00:01 raspberrypi CRON[6313]: (CRON) info (No MTA installed, di…put) 1月 12 21:00:01 raspberrypi CRON[6313]: pam_unix(cron:session): session c…r pi 1月 12 21:03:01 raspberrypi cron[319]: (pi) RELOAD (crontabs/pi) Hint: Some lines were ellipsized, use -l to show in full. pi@raspberrypi:~ $ sudo /etc/init.d/cron status ● cron.service - Regular background program processing daemon Loaded: loaded (/lib/systemd/system/cron.service; enabled; vendor preset: enabled) Active: active (running) since Sun 2020-01-12 09:06:28 JST; 11h ago Docs: man:cron(8) Main PID: 319 (cron) Tasks: 4 (limit: 2077) Memory: 16.6M CGroup: /system.slice/cron.service ├─ 319 /usr/sbin/cron -f ├─6442 /usr/sbin/CRON -f ├─6446 /bin/sh -c /usr/local/bin/python3 /home/pi/GoogleDriveUpload2… └─6447 /usr/local/bin/python3 /home/pi/GoogleDriveUpload2.py 1月 12 20:17:01 raspberrypi CRON[5997]: pam_unix(cron:session): session o…d=0) 1月 12 20:17:01 raspberrypi CRON[6001]: (root) CMD ( cd / && run-parts …rly) 1月 12 20:17:01 raspberrypi CRON[5997]: pam_unix(cron:session): session c…root 1月 12 21:00:01 raspberrypi CRON[6313]: pam_unix(cron:session): session o…d=0) 1月 12 21:00:01 raspberrypi CRON[6317]: (pi) CMD (/usr/bin/python3 /home/….py) 1月 12 21:00:01 raspberrypi CRON[6313]: (CRON) info (No MTA installed, di…put) 1月 12 21:00:01 raspberrypi CRON[6313]: pam_unix(cron:session): session c…r pi 1月 12 21:03:01 raspberrypi cron[319]: (pi) RELOAD (crontabs/pi) 1月 12 21:05:01 raspberrypi CRON[6442]: pam_unix(cron:session): session o…d=0) 1月 12 21:05:01 raspberrypi CRON[6446]: (pi) CMD (/usr/local/bin/python3 ….py) Hint: Some lines were ellipsized, use -l to show in full. pi@raspberrypi:~ $ |
12行目:$which python3 でpython3のフルパスを取得
39行目:定期処理したい内容
41行目:sudo /etc/init.d/cron startでcronが動作開始
47行目:active(runnig)となっており動作が確認できます。
90行目:指定した21:05にpython3が動作しています。
結果
赤枠がGoogleDriveの固有のfolder_idです。これで出先からも監視カメラの動画を確認することが出来るようになりました。
以上。
1 |