やりたいこと(再掲)
最終的にやりたいことは、以下の5つの機能を持つ「最強の防犯カメラ」を作ることです。機能①〜機能③は、市販されている多くの防犯カメラでも持っている機能ですが、機能④や機能⑤の顔認証機能を持つ監視カメラはまだ多くないと思います。
 機能①.動画を24時間撮影し、カメラ本体に動画で記録する
 機能②.動画をWebブラウザや他の機器から参照できるようにライブ配信する
 機能③.動体を検知したら、静止画をLineに通知する
 機能④.家族の顔を認証したら、静止画をLineに通知する
 機能⑤.家族の顔を認証したら、◯◯さんおかえり!!と喋る

家に帰ると、顔を見て「○○さん、おかえり!!」と言ってくれる辺りが、スマートハウスに一歩づ近づいている気がします。
実現に向けた連載
最強の「防犯カメラ」を作成するために、以下のように少しずつに記事を書いていきます。
 1回目:カメラの設定と動画記録
 2回目:カメラ映像のライブ配信
 3回目:動体検知機能とLineへの通知
 4回目:顔認証機能とLineへの通知 ←この記事
 5回目:Raspberry PiへのAlexaの搭載
 6回目:顔認証後にAlexaで音声通知
4回目:顔認証機能とLineへの通知
前回は動体検知機能を作って、カメラが動体を検知した時にLineに通知するようにしてみました。今回は、いよいよ顔検知・顔認証機能を作っていきます。
今回はReal-Time Face Recognition: An End-to-End Projectで紹介されている方法で、顔検知・顔認証機能を作っていきます。
認証用の顔データの学習
顔認証を行うためには、認証する顔データを予め学習させておく必要があります。まずは、顔データの撮影と学習を行っていきます。
①防犯カメラサービスの停止
まずは、防犯カメラサービスを止めておきます。
| 
					 1  | 
						root@raspberrypi:/home/pi/camera# systemctl stop flask.service  | 
					
②顔データ撮影用のプログラムのダウンロード
顔データ撮影用のプログラムは自分で作っても良いですが、Real-Time Face Recognition: An End-to-End Projectで利用しているものがGitHubにあるので、これを利用します。以下のコマンドを入力して「01_face_dataset.py」をダウンロードします。
| 
					 1  | 
						root@raspberrypi:/home/pi/camera# wget https://raw.githubusercontent.com/Mjrovai/OpenCV-Face-Recognition/master/FacialRecognition/01_face_dataset.py  | 
					
このプログラムでは、カメラで画像を撮影し、その画像の中にある人の顔を認識して30枚の顔画像ファイルを出力するものです。
③顔検知用の正面顔特徴分類器のダウンロード
次に「01_face_dataset.py」で顔検知するために必要な、正面顔用の特徴分類器「haarcascade_frontalface_default.xml」をOpenCVのGitHubからダウンロードします。
| 
					 1  | 
						root@raspberrypi:/home/pi/camera# wget https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_default.xml  | 
					
④画像の格納ディレクトリの作成
画像を保存するディレクトリ「dataset」を作成しておきます。
| 
					 1  | 
						root@raspberrypi:/home/pi/camera# mkdir dataset  | 
					
⑤登録用画像の撮影
それでは「01_face_dataset.py」を実行して、顔を撮影します。このプログラムは、画面に撮影の様子を表示するので、VNCでログインしたコンソールから実行します。また、カメラにアクセスするのでroot権限での実行が必要です。
| 
					 1  | 
						root@raspberrypi:/home/pi/camera# python 01_face_dataset.py  | 
					
プログラムを実行すると、以下のようにユーザIDを聞かれます。ユーザIDは、登録する人ごとに1からの連番を振るものです。一人目の登録であれば「1」を入力してエンターを押します。すると、画面にカメラの映像が表示されるので、カメラの前に立って顔を撮影します。顔を認識すると、青色の枠で表示されるので、様々な表情や近さで撮影してみてください。1〜2分ほどカメラを見ていると30枚分の顔画像が撮影され、プログラムが終了します。

なお、複数の人の画像を登録する場合は、もう一度プログラムを起動して、ユーザIDに2などを入力して同様に撮影して下さい。
⑥学習用のプログラムのダウンロード
次に、撮影した顔データを学習させるためのプログラム「02_face_training.py」をダウンロードします。
| 
					 1  | 
						root@raspberrypi:/home/pi/camera# wget https://raw.githubusercontent.com/Mjrovai/OpenCV-Face-Recognition/master/FacialRecognition/02_face_training.py  | 
					
