はじめに

以前、『色違いポケモンを守る為、監視カメラを自作する』を執筆させてもらった者です。
実は、とある環境を構築したいという希望があり、その一環としての監視カメラ作成だったのです。

奥さんにダメ出しされたからじゃないんだからねっ(涙)

玄関をあけて、「ただいま」と言ったら、『おかえりなさい。ご主人様 今日はいかがでしたか?』と言われたいという壮大な野望があります。このような装置をつくって、帰宅時のオアシスを用意したいのです。奥さんに言ってもらうとかすればいいじゃん?という意見は聞こえません。

やりたいこと

  1. 帰宅したことに反応する
  2. 誰が帰宅したか判別する
  3. 2の結果、自分なら「おかえりなさい」他の人なら「どちらさまですか?」と話しかける
    奥さんの場合、処理終了(うるさいって言われるから)
  4. 応答に合わせ、粋な返しをする
  5. 2回くらいの会話ラリーの後、「ゆっくりお休みください」と言って処理終了

この様な装置を作ろうという目標を立てました。

1の「帰宅したことに反応する」は、以前『色違いポケモンを守る為、監視カメラを自作する』で使用した人感センサーを使用することで、「誰かが来た」という反応ができる為、その時に使用したソースを使用することで実現できます。なので今回は、その2にあたる「誰が帰宅したか判別する」を実現しようと思います。

誰が帰宅したか判別する

誰がという部分を判別するためには、何かを基準に判別・認証しなければなりません。

世の中には個人を判別するための声紋認証や指紋認証、虹彩認証などがあり、今回は手軽に判別することができ、身近な判別方法である顔認証をやってみようと思います。

使用機材

今回は『色違いポケモンを守る為、監視カメラを自作する』と同じ環境になる以下の機材を使用しますが、モーションセンサーは今回使用しません。

  • Raspberry Pi 4 Model B (OS:Raspbian GNU/Linux 12)
  • QIAN 1080P HD Webカメラ

プログラム

今回もPythonでVideo映像から顔認証し、〇〇さんだっ!とRaspberry Piで反応できるプログラムを組みたいと思います。

Raspberry Piが顔と認識できるように設定

Raspberry Piが顔と認識するためには、いろいろな方法がありますが、
今回はOpenCVの機能を拝借して、顔と判断できる様にしてみようと思います。

OpenCVでは、顔・目などを検出できるカスケード識別器のファイルが用意されており、下記リンク先からダウンロードできます。

https://github.com/opencv/opencv/tree/master/data/haarcascades

上記のプロジェクトをCloneしておきます。

認識するかな〜?

上記で取得したファイルを使用して、取り込んだ映像から顔が認識されるか確認してみようと思います。

#! /usr/bin/python

import cv2 as cv

# haarcascade_frontalface_default.xml が置いてあるパスを指定する
# 個人の環境に合わせて指定してあげてください。
HAAR_FILE = \
"(保存しているフォルダ)/haarcascade_frontalface_default.xml"

cascade = cv.CascadeClassifier(HAAR_FILE)

# 取り込み開始
cap = cv.VideoCapture(0)

while(True):
    ret, frame = cap.read()
    
    face = cascade.detectMultiScale(frame)

    # 顔を四角で囲む線を作成
    for x, y, w, h in face:
        cv.rectangle(frame,(x,y),(x+w,y+h),(0,0,255),1)

    # 線を描画
    cv.imshow('Capture',frame)
    
    # q を押したら終了
	key = cv2.waitKey(1) & 0xFF
	if key == ord("q"):
		break

cap.release()
cv.destroyAllWindows()

結果

顔に赤枠が出るようになりました。この赤枠は自分が横に移動した時にも追随して表示してくれます。

顔が誰かを判別する

先ほどのソースでOpenCVを使い、顔が分かることを確認できたので、OpenCVにある顔検出と顔認識のAPIを使用してみようと思います。尚、この顔検出、顔認識はOpenCV 4.5.4 以上である必要があります。

流れとしては以下の様になります。

