ラズパイで作れるよ

ラズパイとOpenCVで監視カメラを自作してみた【前編】

スポンサーリンク

どうも。まっきーです!

ラズパイとOpenCVを用いた「顔認証」「動体検知 」で監視(防犯)カメラを作成してみました。

「ラズパイを使って何かを自作したい!」と思っている方は参考にしてみてください!

初心者の方でもできるように説明していきます!

完成イメージ図

実装する主要機能は以下の3つです。

  • OpenCVで動体検知
  • 顔認識(end to endリンク)
  • LINE NotifyでLINEに通知

イメージ図のように、動体(不審な動き)を検知した場合、顔を認識した場合にLINEの通知を行うという簡易的な防犯カメラを作成していきます。

実際の動画

全体のフローチャート

全体的なフローは以下のようになります。

より実用的な監視(防犯)カメラにするために、動体検知後10秒以内に顔認証すれば、帰宅の通知。10秒たっても顔認証がされなかったら不審な動きと判断して通知を送るような機能にしてみました!

必要なもの

基本的に必要なものは以下の2つのみです!

用意するもの

  • Raspberry Pi 3B+
  • ラズパイカメラモジュール

ディレクトリ構造

$ tree

SecurityPJT
│
├── security_camera.py#メインプログラム
├── body_detect.py#動体検知プログラム
├── line.py#LINE通知用プログラム
├── face_recog.py#顔識別用プログラム
│
├── haarcascade_frontalface_default.xml#顔を取るための特徴量(OpenCvからダウンロード)
├── face_capture.py #顔認証用画像撮影プログラム
├── datasets #顔認証元画像保存ディレクトリ
│   ├── User.1.1.jpg
│   ├── User.1.2.jpg
│   ├── User.1.3.jpg
│   :
│   (他の人を付け加えるならここに追加される)
│ 
├── face_train.py #顔画像訓練用
├── trainer # 訓練データ保存先ディレクトリ
│   └── trainer.yml
└── image #動体検知・顔認証画像保存用ディレクトリ

「datasets,trainer,image」のディレクトリはあらかじめ作成しておきます。

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

やることは3つのみです。

  • OpenCVのインストール
  • カスケード分類器のダウンロード
  • LINE Notifyの登録

OpenCV

今回はOpenCVを使用しますので、OpenCVをダウンロードします。

ターミナルで以下のコマンドを入力してください。

sudo apt update 
sudo apt upgrade
#必要なライブラリをインストール 
sudo apt-get install libatlas-base-dev
sudo apt-get intall libjasper-dev
sudo apt-get intall libqtgui4
sudo apt-get intall python3-pyqt5
sudo apt install libqt4-test
#バージョン指定でOpenCVをインストール 
sudo pip3 install opencv-python==4.1.0.25

カスケード分類器のインストール

「SecurityPJT」ディレクトリで実行します。

wget https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_default.xml

ダウンロードできない場合は「ラズパイ OpenCVインストール方法」 にも詳しく書いてありますので参考にしてみてください!

LINE Notify

LINENotifyの設定方法は「ラズパイからLINEに通知を送る with 人感センサー」に詳しくまとめてあるので、参考にしてください。(10分ぐらいで設定できます!)

参考記事
ラズパイからLINEに通知を送る with 人感センサー

続きを見る

ソースコード

基本的にすべてのコードをコピペするだけで実装できると思います。細かなパラメーターの設定等、必要なところにはマークと解説を入れてあります!

body_detect.py

import cv2
import datetime
import time

def body_detect():
    #画像保存ディレクトリ
    save_dir = './image/'
    file_suffix = 'detect.jpg'

    # カメラ映像を取得
    cam = cv2.VideoCapture(0)
    #Video Graphics Array(略称:VGA)を設定
    cam.set(3, 640) # set video 横
    cam.set(4, 480) # set video 高さ

    #2値化したときのピクセル値
    DELTA_MAX = 255
    #ドットの変化を検知する閾値
    DOT = 8
    #比較用のデータを格納
    avg = None
    while True:
        # 1フレームずつ取得する。
        ret, frame = cam.read()
        if not ret:
            break
        #画像を反転
        frame = cv2.flip(frame,-1)
        #画像ファイル名用の時間取得
        date = datetime.datetime.now()
        file_name = str(date) + file_suffix 

        # グレースケールに変換
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        # 比較用のフレームを取得するavgがNoneの場合進むつまり、値があればコピーしていく
        if avg is None:
            avg = gray.copy().astype("float")
            continue

        # 現在のフレームと移動平均との差を計算
        cv2.accumulateWeighted(gray, avg, 0.6)
        frameDelta = cv2.absdiff(gray, cv2.convertScaleAbs(avg))

        # デルタ画像を閾値処理を行う 閾値処理された後の二値画像が帰ってくる
        thresh = cv2.threshold(frameDelta, DOT, DELTA_MAX, cv2.THRESH_BINARY)[1]

        # 画像の閾値に輪郭線を入れる 戻り値は(画像、輪郭、階層)
        img,contours, hierarchy = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        #輪郭の面積を求めてデータサイズを超えるものを見つけた場合検知とする
        max_area = 0
        for cnt in contours:
            area = cv2.contourArea(cnt)#この時の輪郭の値を計算して面積を求める
            if max_area < area:
                max_area = area
            if max_area > 8000:#面積は目的に適した値に調節
                print("動体検知しました"+str(date))
                #frameに枠を描画
                #frame = cv2.drawContours(frame, contours, -1, (0, 255, 0), 3)
                cv2.imwrite(save_dir + file_name,frame)
                return  save_dir + file_name
            else:
                pass
        
        frame = cv2.drawContours(frame, contours, -1, (0, 255, 0), 3)
        # 結果を出力
        cv2.imshow("Frame", frame)
        key = cv2.waitKey(30)
        if key == 27:
            break
    cam.release()
    cv2.destroyAllWindows()