⑦02_face_training.pyの変更
オリジナルの「02_face_training.py」は、OpenCV「3.3.0」以上で動作するので、RaspberryPiにインストールされる「3.2.0」で実行するためには、以下の2点の修正を行う必要があります。
変更点1:21行目あたり「LBPHFaceRecognizer_create()」を以下のように変更。
| 
					 1 2  | 
						#recognizer = cv2.face.LBPHFaceRecognizer_create() recognizer = cv2.face.createLBPHFaceRecognizer()  | 
					
変更点2:51行目あたり「recognizer.write()」を以下のように変更。
| 
					 1 2  | 
						#recognizer.write('trainer/trainer.yml') recognizer.save('trainer/trainer.yml')  | 
					
⑧学習済みデータ格納ディレクトリの作成
学習済みデータを格納する「trainer」ディレクトリを作成します。
| 
					 1  | 
						root@raspberrypi:/home/pi/camera# mkdir trainer  | 
					
⑨学習用のプログラムの実行
それでは「02_face_training.py」を実行して、撮影した顔データを学習させます。「trainer」フォルダに「trainer.yml」が作成されていればOKです。
| 
					 1 2 3 4 5 6 7 8 9  | 
						root@raspberrypi:/home/pi/camera# python 02_face_training.py   [INFO] Training faces. It will take a few seconds. Wait ...  [INFO] 2 faces trained. Exiting Program root@raspberrypi:/home/pi/camera# ls -l trainer/ 合計 7232 -rw-r--r-- 1 root root 7404199  2月  1 15:58 trainer.yml  | 
					
以上で、認証用の顔データの学習は完了です。
Line通知機能の修正
ここからは、顔が認証された場合にLineへ通知する機能を作成していきます。
①functionノードの修正
Node-redの一つ目のfunctionノードに顔認証用の分岐条件を追加します。プログラムの内容は、ほぼ動体検知の場合と同じです。
| 
					 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  | 
						let files = msg.req.files; let action=msg.payload.action; let text=msg.payload.text; let msg1 = msg; let msg2 = {}; //ファイル無し if (files.length === 0) {    msg1.payload = "サーバ通知ERR 添付ファイルなし";    return [msg1, null]; //ファイル有り }else{     //動体検知     if(action=='move'){         msg1.payload = "サーバ通知OK";         msg2.text = text;         msg2.payload = files[0].buffer;         msg2.filename = "/root/.node-red/cameraPicts/" + files[0].originalname;         return [msg1, msg2];     //-----ここから追加------------------------     //顔認証     }else if(action=='goHome'){         msg1.payload = "サーバ通知OK";         msg2.text = text+"が帰ってきました";         msg2.payload = files[0].buffer;         msg2.filename = "/root/.node-red/cameraPicts/" + files[0].originalname;         return [msg1, msg2];    //-----ここまで追加------------------------     //その他     }else{         msg1.payload = "サーバ通知ERR アクション不正";         return [msg1, null];     } }  | 
					
②ノードのデプロイ
画面右上の「デプロイ」ボタンを押して、作成したフローをデプロイします。

顔検知・顔認証プログラムの作成
ここからは、Camera.pyに顔認証機能を追加していきます。なお、完全なソースコードはGitHubのnaka-kazz/RasPiCameraに置いたので、適宜参照してください。
①定数定義の追加
まずは、Camera.pyの定数定義の一番下に、定数定義を追加します。
| 
					 1 2 3 4 5 6 7 8 9 10 11  | 
						################################################### ## 定数定義 ################################################### #名前変数 names=['none','papa','mama','taro','hanako','zirou'] # 顔認証のための学習データを読み込む cascadePath ="haarcascade_frontalface_default.xml" faceCascade = cv2.CascadeClassifier(cascadePath); recognizer = cv2.face.createLBPHFaceRecognizer() recognizer.load('trainer/trainer.yml')  | 
					