1.  ユーザーの特徴量を作成して辞書に登録。
2.  画像から顔を検出して特徴量を計算し、辞書内の一番近い特徴量を探し出す。
3. 上(2)の結果から、ユーザー名を特定する。

まず、だれの顔かを判別するための情報を得るため顔写真を用意します。
下のプログラムを実行し、顔情報を画像で保存します。

#! /usr/bin/python

import os
import sys
import cv2 as cv

name = input("Type your name: ")
userDirPath = "dataset/" + name
try:
    os.makedirs(userDirPath, exist_ok=True)
except Exception as e:
    print(f"Error: {e}")
    sys.exit()

cap = cv.VideoCapture(0)
if not cap.isOpened():
    print("カメラが認識できません")
    sys.exit()

cv.namedWindow("Take a pic", cv.WINDOW_NORMAL)
cv.resizeWindow("Take a pic", 500, 300)

img_counter = 0

while True:
    ret, frame = cap.read()
    print("ret:", ret)  # デバッグ用

    if not ret:
        print("フレーム取得失敗")
        break

    cv.imshow("Take a pic", frame)

    k = cv.waitKey(1)
    if k%256 == 27:
        print("ESCで終了")
        break
    elif k%256 == 32:
        img_name = f"{userDirPath}/image_{img_counter}.jpg"
        cv.imwrite(img_name, frame)
        print(f"保存: {img_name}")  # デバッグ用
        img_counter += 1

cap.release()
cv.destroyAllWindows()

上記コードで、取得する顔画像は、正面だけでなく様々な角度で撮影し判定する材料を多く用意させます。

結果

さまざまな角度から自分の画像を取得してフォルダ内に画像ファイルが用意できました。
今回自分の名前は 「fix」と設定しています。

ちなみに画像の中身はこうなってます。

画像からのデータ作成

顔認証を行うための元ネタとなる画像が取得できたので、その画像を使用して、認証を行う時の学習データを作成するための仮想環境を作成し、学習データを作成する手順になります。Raspberry Piでimutilsface_recognitionがインストールできないのでねぇ・・・

1. 必要なパッケージのインストール

ターミナルで以下を順番に実行してください。

sudo apt update
sudo apt install python3-pip python3-dev build-essential cmake
sudo apt install libopenblas-dev liblapack-dev libjpeg-dev libatlas-base-dev libboost-python-dev
sudo apt install libssl-dev libffi-dev

2. 仮想環境の作成・有効化

python3 -m venv venv
source venv/bin/activate

3. Pythonパッケージのインストール

pip install --upgrade pip
pip install imutils opencv-python
pip install dlib
pip install face_recognition

dlibのインストールは時間がかかる場合があります。

4. 仮想環境を使う

使用しているビルダーで、作成した仮想環境のPythonを選択してください。
エラーになったら作成した仮想環境にインポートされていないことがあるので、以下コマンドで追加してください。

pip install imutils
pip install opencv-python

5. 学習データ作成

では、準備ができたので、学習データを作成します。


#! /usr/bin/python

from imutils import paths
import face_recognition
import pickle
import cv2
import os

print("start processing faces...")

imagePaths = list(paths.list_images("dataset"))
print("画像数:", len(imagePaths))

knownEncodings = []
knownNames = []

for (i, imagePath) in enumerate(imagePaths):
    print("[INFO] Image {}/{}".format(i + 1, len(imagePaths)))
    name = imagePath.split(os.path.sep)[-2]
    try:
        image = cv2.imread(imagePath)
        if image is None:
            print(f"画像が読み込めません: {imagePath}")
            continue
        rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        boxes = face_recognition.face_locations(rgb, model="hog")
        encodings = face_recognition.face_encodings(rgb, boxes)
        if not encodings:
            print(f"顔が検出できません: {imagePath}")
        for encoding in encodings:
            knownEncodings.append(encoding)
            knownNames.append(name)
    except Exception as e:
        print(f"エラー: {e} ({imagePath})")

print("serializing encodings...")

