OpenCVのSFaceで顔認証の準備

OpenCVのSFaceで顔認証

https://sites.google.com/iot-com.net/home/ホーム/実験室/jetson-nano/jetson-nanoのopen-cvで顔認証
を参考に
OpenCVにDNNを使用した顔認識SFaceが実装され、誰の顔かを認識できる様になったのとの記事を見つけて試してみました。
記事 : OpenCVの新しい顔認識を試してみる https://qiita.com/UnaNancyOwen/items/8c65a976b0da2a558f06
Github : OpenCV ObjDetect Module Face Recognition (SFace) Sample https://gist.github.com/UnaNancyOwen/49df508ad8b6d9520024354df0e3e740

顔認識は OpenCV 4.5.4 以上からの導入になる

基本的にはGithub のPython をコピペすればそのまま動作する

generate_aligned_faces.py
入力した写真から人の顔の部分を切り出して保存するプログラム
複数の人物が写っている場合は全員を切り出して
face001.jpg , face002.jpg ・・・ と名前を付けて保存する
出力されたファイル名を 人の名前に変更しておくと後々便利です。 
face001.jpg → taro.jpg
というようにリネームする

generate_feature_dictionary.py
切り出した顔のjpgファイルを読み込んで、顔の特徴量に変換するプログラム
顔写真 taro.jpg を入力すると 顔の特徴量 taro.npy が出力される
このnumpyファイルに各個人の顔の特徴量が128次元ベクトルに変換されて入る

face_recognizer.py
入力された写真に上記で作成した顔の特徴量が近い人が写っているかを判別するプログラム
特徴量の npyファイルは同じフォルダに入っているものが全て自動で読み込まれる
表示される名前は特徴量ファイル名となるので人物名をファイル名にした方がわかりやすい
類似した顔が無い場合には Unknown と表示

これらを元に実践する

generate_aligned_faces.py
で写真のファイルを引数にして実行

python generate_aligned_faces.py image.jpg

とすれば
写真に写っている人の分だけファイルができる
そのファイル名を人の名前に変更する

つまり全て
face001.jpg
という感じで
Face00x.jpg
となっているので写真ごとに名前を変える

次に
generate_feature_dictionary.py

切り出した顔のjpgファイルを読み込んで、顔の特徴量に変換するプログラムです。
例えば 顔写真 taro.jpg を入力すると 顔の特徴量 taro.npy が出力されます。
このnumpyファイルに各個人の顔の特徴量が128次元ベクトルに変換されて入ってます。

python generate_feature_dictionary.py face001.jpg
python generate_feature_dictionary.py face002.jpg

写真の人の分だけ実行すればOK
人物名なら

python generate_feature_dictionary.py tarojpg
python generate_feature_dictionary.py jiro.jpg
python generate_feature_dictionary.py hoge.jpg
python generate_feature_dictionary.py hogehoge.jpg

これで
顔の特徴量 taro.npy
というようなnpyファイルが作成される
実際には画像ファイル名.npyファイルになる

実行するにあたり
写真を用意する必要がある
横も認識したいのなら、横の写真も必要になる

とりあえず写真を探すこと
まずは自分の写真を撮影し
GooglePhotoからダウンロード

 cp ~/Downloads/PXL_20240612_091410912.jpg .

でコピー

作業ディレクトリは

/Users/snowpool/aw10s/face_recog

で行う

https://gist.github.com/UnaNancyOwen/49df508ad8b6d9520024354df0e3e740#file-face_recognizer-pyのコードをそのまま使う

