東京都府中市、九段下のWEB制作会社Maromaroのブログです

2025.02.10

Sasaki

Raspberry Piで顔認証

こんにちは!Maromaro 佐々木です。

今回、扉を顔認証で開くようにしたいと思い、Raspberry Piを利用して顔認証を行なってみました。

事前準備

Raspberry Pi

Node-REDのプラグイン

  • node-red-contrib-usbcameraのインストール
  • node-red-node-base64のインストール
  • node-red-dashboardのインストール
  • node-red-contrib-image-outputのインストール

まずカメラ起動ができるか確認

下記のようにしてnode-redでUSBカメラの映像がとれるか確認


templateの設定

<h1>カメラ映像</h1>
<div>
<img width="800" height="600" src="data:image/png;base64,{{msg.payload}}">
</div>

このようにしておくことで映像が流れてくればOK
※ダッシュボードを設定して表示できると思います。

認証に利用するpythonスクリプトを用意

事前に下記で各種インストール

#事前に
sudo apt update && sudo apt upgrade

#dlibがすごい時間かかります
sudo apt-get install python3-opencv
pip install dlib git opencv-python opencv-python-headless opencv-contrib-python --break-system-packages

#顔認証ライブラリ関連
#https://pypi.org/project/face-recognition/#files
#から事前にwhlファイルをDLしておく
pip install face_recognition-1.3.0-py2.py3-none-any.whl --break-system-packages
pip install git+https://github.com/ageitgey/face_recognition_models --break-system-packages

コード

import cv2
import sys
import os
import subprocess
import face_recognition
import numpy as np

# カメラプロセスを確認して強制終了する関数
def kill_camera_process(device="/dev/video0"):
    """
    カメラデバイスを使用中のプロセスを強制終了する。
    """
    try:
        # `lsof`コマンドで指定デバイスを使用しているプロセスを取得
        result = subprocess.run(['lsof', device], capture_output=True, text=True)
        if result.stdout:
            lines = result.stdout.strip().split('\n')[1:]  # ヘッダ行を除外
            for line in lines:
                pid = int(line.split()[1])  # PIDを取得
                print(f"プロセス {pid} を強制終了します")
                os.kill(pid, 9)  # 強制終了
    except Exception as e:
        print(f"プロセス終了時にエラーが発生しました: {e}")

# 認証対象の顔画像を読み込む関数
def load_known_faces(known_faces_dir):
    """
    `known_faces_dir`フォルダ内の名前ディレクトリから顔画像を読み込み、エンコードを生成する。

    Parameters:
    - known_faces_dir: 既知の顔画像が格納されているルートフォルダ

    Returns:
    - known_face_encodings: 既知の顔エンコードリスト
    - known_face_names: 既知の名前リスト
    """
    known_face_encodings = []
    known_face_names = []

    # フォルダ内の各人物ディレクトリを探索
    for person_name in os.listdir(known_faces_dir):
        person_dir = os.path.join(known_faces_dir, person_name)
        if not os.path.isdir(person_dir):
            continue  # ディレクトリでなければスキップ

        # 各人物ディレクトリ内の顔画像を読み込む
        for filename in os.listdir(person_dir):
            if filename.endswith(".jpg") or filename.endswith(".png"):
                image_path = os.path.join(person_dir, filename)
                image = face_recognition.load_image_file(image_path)

                # 画像から顔エンコードを取得
                encodings = face_recognition.face_encodings(image)
                if not encodings:
                    print(f"警告: {image_path} で顔が検出されませんでした")
                    continue

                # 取得したエンコードと名前をリストに追加
                known_face_encodings.append(encodings[0])
                known_face_names.append(person_name)

    return known_face_encodings, known_face_names

# 顔認証のセットアップ
known_faces_dir = "known_faces"  # 既知の顔画像を保存するフォルダ
recognized_faces_dir = "recognized_faces"  # 認証成功時に保存するフォルダ
os.makedirs(recognized_faces_dir, exist_ok=True)  # 保存先フォルダを作成(存在しない場合)

