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/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で検索
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 バイト