data = {"encodings": knownEncodings, "names": knownNames}
with open("encodings.pickle", "wb") as f:
    f.write(pickle.dumps(data))

print("完了。エンコーディング数:", len(knownEncodings))

上記コードを実行すると「encodings.pickle」という学習ファイルが作成されます。ちなみに判定する画像が多い場合、処理に時間がかかる為、ここでコーヒーブレイクしてください。

そして、出来上がった「encodings.pickle」が、顔写真から顔認識するための数値化データを保存したファイルになります。

いよいよ顔認証

学習済みファイルが用意できたので、実際の映像から顔を認識し「○○さんだ!!!」と判別するコードを書いていきます。

#! /usr/bin/python

from imutils.video import VideoStream
from imutils.video import FPS
import face_recognition
import imutils
import pickle
import time
import cv2

currentname = "unknown"

# 個人の環境に合わせてパスを指定してあげてください。
encodingsP = "encodings.pickle"
cascade = "haarcascade_frontalface_default.xml"

data = pickle.loads(open(encodingsP, "rb").read())
detector = cv2.CascadeClassifier(cascade)

vs = VideoStream(src=0).start()
time.sleep(2.0)

while(True):
	frame = vs.read()
	frame = imutils.resize(frame, width=500)
	
	gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
	rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

	rects = detector.detectMultiScale(gray, scaleFactor=1.1, 
		minNeighbors=5, minSize=(30, 30),
		flags=cv2.CASCADE_SCALE_IMAGE)
	boxes = [(y, x + w, y + h, x) for (x, y, w, h) in rects]

	encodings = face_recognition.face_encodings(rgb, boxes)
	names = []

	for encoding in encodings:
		matches = face_recognition.compare_faces(data["encodings"], encoding)
		name = "Unknown"

        #ここから顔情報と取り込んだ情報をマッチングさせます
		if True in matches:
			matchedIdxs = [i for (i, b) in enumerate(matches) if b]
			counts = {}

			for i in matchedIdxs:
				name = data["names"][i]
				counts[name] = counts.get(name, 0) + 1

			name = max(counts, key=counts.get)
			
			if currentname != name:
				currentname = name
		
        # ここで名前をセット
		names.append(name)

    # 名前と枠を表示
	for ((top, right, bottom, left), name) in zip(boxes, names):
		cv2.rectangle(frame, (left, top), (right, bottom),(0, 255, 225), 2)
		y = top - 15 if top - 15 > 15 else top + 15
		cv2.putText(frame, name, (left, y), cv2.FONT_HERSHEY_SIMPLEX,.8, (0, 255, 255), 2)

	cv2.imshow("Running...", frame)
	key = cv2.waitKey(1) & 0xFF

	if key == ord("q"):
		break

# 終了処理
cv2.destroyAllWindows()
vs.stop()

上記コードを実行するとき、haarcascade_frontalface_default.xmlがあるか確認してください。上記コードと同じところに置いておきましょう。

もしない場合以下コマンドでダウンロードできます。

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

実行結果

実行すると上記画像の様に自分の顔を囲んだ枠が表示され名前(今回はfixと設定)も表示されます。うまく認識できない場合は名前が「Unknown」と表示されます。これで「○○さんだ!!」と認識できるコードを作ることができました。

最後に

今回作成したコードで当初の目標の一つ、『誰が帰宅したか判別する』が出来る様になりました。

これで、Raspberry Piが『誰か来た』を認識し、今回のコードで、『あっ、ご主人が帰宅したんだ!!』と認識できる様になったわけです。着実に帰宅時に「おかえり」を言ってもらえる率が上がっています。ワクワクします!!!

あっ!ちなみに、家庭に問題があるわけではなく、自己満足という注釈はつけさせてもらいますね。

次回は、いよいよRaspberry Piに喋らせることを実現しようと思う今日この頃でございます。



ギャップロを運営しているアップフロンティア株式会社では、一緒に働いてくれる仲間を随時、募集しています。 興味がある!一緒に働いてみたい!という方は下記よりご応募お待ちしております。
採用情報をみる