# 既知の顔を読み込む
known_face_encodings, known_face_names = load_known_faces(known_faces_dir)
print(f"既知の顔数: {len(known_face_encodings)}")

# カメラを開く
cap = cv2.VideoCapture(0)
if not cap.isOpened():
    print("カメラが開けませんでした")
    kill_camera_process("/dev/video0")  # プロセスを強制終了
    sys.exit(1)

print("カメラが正常に認識されました")

# カメラ映像をリアルタイムで処理する
while True:
    # カメラから1フレームを取得
    ret, frame = cap.read()
    if not ret:
        print("カメラから画像を取得できませんでした")
        break

    # 取得したフレームをRGBに変換
    rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

    # フレームから顔の位置を検出
    face_locations = face_recognition.face_locations(rgb_frame)

    # 検出された顔のエンコードを取得
    face_encodings = face_recognition.face_encodings(rgb_frame, face_locations)

    # 検出された各顔について認証を行う
    for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
        # 既知の顔と現在の顔を比較
        matches = face_recognition.compare_faces(known_face_encodings, face_encoding, tolerance=0.6)

        # 既知の顔との距離(類似度)を取得
        face_distances = face_recognition.face_distance(known_face_encodings, face_encoding)

        if face_distances.size > 0:
            # 最も距離が近い顔を取得
            best_match_index = np.argmin(face_distances)
            if matches[best_match_index]:
                # 認証成功の場合
                name = known_face_names[best_match_index]
                print(f"認証成功: {name}")

                # 認識された顔に枠と名前を描画
                cv2.rectangle(frame, (left, top), (right, bottom), (0, 255, 0), 2)
                cv2.putText(frame, name, (left + 6, bottom - 6), cv2.FONT_HERSHEY_DUPLEX, 0.5, (255, 255, 255), 1)

                # 認証成功したフレームを保存
                save_path = os.path.join(recognized_faces_dir, f"{name}_recognized.jpg")
                cv2.imwrite(save_path, frame)
                cv2.imwrite("face.jpg", frame)

            else:
                # 認証失敗(未知の顔)
                print("未知の顔が検出されました")
                cv2.rectangle(frame, (left, top), (right, bottom), (0, 0, 255), 2)
cv2.putText(frame, "Unknown", (left + 6, bottom - 6), cv2.FONT_HERSHEY_DUPLEX, 0.5, (255, 255, 255), 1) else: print("検出できませんでした。") # 'q'キーが押されたらループを終了 if cv2.waitKey(1) & 0xFF == ord('q'): print("終了キーが押されました") break # カメラを解放してウィンドウを閉じる cap.release() cv2.destroyAllWindows()

face_detection.pyなどとして保存
※ちゃんと利用するとなると、関数などは別モジュールなどにすると良いかと思います。

使い方

known_facesフォルダを作成し、その中に名前フォルダを作成する。
さらに名前フォルダの中に対象となる顔画像をいれることで動作します。

known_faces/名前/1.jpg

上記のようにします。

顔画像の準備ができたら下記でコマンド実行

python face_detection.py

実行結果

known_facesフォルダにいれた顔が認識され、認証成功となれば正しく動作しています。

「未知の顔が検出されました」と出たらうまく認証ができない画像となります。
※もしも顔画像に周りの風景などが入っていたら、顔部分だけトリミングなどすると精度あがります。

・・・うまくいきましたでしょうか?

Node-redの設定を添付のようにして、認証したら画像を表示するようにもできますね。

翌朝、寝癖がひどい私がしっかりと撮れました

このままだと顔じゃないものも認識されてしまったりするので・・・。
distanceなどをもとに閾値を設けたほうが実用的かもです。

私はこのコードをもとに、秘密の呪文と組み合わせて・・・・顔認証+音声入力で扉が開くような仕組みを作りました!

環境によってはまりポイントがたくさんあるかもしれませんので、その点注意しながら頑張ってみてください!

 

以上、佐々木でした。