namesは、ライブ配信映像やログに顔を検知した時に名前を表示させるものです。配列要素の1番目から、顔の撮影時に登録したユーザIDの順に名前を入力して下さい。
faceCascadeは、カメラ画像の中から顔を検知するためのものです。また、recognizerは顔認証のためのエンジンであり、上で学習させた顔データをロードしています。
②faceDetect関数の追加
今回のメインである、顔認証の機能であるfaceDetect関数を、moveDetect関数の下に追加します。
| 
					 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  | 
						###################################################     ## 顔認証のためのメソッド     ###################################################     @staticmethod     def faceDetect(img):         #入力画像をグレースケールに変換         grayImg=cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)         #顔検知を行う         faces = faceCascade.detectMultiScale(grayImg,scaleFactor = 1.2,minNeighbors = 5,minSize = (50, 50))         # 顔が検出された場合         for(x,y,w,h) in faces:             #顔認証を行う             no, confidence = recognizer.predict(grayImg[y:y+h,x:x+w])             # 信頼度が0〜100でない場合は終了             if (confidence < 0 and confidence > 100):                 break             #現在日付を取得             nowstr=datetime.datetime.now().strftime("%Y%m%d_%H%M%S")             nowTime=time.time()             #ログ出力             print(nowstr+' 顔検知  '+names[no]+':'+"  {0}%".format(round(100 - confidence)))             #サーバに通知(一定時間間隔)              if int(nowTime) - befTimes[no] > interval:                 #検知時間を保存                 befTimes[no]=int(nowTime)                 #ファイルに保存                  filename=pictpath+'/'+names[no]+nowstr+'.jpg'                 cv2.imwrite(filename, img)                 #サーバ通知                 file=open(filename, 'rb').read()                 files = {'file': (names[no]+nowstr+'.jpg', file, 'image/jpeg')}                 params = {'action': 'goHome', 'text': str(names[no])}                 response = requests.post(url, files=files, data=params)                 #サーバ通知のログを出力                 print(nowstr+' サーバ通知 顔'+str(response.status_code)+':'+str(response.content))             #配信画像に枠と名前を付ける             cv2.rectangle(img, (x,y), (x+w,y+h), (0,255,0), 2)             cv2.putText(img, str(names[no]), (x+5,y-5), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,255), 2)             cv2.putText(img, str(confidence), (x+5,y+h-5), cv2.FONT_HERSHEY_SIMPLEX, 1, (255,255,0), 1)         return;  | 
					
プログラムの特徴的な部分だけ説明していきます。まず、顔認証に色情報は不要なので、処理を軽くするために、カメラの画像をグレースケール画像に変換しています。
次に「faceCascade.detectMultiScale()」で、カメラで撮影した画像の中の人の顔を認識します。そして、次のforループの中で、認識されたそれぞれの顔について「recognizer.predict()」で顔認証を行っています。OpenCVを使うと、この一行だけで顔認証が行えます。
「recognizer.predict()」の戻り値は、顔を登録した時のユーザIDの番号と信頼度の2つです。信頼度が0以上で100以下だったら、顔認証OKとして判断し、ログ出力やサーバ通知を行っています。
また「#配信画像に枠と名前を付ける」の部分は、ライブ配信する画像に認識した顔の周りに枠をつけて、名前や信頼度を表示させるためのものです。
③faceDetect関数の呼出し部分の追加
上で定義したfaceDetect関数を呼び出す部分を、moveDetect関数内の「#ログ出力」の下に追加します。動体検知後に顔認証を行うことで、全体の処理を軽くできます。
| 
					 1 2 3 4 5 6 7 8  | 
						#ログ出力 print(nowstr+' 動体検知 '+filename+' '+str(max_area)) #顔認証 Camera.faceDetect(img) #<------------------ここに追加 #サーバに通知(一定時間間隔) if int(nowTime) - befTimes[0] > interval:  | 
					
④テスト
それでは、作成したプログラムを動かしてみましょう。プログラムが起動したら、カメラに顔を向けてみて下さい。以下のようにログが表示されて、Lineに通知が来れば完成です!!
| 
					 1 2 3 4 5 6 7 8 9 10 11 12  | 
						root@raspberrypi:/home/pi/camera# ./cameraServer.sh  Starting camera thread.  * Serving Flask app "cameraServer" (lazy loading)  * Environment: production    WARNING: Do not use the development server in a production environment.    Use a production WSGI server instead.  * Debug mode: off  * Running on http://0.0.0.0:80/ (Press CTRL+C to quit) 20200201_185254 動体検知 /home/pi/camera/picts/move_20200201_185254.jpg 1661.0 20200201_185254 サーバ通知 動体 200:サーバ通知OK 20200201_185256 顔検知  mama:  32.0% 20200201_185256 サーバ通知 顔200:サーバ通知OK  | 
					
