目的
M5StickCを使った玄関の鍵かけ忘れ防止システム(運用編) に監視カメラ要素を追加して、防犯システムを作る1
("まとめ"でも言及するが、まだ課題が多く実運用には耐えられないため、今回は試作の位置づけ)
ドアの鍵状態の検知は、M5StickCを使った玄関の鍵かけ忘れ防止システム(運用編) で実現済みなので、今回は監視カメラの機能を実現する
環境
詳細
- Raspberry Pi 3 ModelB
- カメラモジュール:Raspberry Pi Camera Module V2
- 人感センサー(HC-SR501)
作業メモ
OpenCVによる顔認識プログラムの導入("環境"の③の下準備)
参考サイト
https://sozorablog.com/camera_shooting/
https://youtu.be/9P-Hq8Dh1R0
- 参考サイト で紹介されている顔認証プログラムを導入
$ git clone https://github.com/kotamorishi/installOpenCV.git
$ cd installOpenCV
$ ./installOpenCV.sh
・・
Cloning into 'facial_recognition'...
remote: Enumerating objects: 57, done.
remote: Counting objects: 100% (57/57), done.
remote: Compressing objects: 100% (30/30), done.
remote: Total 57 (delta 23), reused 52 (delta 22), pack-reused 0
Unpacking objects: 100% (57/57), done.
- Opencvを導入
$ sudo pip3 install opencv-python
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting opencv-python
Downloading https://www.piwheels.org/simple/opencv-python/opencv_python-4.7.0.68-cp37-cp37m-linux_armv7l.whl (11.8 MB)
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq 11.8/11.8 MB 1.5 MB/s eta 0:00:00
Collecting numpy>=1.17.0
Downloading numpy-1.21.6.zip (10.3 MB)
qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq 10.3/10.3 MB 1.7 MB/s eta 0:00:00
Installing build dependencies ... done
Getting requirements to build wheel ... done
Preparing metadata (pyproject.toml) ... done
Building wheels for collected packages: numpy
Building wheel for numpy (pyproject.toml) ... done
Created wheel for numpy: filename=numpy-1.21.6-cp37-cp37m-linux_armv7l.whl size=12328135 sha256=6b8e66103117c4d84f9a40e5fac7bc91b8dfacff477fe57692beac60e901d5e4
Stored in directory: /root/.cache/pip/wheels/f8/ee/2a/5ceed41a4d2d69e4f5133e5789b80cd7604fb4eeba6b9d9184
Successfully built numpy
Installing collected packages: numpy, opencv-python
Attempting uninstall: numpy
Found existing installation: numpy 1.16.2
Uninstalling numpy-1.16.2:
Successfully uninstalled numpy-1.16.2
Successfully installed numpy-1.21.6 opencv-python-4.7.0.68
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
- 導入した環境の動作確認
自分の顔画像撮影
(自分を不審者として検知されないために撮影は必要)
$ cd ~/facial_recognition/
$ python3 headshots.py
撮影した画像の学習
$ python3 train_model.py
[INFO] start processing faces...
[INFO] processing image 1/12
[INFO] processing image 2/12
[INFO] processing image 3/12
[INFO] processing image 4/12
[INFO] processing image 5/12
[INFO] processing image 6/12
[INFO] processing image 7/12
[INFO] processing image 8/12
[INFO] processing image 9/12
[INFO] processing image 10/12
[INFO] processing image 11/12
[INFO] processing image 12/12
[INFO] serializing encodings...
学習により生成されたモデルの確認
$ ls -l
合計 968
-rw-r--r-- 1 pi pi 674 1月 2 17:20 README.md
drwxr-xr-x 4 pi pi 4096 1月 2 22:37 dataset
-rw-r--r-- 1 pi pi 7675 1月 2 22:41 encodings.pickle # これ
・・・
顔認証を試してみる
→ maru
が学習時に登録した自分の名前で、自分の顔を撮影した際に検知できたことを確認
$ python3 facial_req.py
[INFO] loading encodings + face detector...
[INFO] starting video stream...
# ここでカメラで自分の顔を撮影
maru
※ちなみにTeratermなどでfacial_req.py
を実行した場合、ディスプレイの画像表示ができずエラーになるケースがあるので、その場合はリモートデスクトップなどで確認してください
$ python3 facial_req.py
[INFO] loading encodings + face detector...
[INFO] starting video stream...
Unable to init server: Could not connect: 接続を拒否されました
Traceback (most recent call last):
File "facial_req.py", line 105, in <module>
cv2.imshow("Facial Recognition is Running", frame)
cv2.error: OpenCV(4.7.0) /tmp/pip-wheel-hb849x9b/opencv-python_1ad64f0dd2fc45758056f9a15186db35/opencv/modules/highgui/src/window_gtk.cpp:635: error: (-2:Unspecified error) Can't initialize GTK backend in function 'cvInitSystem'
"環境"の機能を実装
人が来たことを検知する機能("環境"の①)
参考サイト
https://qiita.com/atmaru/items/2282445d327b0af0e6c1
https://tomosoft.jp/design/?p=8685
- 人感センサーの立ち上がりエッジを検出して、後述するカメラ撮影やLINE Notifyへの通知をするpython moduleを動作
-
User Setting
を各自の環境に応じて設定が必要
#!/usr/bin/python3
import RPi.GPIO as GPIO
import time
import sys
from importlib import import_module
##### User Setting ###############################
# moduleファイルのpath
modulePath = "/home/pi/facial_recognition"
# 人感センサーのGPIO接続先
DETECT_PIN = 23
##################################################
# moduleファイルのimport
sys.path.append(modulePath)
facial_req = import_module("facial_req_module")
send_message = import_module("send_message_module")
def main():
GPIO.setwarnings(False)
# layout設定
GPIO.setmode(GPIO.BCM)
# BCMの23番ピンを入力に設定
GPIO.setup(DETECT_PIN, GPIO.IN)
# callback登録(GPIO.RISING:立上がりエッジ検出、bouncetime:300ms)
GPIO.add_event_detect(DETECT_PIN, GPIO.RISING, callback=callback, bouncetime=300)
try:
# 無限ループ
while(True):
time.sleep(1)
# Keyboard入力があれば終わり
except KeyboardInterrupt:
print("break")
GPIO.cleanup()
def callback(channel):
print("button pushed %s"%channel)
FlgUnknown = facial_req.facial_req()
if FlgUnknown == 1:
send_message.send_message()
if __name__ == "__main__":
main()
カメラ撮影、OpenCVによる不審者特定する機能("環境"の②、③)
参考サイト
https://sozorablog.com/camera_shooting/
-
前項で使った
facial_req.py
(顔認識するプログラム) をベースに認識した顔が不審者かどうかを確認できる機能
を追加(プログラム中ではFlgUnknown
で情報管理)2 -
User Setting
を各自の環境に応じて設定が必要
#! /usr/bin/python3
# import the necessary packages
from imutils.video import VideoStream
from imutils.video import FPS
import face_recognition
import imutils
import pickle
import time
import cv2
##### User Setting ###############################
# 顔認識プログラムのloop回数
MAX_LOOP_NUM = 30
# installした"facial_recognition"フォルダのpath
# unknown検知時の画像ファイル保存先path
datapath = "/home/pi/facial_recognition/"
##################################################
def facial_req():
#Initialize 'currentname' to trigger only when a new person is identified.
currentname = "unknown"
#Determine faces from encodings.pickle file model created from train_model.py
encodingsP = datapath + "encodings.pickle"
#use this xml file
cascade = datapath + "haarcascade_frontalface_default.xml"
# load the known faces and embeddings along with OpenCV's Haar
# cascade for face detection
print("[INFO] loading encodings + face detector...")
data = pickle.loads(open(encodingsP, "rb").read())
detector = cv2.CascadeClassifier(cascade)
# initialize the video stream and allow the camera sensor to warm up
print("[INFO] starting video stream...")
vs = VideoStream(src=0).start()
#vs = VideoStream(usePiCamera=True).start()
time.sleep(2.0)
# loop over frames from the video file stream
loop_count = 0
for loop_count in range(MAX_LOOP_NUM):
# "Unknown" flag
FlgUnknown = 0
# grab the frame from the threaded video stream and resize it
# to 500px (to speedup processing)
frame = vs.read()
frame = imutils.resize(frame, width=500)
# convert the input frame from (1) BGR to grayscale (for face
# detection) and (2) from BGR to RGB (for face recognition)
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# detect faces in the grayscale frame
rects = detector.detectMultiScale(gray, scaleFactor=1.1,
minNeighbors=5, minSize=(30, 30),
flags=cv2.CASCADE_SCALE_IMAGE)
if len(rects) > 0:
# OpenCV returns bounding box coordinates in (x, y, w, h) order
# but we need them in (top, right, bottom, left) order, so we
# need to do a bit of reordering
boxes = [(y, x + w, y + h, x) for (x, y, w, h) in rects]
# compute the facial embeddings for each face bounding box
encodings = face_recognition.face_encodings(rgb, boxes)
names = []
# loop over the facial embeddings
for encoding in encodings:
# attempt to match each face in the input image to our known
# encodings
matches = face_recognition.compare_faces(data["encodings"],
encoding)
name = "Unknown" #if face is not recognized, then print Unknown
# check to see if we have found a match
if True in matches:
# find the indexes of all matched faces then initialize a
# dictionary to count the total number of times each face
# was matched
matchedIdxs = [i for (i, b) in enumerate(matches) if b]
counts = {}
# loop over the matched indexes and maintain a count for
# each recognized face face
for i in matchedIdxs:
name = data["names"][i]
counts[name] = counts.get(name, 0) + 1
# determine the recognized face with the largest number
# of votes (note: in the event of an unlikely tie Python
# will select first entry in the dictionary)
name = max(counts, key=counts.get)
#If someone in your dataset is identified, print their name on the screen
if currentname != name:
currentname = name
#print(currentname)
# stop sequence
facial_req_stop(vs)
FlgUnknown = 0
print("face detect " + currentname)
return FlgUnknown
# update the list of names
names.append(name)
# not detect in list(=Unknown)
print("face detect Unknown")
cv2.imwrite(datapath + 'unknown.jpg', frame)
FlgUnknown = 1
# stop sequence
facial_req_stop(vs)
return FlgUnknown
else:
print("not detect")
time.sleep(1)
# stop sequence
facial_req_stop(vs)
# end of process
return FlgUnknown
def facial_req_stop(vs):
# do a bit of cleanup
vs.stop()
不審者を検知した場合にLINE Notify3へ通知をする機能("環境"の④)
参考サイト
https://sozorablog.com/camera_shooting/ 4
-
前項の
FlgUnknown
が1(不審者あり)
の場合に実行される - 警告文とカメラ画像を送信する機能
-
User Setting
を各自の環境に応じて設定が必要
#!/usr/bin/python3
import requests
##### User Setting ###############################
# unknown検知時の画像ファイル保存先path
datapath = "/home/pi/facial_recognition/"
##################################################
def send_message():
url = "https://notify-api.line.me/api/notify"
token = "ZTmXhjE98KgvLLHQqAajJHYit9x8MUXkI8HO7zNFbOg"
headers = {"Authorization" : "Bearer "+ token}
files = {'imageFile': open(datapath + "unknown.jpg", "rb")}
message = "detect Unknown"
payload = {"message" : message}
r = requests.post(url, headers = headers, params=payload, files=files)
serviceとして登録
ここまでに実装した機能を毎回自動で起動するよう、システムのサービスとして登録する
以前、自分で作った記事を参考に登録
- service用のファイル作成
[Unit]
Description=GPIO INTTERUPT
After=multi-user.target
DefaultDependencies=no
[Service]
Type=simple
ExecStart=/usr/bin/gpio_int.py
Restart=no
[Install]
WantedBy=multi-user.target
- 作成したファイルの配置
$ chmod u+x gpio_int.py
$ sudo cp gpio_int.py /usr/bin/
$ sudo cp gpio_int.service /etc/systemd/system/
- サービスとして認識されているか確認
→ 認識はされているのでOK
$ sudo systemctl list-unit-files --type=service | grep gpio_int
gpio_int.service disabled
- 動作確認
→ エラーなく起動し終了しているのでOK
$ sudo systemctl start gpio_int
$ sudo systemctl stop gpio_int
$ sudo systemctl status gpio_int
● gpio_int.service - GPIO INTTERUPT
Loaded: loaded (/etc/systemd/system/gpio_int.service; disabled; vendor preset: enabled)
Active: inactive (dead)
3月 18 21:00:46 raspberrypi systemd[1]: Started GPIO INTTERUPT.
3月 18 21:00:50 raspberrypi systemd[1]: Stopping GPIO INTTERUPT...
3月 18 21:00:50 raspberrypi systemd[1]: gpio_int.service: Main process exited, code=killed, status=15/TERM
3月 18 21:00:50 raspberrypi systemd[1]: gpio_int.service: Succeeded.
3月 18 21:00:50 raspberrypi systemd[1]: Stopped GPIO INTTERUPT.
3月 18 21:01:40 raspberrypi systemd[1]: Started GPIO INTTERUPT.
3月 18 21:01:41 raspberrypi systemd[1]: Stopping GPIO INTTERUPT...
3月 18 21:01:41 raspberrypi systemd[1]: gpio_int.service: Main process exited, code=killed, status=15/TERM
3月 18 21:01:41 raspberrypi systemd[1]: gpio_int.service: Succeeded.
3月 18 21:01:41 raspberrypi systemd[1]: Stopped GPIO INTTERUPT.
- サービスの有効化
$ sudo systemctl enable gpio_int
Created symlink /etc/systemd/system/multi-user.target.wants/gpio_int.service → /etc/systemd/system/gpio_int.service.
- 再起動して確認
→ 再起動後、サービスが起動していることを確認できたのでOK
$ sudo reboot
$ sudo systemctl status gpio_int
● gpio_int.service - GPIO INTTERUPT
Loaded: loaded (/etc/systemd/system/gpio_int.service; enabled; vendor preset:
Active: active (running) since Sat 2023-03-18 21:04:42 JST; 1min 33s ago
Main PID: 846 (gpio_int.py)
Tasks: 2 (limit: 1939)
CGroup: /system.slice/gpio_int.service
mq846 /usr/bin/python3 /usr/bin/gpio_int.py
3月 18 21:04:42 raspberrypi systemd[1]: Started GPIO INTTERUPT.
動作結果(LINE Notify通知画面)
※画像は適当な雑誌の画像です
以下の通り、不審者を検知した場合に、警告文とカメラ画像をLINE Notifyへ通知することができた
まとめ
目的の達成度は暫定達成くらいのレベル
M5StickCを使った玄関の鍵かけ忘れ防止システム(運用編) に監視カメラ要素を追加して、防犯システムを作る1
理由としては実運用を考えると、残課題が多く残っているため
今後は以下を解決できる方法を検討していく必要あり
- 暗所での利用
- 現在使っているカメラは暗所での撮影は非対応
- 監視カメラという用途を考えると、暗所での撮影対応は必要
- モバイル対応
- 簡単に電源供給ができない場所での利用を想定した場合に必要
- モバイルバッテリーなどの利用を検討
- その場合は消費電力を減らす努力も必要
- とにかく実現までのコスパが悪い
- 今回はありものを使って実現したので、材料準備のコストはかかっていないが、一から揃える場合は結構なコストがかかる
- 正直Switchbotでいいのでは?と思ってしまうところはある・・