Download Zip
でダウンロードし展開
中身を

 cp ~/Downloads/49df508ad8b6d9520024354df0e3e740-54e7dbd2f15b6137dc2b6d4ef6ce3143528c3978/* .

でコピー

ソースだけでなくモデルのダウンロードが必要

https://github.com/ShiqiYu/libfacedetection.train/blob/master/tasks/task1/onnx/yunet.onnx
をクリックしたら

404 - page not found
The 
master
 branch of 
libfacedetection.train
 does not contain the path 

https://drive.google.com/file/d/1ClK9WiB492c5OZFKveF3XiHCejoOxINW/view
については
プレビューできません

となる

とりあえずモデルのダウンロードを調べることにする

https://www.eranger.co.jp/blog/news/face-detection-recognition-by-opencv
の記事を参考に

face_detection_yunet_2023mar.onnx
で検索

https://github.com/opencv/opencv_zoo/blob/main/models/face_detection_yunet/face_detection_yunet_2023mar.onnx
にデータがあったので

https://github.com/opencv/opencv_zoo/tree/main
のreadmeを見てから

git clone https://github.com/opencv/opencv_zoo.git

でリポジトリのclone

cp opencv_zoo/models/face_detection_yunet/face_detection_yunet_2023mar.onnx .

で作業ディレクトリにコピー

python generate_aligned_faces.py PXL_20240612_091410912.jpg 

を実行したが

Traceback (most recent call last):
  File "/Users/snowpool/aw10s/face_recog/generate_aligned_faces.py", line 60, in <module>
    main()
  File "/Users/snowpool/aw10s/face_recog/generate_aligned_faces.py", line 33, in main
    face_detector = cv2.FaceDetectorYN_create(weights, "", (0, 0))
cv2.error: OpenCV(4.10.0) /Users/xperience/GHA-Actions-OpenCV/_work/opencv-python/opencv-python/opencv/modules/dnn/src/onnx/onnx_importer.cpp:277: error: (-5:Bad argument) Can't read ONNX file: /Users/snowpool/aw10s/face_recog/yunet.onnx in function 'ONNXImporter'

となる

python

でPythonインタープリターを使用し

import cv2
print(cv2.__version__)

でバージョンを確認すると
4.10.0
となった

pip show opencv-python

Name: opencv-python
Version: 4.8.0.74
Summary: Wrapper package for OpenCV python bindings.
Home-page: https://github.com/opencv/opencv-python
Author: 
Author-email: 
License: Apache 2.0
Location: /Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages
Requires: numpy, numpy, numpy, numpy, numpy
Required-by: ultralytics

だとバージョンが違う

Pythonインタープリターで
現在のインポートされているOpenCVの場所を確認

import cv2
print(cv2.__file__)

の結果は

/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages/cv2/__init__.py

システムパスの確認

import sys
print(sys.path)

の結果は

['', '/Users/snowpool/.pyenv/versions/3.10.6/lib/python310.zip', '/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10', '/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/lib-dynload', '/Users/snowpool/.pyenv/versions/3.10.6/lib/python3.10/site-packages']

その前によくREADMEを読んで再度実行

rm face_detection_yunet_2023mar.onnx

で一度削除し

cd opencv_zoo

git lfs install

これで
Git LFSが有効化され、大きなファイルを扱う準備が整う

git lfs pull
Git LFSを用いて管理されているファイル(大容量のファイルなど)をダウンロードします。git clone はリポジトリのメタデータと小さなファイルのみをダウンロードするため、git lfs pull を使用してLFSを介して管理されている大きなファイルを取得する必要があります
とのこと

cp opencv_zoo/models/face_detection_yunet/face_detection_yunet_2023mar.onnx .

でファイルをコピー

次に
* face_recognizer_fast.onnx
これもダウンロードできなかったので
githubで検索

https://github.com/MYJLAB-2022-HackThon/FaceServer/blob/27ce7099eb3ec46bb07d988b9681e9cc2a6b291c/app/face_recognizer_fast.onnx
にあったので

git clone https://github.com/MYJLAB-2022-HackThon/FaceServer.git

でリポジトリをclone

cp FaceServer/app/face_recognizer_fast.onnx .

でファイルをコピー

これで再度

 python generate_aligned_faces.py PXL_20240612_091410912.jpg

を実行したら

Traceback (most recent call last):
  File "/Users/snowpool/aw10s/face_recog/generate_aligned_faces.py", line 60, in <module>
    main()
  File "/Users/snowpool/aw10s/face_recog/generate_aligned_faces.py", line 33, in main
    face_detector = cv2.FaceDetectorYN_create(weights, "", (0, 0))
cv2.error: OpenCV(4.10.0) /Users/xperience/GHA-Actions-OpenCV/_work/opencv-python/opencv-python/opencv/modules/dnn/src/onnx/onnx_importer.cpp:277: error: (-5:Bad argument) Can't read ONNX file: /Users/snowpool/aw10s/face_recog/yunet.onnx in function 'ONNXImporter'

となる

多分

    # モデルを読み込む
    weights = os.path.join(directory, "yunet.onnx")
    face_detector = cv2.FaceDetectorYN_create(weights, "", (0, 0))
    weights = os.path.join(directory, "face_recognizer_fast.onnx")

のonnxファイルの指定を変えればいけるはず

generate_aligned_faces.py
の中の

    # weights = os.path.join(directory, "yunet.onnx")

    weights = os.path.join(directory, "face_detection_yunet_2023mar.onnx")

にして実行

とりあえず

import os
import argparse
import numpy as np
import cv2

def main():
    # 引数をパースする
    parser = argparse.ArgumentParser("generate aligned face images from an image")
    parser.add_argument("image", help="input image file path (./image.jpg)")
    args = parser.parse_args()

    # 引数から画像ファイルのパスを取得
    path = args.image
    directory = os.path.dirname(args.image)
    if not directory:
        directory = os.path.dirname(__file__)
        path = os.path.join(directory, args.image)

    # 画像を開く
    image = cv2.imread(path)
    if image is None:
        exit()

    # 画像が3チャンネル以外の場合は3チャンネルに変換する
    channels = 1 if len(image.shape) == 2 else image.shape[2]
    if channels == 1:
        image = cv2.cvtColor(image, cv2.COLOR_GRAY2BGR)
    if channels == 4:
        image = cv2.cvtColor(image, cv2.COLOR_BGRA2BGR)

    # モデルを読み込む
    # weights = os.path.join(directory, "yunet.onnx")
    weights = os.path.join(directory, "face_detection_yunet_2023mar.onnx")
    
    face_detector = cv2.FaceDetectorYN_create(weights, "", (0, 0))
    weights = os.path.join(directory, "face_recognizer_fast.onnx")
    face_recognizer = cv2.FaceRecognizerSF_create(weights, "")

    # 入力サイズを指定する
    height, width, _ = image.shape
    face_detector.setInputSize((width, height))

    # 顔を検出する
    _, faces = face_detector.detect(image)

    # 検出された顔を切り抜く
    aligned_faces = []
    if faces is not None:
        for face in faces:
            aligned_face = face_recognizer.alignCrop(image, face)
            aligned_faces.append(aligned_face)

    # 画像を表示、保存する
    for i, aligned_face in enumerate(aligned_faces):
        cv2.imshow("aligned_face {:03}".format(i + 1), aligned_face)
        cv2.imwrite(os.path.join(directory, "face{:03}.jpg".format(i + 1)), aligned_face)

    cv2.waitKey(0)
    cv2.destroyAllWindows()

if __name__ == '__main__':
    main()

として

サンプル画像をダウンロード後

python generate_aligned_faces.py 136777535-36d6bce1-91bf-446c-9377-645cc60b9c65.jpg

とすると
face001.jpg
face002.jpg
が作成されますが

スマホで撮影した画像で

python generate_aligned_faces.py PXL_20240612_091410912.jpg

とすると処理が終わりません

ファイルサイズなども関連しているかもしれないため一度ファイルサイズなども調べてみます

ファイル情報を調べたいので

 vim file_info.py

import cv2
import os
import argparse

def main():
    # コマンドライン引数を解析するパーサーを作成
    parser = argparse.ArgumentParser(description="Display image properties")
    parser.add_argument("image_path", help="Path to the image file")
    args = parser.parse_args()

    # 画像を読み込む
    image = cv2.imread(args.image_path)
    if image is None:
        print("画像が読み込めませんでした。")
        return

    # 画像の高さ、幅、チャンネル数を取得
    height, width, channels = image.shape
    print(f"画像の幅: {width} ピクセル")
    print(f"画像の高さ: {height} ピクセル")
    print(f"色チャネル数: {channels}")

    # ファイルサイズを取得
    file_size = os.path.getsize(args.image_path)
    print(f"ファイルサイズ: {file_size} バイト")

if __name__ == '__main__':
    main()

で
python file_info.py PXL_20240612_091410912.jpg

結果は

画像の幅: 2736 ピクセル
画像の高さ: 3648 ピクセル
色チャネル数: 3
ファイルサイズ: 2319152 バイト
python file_info.py 136777535-36d6bce1-91bf-446c-9377-645cc60b9c65.jpg

だと

画像の幅: 450 ピクセル
画像の高さ: 312 ピクセル
色チャネル数: 3
ファイルサイズ: 26914 バイト

リサイズして半分にしてから実行したいので

vim resize_save.py

中身は

import cv2
import os
import argparse

def main():
    # コマンドライン引数を解析するパーサーを作成
    parser = argparse.ArgumentParser(description="Resize and save an image")
    parser.add_argument("image_path", help="Path to the image file")
    args = parser.parse_args()

    # 画像を読み込む
    image = cv2.imread(args.image_path)
    if image is None:
        print("画像が読み込めませんでした。")
        return

    # 画像の元の高さ、幅を取得
    height, width = image.shape[:2]

    # 新しい寸法を計算(元のサイズの半分)
    new_width = width // 2
    new_height = height // 2

    # 画像をリサイズ
    resized_image = cv2.resize(image, (new_width, new_height))

    # 新しいファイル名を設定
    new_file_path = os.path.splitext(args.image_path)[0] + "_resized.jpg"

    # リサイズした画像を保存
    cv2.imwrite(new_file_path, resized_image)
    print(f"リサイズされた画像が保存されました: {new_file_path}")

if __name__ == '__main__':
    main()

これを実行し

python resize_save.py PXL_20240612_091410912.jpg    

でファイルサイズを半分にしたが結果は同じのため

python file_info.py PXL_20240612_091410912_resized.jpg

でさらに半分にすることでようやく

Face001.jpg

が作成できた

これにより
Pixcel8で撮影したスマホの写真で顔データを作る場合には
元画像の1/4にする必要があることが判明

ちなみにファイルの比較は

python file_info.py PXL_20240612_091410912_resized_resized.jpg 
画像の幅: 684 ピクセル
画像の高さ: 912 ピクセル
色チャネル数: 3
ファイルサイズ: 228769 バイト
python file_info.py PXL_20240612_091410912.jpg 
画像の幅: 2736 ピクセル
画像の高さ: 3648 ピクセル
色チャネル数: 3
ファイルサイズ: 2319152 バイト

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です