動体検知をするためのコードです。マークがついている行の値を変更することによって、動体検知の精度を変更することができます。適当に変更してみて、ご自身の環境に適した値を見つけてみてください!

動体検知の仕組みについては、「ラズパイとOpenCVで動体検知」で詳しく解説しています。

参考記事
ラズパイとOpenCVで動体検知

続きを見る

line.py

import requests

def send(message,image):

    url = "https://notify-api.line.me/api/notify"
    access_token = '取得したアクセストークン'
    headers = {'Authorization': 'Bearer ' + access_token}

    payload = {'message': message}
    files = {'imageFile': open(image, 'rb')}
    r = requests.post(url, headers=headers, params=payload, files=files,)

自身で取得したアクセストークンを入力することで、関数が使えるようになります!

face_recog.py

import cv2
import numpy as np
import os 
import datetime

def face_recog():
    #撮影画像保存用ディレクトリ設定
    save_dir = './image/'
    file_suffix = 'recog.jpg'
    #顔認識の準備
    recognizer = cv2.face.LBPHFaceRecognizer_create()
    recognizer.read('trainer/trainer.yml')
    cascadePath = "haarcascade_frontalface_default.xml"
    faceCascade = cv2.CascadeClassifier(cascadePath)
    font = cv2.FONT_HERSHEY_SIMPLEX
    #idの初期化
    id = 0
    # 名前とidを紐づける: 例 ==> まっきー: id=0,  etc
    names = ['makky'] #必要に応じて増やす
    # カメラ初期化
    cam = cv2.VideoCapture(0)
    cam.set(3, 640) # set video widht
    cam.set(4, 480) # set video height
    # 顔を認識する最小の長方形を定義
    minW = 0.1*cam.get(3)
    minH = 0.1*cam.get(4)
    #制限時間を設定するための準備→現在時間(now)と+10秒した(end)
    now = datetime.datetime.now()
    end = now + datetime.timedelta(seconds = 10)

    while now <= end:
        ret, img =cam.read()
        img = cv2.flip(img) # 取得画像を逆さにしたい場合は(img,-1)
        gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)

        #現在時間、ファイル名設定
        date = datetime.datetime.now()
        file_name = str(date) + file_suffix
        #ループを抜けるための現在時間の取得
        now = datetime.datetime.now()
            
        faces = faceCascade.detectMultiScale( 
            gray,
            scaleFactor = 1.2,
            minNeighbors = 5,
            minSize = (int(minW), int(minH)),
        )
        for(x,y,w,h) in faces:
            cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)
            id, confidence = recognizer.predict(gray[y:y+h,x:x+w])
            # 信頼度 100 ==> "0"  confidenceが0に近いほど信頼度が高い 
            if (confidence < 100):
                id = names[id]
                confidence = "  {0}%".format(round(100 - confidence))
                cv2.putText(img, str(id), (x+5,y-5), font, 1, (255,255,255), 2)
                cv2.putText(img, str(confidence), (x+5,y+h-5), font, 1, (255,255,0), 1) 
                #cv2.putText(img, str(date), (x-50,y-50), font, 1, (255,0,0), 1)
                #撮影画像の保存
                cv2.imwrite(save_dir+file_name,img)
                #ログ用
                print("撮影"+id,save_dir+file_name)
                return id,save_dir+file_name
            else:
                id = "unknown"
                confidence = "  {0}%".format(round(100 - confidence))
                image = cv2.imwrite(save_dir+file_name,img)
                return id,save_dir+file_name
                
        #映像表示
        cv2.imshow('camera',img)
        k = cv2.waitKey(10) & 0xff # Press 'ESC' for exiting video
        if k == 27:
            break
    #10秒経過後の処理
    # Do a bit of cleanup
    print("\n [INFO] Exiting Program and cleanup stuff")
    cam.release()
    cv2.destroyAllWindows()        
    id = "timeover"
    return id,save_dir+file_name