チューニング
プログラムがちゃんと動くようになったら、顔認証の精度を高くできるように、顔データやパラメータを調整してみて下さい。
・顔データ:光の当たり方や顔の大きさなどで、認証できたりできなかったりします。認証の精度を高めるためには、実際に防犯カメラを設置する場所に置いて、学習用の顔データを撮影すると良い結果となりました。
・minSize:faceCascade.detectMultiScale()のminSizeパラメータは、顔検知を行う最小サイズを設定します。カメラから離れている状態で顔認証してしまうと、範囲が狭く範囲内のピクセル数が少ないので精度が落ちてしまいます。これも、実際の設置場所に合わせて大きな値とすると精度が高くなりました。
・confidence:ドアセキュリティに利用する場合など、高い信頼性が求められる場合には、confidenceの判定条件をキツくした方が良いと思います。
ちなみにチューニングを繰り返していると、我が家では80%ぐらいの精度でそれぞれの人の顔を認証できるようになりました。たまに、子供の顔をパパとして認証してしまいますが、これは親子の顔が似ているからなんですかねー?
CPU・メモリ使用率
ここまでの動画配信・記録、動体検知・line通知、顔検知・顔認証機能を含む防犯カメラをRaspberry Pi 3B+で実行した時のCPU使用率、メモリ使用率は以下のとおりでした。
・平常時(動画配信・記録時)

・繁忙時(動体検知・顔認証の動作時)

この結果からも、十分にRaspberry Pi 3B+で動作可能かと思います。あとはAlexaの動作に耐えられるかです。
おわりに
今回は防犯カメラに顔認証機能を追加し、Lineに通知することをやってみました。次回は、防犯カメラにAlexaを搭載したいと思います。
間違い・改善点や質問など、あったらコメント欄に書いていただければと思います。
連載記事
 1回目:カメラの設定と動画記録
 2回目:カメラ映像のライブ配信
 3回目:動体検知機能とLineへの通知
 4回目:顔認証機能とLineへの通知
 5回目:Raspberry PiへのAlexaの搭載 ←次はこれ
 6回目:顔認証後にAlexaで音声通知
  
  
  
  
コメント
初めまして、いつもブログを楽しみに拝見しています。
私もRaspberry piを使用し画像認識で積層灯を認識し
アクションをしたいと思っており試行錯誤しています、
教えていただけませんでしょうか?
ブログをお読みいただき有ありがとうございます。
私にできることでしたら、回答させていただきますので、メールでもFacebookのIMでも大丈夫ですので、
ご連絡いただければと思います。
④のテストでプログラミングを実行すると、
「base_camera.py, line 94, in _thread」
「Camera.py, line211, in frames」
という2つのエラーが出てしまうのですが、解決策を教えてください。
何度も質問してしまいすみません。
こちらもエラーの全文を教えていただけば解決策がわかるかもしれません。Camera.pyの211行目が間違っているようですが、211行目には何が書いてありますでしょうか?
顔認証の精度を高くするため具体的にやったことを教えてください。
ブログをお読みいただきありがとうございます。精度向上のための施策ですが、以下をやりました。参考になれば。
1.実際に使う場所にカメラを設置して顔を撮影する
2.運用を想定して、カメラを意識する事なくその場を通過するように顔を撮影する
3.朝、昼、夕、夜など、時間に応じて明るさ(光の加減)が変わるのでそれぞれごとに顔を撮影する
質問に答えていただきありがとうございます。
またお聞きしたいことがあるのですが、顔認証では顔データを学習しているのですか? それともカメラに映った顔を撮影した30枚の画像から当てはまるものを選ぶことで認証しているのですか?
顔認証の仕組みについて教えてください。
質問ばかりで申し訳ないです。、
いつもありがとうございます。顔認証では、顔の特徴点を学習して顔を認証しています。
ただし、ブログに記載の方法では学習するのは初回に顔の写真を撮影した時のみで、その後顔認証を行なっても学習データが更新されることはありません。
なので、徐々に精度を高めたい場合には、動体検知を行なった写真を保存しておき、定期的に再学習させると精度があがると思われます。
詳細は以下を参照ください。
https://www.hackster.io/mjrobot/real-time-face-recognition-an-end-to-end-project-a10826
質問になるのですが、顔認証以外の人だけが動体検知や顔認証をしたときにLINEに通知が来るようにしたいのですが、何かいい案はありますか?
ブログをお読みいただきありがとうございます。物体検出(ObjectDetection)して人を検知することでできます。
Rasberry Pi用の記事は近々書こうと思いますが、以下にJetson nano用のものがありますので、参考になると思われます。
https://www.smarthome-diy.info/blog/developper/smarthome/2020/12/2471/