【Python】顔データの保存

## はじめに

顔認識をするときに毎回顔データの処理を行うのがめんどくさいと思ったので、初回起動時にデータを保存することにしました。
顔認識自体のコードは以下の記事を見てください。

https://qiita.com/mikannohako/items/8f3d15f43c1cba1edc96

## 実行環境

- Python 3.11
- Windows 10

## 使用ライブラリ

| ライブラリ名            | バージョン  | 主な機能                 |
|------------------------|------------|--------------------------|
| `cv2` (opencv-python) | 4.10.0.84  | 画像処理ライブラリ       |
| `face_recognition`    | 1.3.0      | 顔認識ライブラリ         |
| `numpy`              | 2.2.1      | 数値計算ライブラリ       |
| `tqdm`              | 4.67.1      | 進捗バー表示             |

以下のコマンドで一括インストールできます。

```:ライブラリのインストール
pip install opencv-python face-recognition numpy tqdm
```

:::note info
バージョン指定は基本必要ないですが、エラーが起きる場合はバージョンを指定してみてください。
:::

## 実装

以下のコードを `.py` ファイルにコピペして実行してください。  
処理の流れは以下の通りです。

1. `face_data.pkl` がある場合はロードし、顔データを利用
2. `face_data.pkl` が無ければ、画像フォルダから顔データを取得し保存
3. 画像処理が完了すると、カメラ映像が表示され顔認識が開始
4. `q` キーを押すと終了

:::note
顔認識する画像を変更した場合は`face_data.pkl`を削除してください。
:::

```python:コード全文
import cv2
import face_recognition
import numpy as np
import os
import pickle  # データ保存用
from tqdm import tqdm  # プログレスバー表示ライブラリ

tolerance = 0.4
data_file = "face_data.pkl"  # 保存するデータファイル名

# 顔データをロードする
def load_face_data(file):
    if os.path.exists(file):
        with open(file, "rb") as f:
            return pickle.load(f)
    return None

# 顔データを保存する
def save_face_data(file, encodings, names):
    with open(file, "wb") as f:
        pickle.dump((encodings, names), f)

# 既存データをロード
face_data = load_face_data(data_file)

if face_data:
    known_face_encodings, known_face_names = face_data
    print("顔データをロードしました。")
else:
    print("顔データが見つからないため、新たに作成します。")
    # 画像フォルダのパス
    image_folder = 'images/'

    # 画像を読み込み、顔の特徴を記録する
    known_face_encodings = []
    known_face_names = []

    # フォルダごとに画像を処理
    folder_list = [folder for folder in os.listdir(image_folder) if os.path.isdir(os.path.join(image_folder, folder))]

    # tqdm を使ってフォルダの処理状況を表示
    for foldername in tqdm(folder_list, desc="Processing Folders"):
        folder_path = os.path.join(image_folder, foldername)
        
        # フォルダ内の画像ファイルを処理
        image_files = [f for f in os.listdir(folder_path) if f.endswith(".jpg")]

        # tqdm を使ってフォルダ内の画像ファイルの処理状況を表示
        for filename in tqdm(image_files, desc=f"Processing Images in {foldername}", leave=False):
            image_path = os.path.join(folder_path, filename)
            image = face_recognition.load_image_file(image_path)
            encoding = face_recognition.face_encodings(image)
            
            if encoding:
                # 複数の画像から同じ人物の特徴を追加
                known_face_encodings.append(encoding[0])
                known_face_names.append(foldername)  # フォルダ名を表示名として取得

    # データを保存
    save_face_data(data_file, known_face_encodings, known_face_names)
    print("顔データを保存しました。")

# カメラを初期化
video_capture = cv2.VideoCapture(0)

process_this_frame = True

while True:
    ret, frame = video_capture.read()

    if process_this_frame:
        # ここで顔認識処理
        small_frame = cv2.resize(frame, (0, 0), fx=0.25, fy=0.25)
        rgb_small_frame = np.ascontiguousarray(small_frame[:, :, ::-1])
        
        # 顔検出と顔認識
        face_locations = face_recognition.face_locations(rgb_small_frame)
        face_encodings = face_recognition.face_encodings(rgb_small_frame, face_locations)
        
        # 顔の座標を拡大して元のフレームに戻す
        face_locations = [(top * 4, right * 4, bottom * 4, left * 4) for (top, right, bottom, left) in face_locations]
    
    # 毎フレーム処理を行わないようにする
    process_this_frame = not process_this_frame
    
    # 各顔に対して認識
    for (top, right, bottom, left), face_encoding in zip(face_locations, face_encodings):
        matches = face_recognition.compare_faces(known_face_encodings, face_encoding, tolerance=tolerance) # 厳しくするならtoleranceを小さく
        name = "Unknown"
        
        # マッチした顔があれば名前を取得
        if True in matches:
            first_match_index = matches.index(True)
            name = known_face_names[first_match_index]
        
        # 顔の周りにボックスを描画
        cv2.rectangle(frame, (left, top), (right, bottom), (0, 0, 255), 2)
        font = cv2.FONT_HERSHEY_DUPLEX
        cv2.putText(frame, name, (left + 6, bottom - 6), font, 0.5, (255, 255, 255), 1)
    
    # フレームを表示
    cv2.imshow('Video', frame)
    
    # 'q'キーが押されたらループを終了
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# カメラを解放し、ウィンドウを閉じる
video_capture.release()
cv2.destroyAllWindows()
```

## おわりに

顔認識プログラムを使うたびに毎回顔データを処理するのは時間がかかるので、  
一度処理した顔データを保存して、次回以降はそのデータを利用できるようにしました。  

これにより処理時間が短縮され、スムーズに顔認識を実行できます。  
参考になれば嬉しいです。