10秒たっても顔認証されなかったら...というのに対応させるために、timedeltaを使用しています。

マークしてある、namesのところは、登録する方の数に応じて名前をご自身で付け加えてください!

face_capture.py

import cv2
import os
cam = cv2.VideoCapture(0)
cam.set(3, 640) # set video width
cam.set(4, 480) # set video height
face_detector = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
# フェイスid入力要請
face_id = input('\n enter user id end press <return> ==>  ')
print("\n [INFO] Initializing face capture. Look the camera and wait ...")
# count変数の初期化
count = 0
while(True):
    ret, img = cam.read()
    img = cv2.flip(img) # 取得写真を反転させる場合は(img,-1)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_detector.detectMultiScale(gray, 1.3, 5)
    for (x,y,w,h) in faces:
        cv2.rectangle(img, (x,y), (x+w,y+h), (255,0,0), 2)     
        count += 1
        # 撮影した写真をdatasetsに保存
        cv2.imwrite("datasets/User." + str(face_id) + '.' + str(count) + ".jpg", gray[y:y+h,x:x+w])
        cv2.imshow('image', img)
    k = cv2.waitKey(100) & 0xff # Press 'ESC' for exiting video
    if k == 27:
        break
    elif count >= 30: # Take 30 face sample and stop video
         break
# Do a bit of cleanup
print("\n [INFO] Exiting Program and cleanup stuff")
cam.release()
cv2.destroyAllWindows()

顔認証用の画像を撮影するためのプログラムです。countの数を変えることによって、撮影する枚数を変更できます。

face_train.py

import numpy as np
from PIL import Image
import os
import cv2
# Path for face image database
path = 'datasets'
recognizer = cv2.face.LBPHFaceRecognizer_create()
detector = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
# function to get the images and label data
def getImagesAndLabels(path):
    imagePaths = [os.path.join(path,f) for f in os.listdir(path)]     
    faceSamples=[]
    ids = []
    for imagePath in imagePaths:
        PIL_img = Image.open(imagePath).convert('L') # convert it to grayscale
        img_numpy = np.array(PIL_img,'uint8')
        id = int(os.path.split(imagePath)[-1].split(".")[1])
        faces = detector.detectMultiScale(img_numpy)
        for (x,y,w,h) in faces:
            faceSamples.append(img_numpy[y:y+h,x:x+w])
            ids.append(id)
    return faceSamples,ids
print ("\n [INFO] Training faces. It will take a few seconds. Wait ...")
faces,ids = getImagesAndLabels(path)
recognizer.train(faces, np.array(ids))
# Save the model into trainer/trainer.yml
recognizer.write('trainer/trainer.yml') # recognizer.save() worked on Mac, but not on Pi
# Print the numer of faces trained and end program
print("\n [INFO] {0} faces trained. Exiting Program".format(len(np.unique(ids))))

顔認証用画像の訓練用プログラムです。これはそのまま実行するだけで大丈夫です。

security_camera.py(メインのコード)

*顔認証後にtime.sleepを2秒加えました(11月6日) 

import line
import body_detect as body
import face_recog as face
import datetime
import time

if __name__ == "__main__":
    while True:
        #idの初期化
        id = 0
        #動体検知スタート(戻り値は保存した写真のパス)
        b_path = body.body_detect()
        #顔認証スタート(10秒間、戻り値id,写真のpath)
        id,f_path = face.face_recog()
        #顔検知後、2秒sleep
        time.sleep(2)
        #顔認証の戻り値で場合分け
        if id == "unknown":
            line.send("登録されていない方が写っています",f_path)
        elif id == "timeover":
            #動体検知の画像を送信
            line.send("動体検知しました",b_path)
        else:    
            line.send(id+"さんが帰ってきました",f_path)
            




    

監視カメラプロジェクトのメインのプログラムです。

フローチャートの通りに書いてあります。idの値によって条件分岐しています。

実装の手順

実装の手順は以下のようになります

  1. face_capture.pyで訓練用の画像撮影
  2. face_train.pyで画像を訓練
  3. security_camera.pyを実行

この手順で実行することで、基本的な機能の確認ができると思います。

顔認証はコードの通り実行すれば問題ないですが、仕組みについて詳しく知りたい方は「ラズパイカメラとOpenCvを使って女優の顔認識してみた」を参考にしてみてください!

設置や自動起動については次回、やっていきたいと思います!

次に記事はコチラ→「ラズパイとOpenCVで監視カメラを自作してみた【後編】

スポンサーリンク

-ラズパイで作れるよ