【AI/ML】OpenCVによる顔検出と顔認識(CascadeClassifier, FaceDetectorYN, FaceRecognizerSF)
最終更新日: 2024/03/06 9:21am
こんにちは。小高です。
先日のブログ「【AI】集合写真から社員をさがせ! Amazon Rekognition (search users by image)」では、AWS(Amazon Web Services)の顔検出(Face Detection)、顔認識(Face Recognition)サービス「Amazon Rekognition」を紹介しました。
ブログに書きましたように、非常に少ない手間で精度の高い結果をえることができました。
今回はクラウドを使わずに、pythonモジュールとして提供されているOpenCVをつかって、同じようなことをしてみたいと思います。
OpenCVでは(昔からある)Haar-Like特徴量をつかった顔検出、DNN(AI)を利用した顔検出・顔認識が提供されています。
なお、後者を使う制約からOpenCVは最新4.9を使いました。
Haar-like特徴量を用いたカスケード型分類器による顔検出
昔からある手法です。
Haar-Like 特徴量とカスケード型分類機については、群馬大学のサイトで簡単に説明したものがありますので、そちらを参照してください。
リンク:Haar-like特徴量を用いたカスケード分類器による前方車両の識別
OpenCV(Python)でのサンプル実装については、以下のサイトを参考にせていただきました。
リンク:【Python】OpenCVによる顔認識:画像から顔を検出する方法
pythonコードとしては、xmlで定義された特徴量(haarcascade_frontalface_default.xml)をつかってカスケード型分類機を作り、グレースケール化したイメージを分類機に送って顔を検出します。
# カスケード型分類器の生成
_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml"))
# 顔を検出する
_ret = _cascade.detectMultiScale(grey-scaled-image.jpg, 1.1, 3, 0, minSize)
以下が結果です。
図1:Haar-Like特徴量による顔検出(1人)
図2:Haar-Like特徴量による顔検出(たくさん)
結構、ちゃんと顔の位置を検出できているとおもませんか?
次は「いい線いっているのだけど。。」という結果です。
図3:Haar-Like特徴量による顔検出(たくさん)
先ほど、pythonコードでカスケード型分類機を作成するとき、haarcascade_frontalface_default.xml というxmlを使いました。これに「frontalface」と書いてあります。「正面画像の特徴量」だという意味です。
他にどんなのがあるかと興味が湧きますよね。下のopencvのgithubで見ることができますが、full body、lower body, upper bodyの他にcat faceやsmileなんていうのもあります。
リンク:OpenCV: Open Source Computer Vision Library
最終更新日が何年も前ですので、最近はあまり流行っていないのがわかりますね。
AWS Rekognitionでは検出できた下の「横顔画像」については、(上の特徴量では)顔を検出することはできませんでした。
図4:Haar-Like特徴量による顔検出(検出できず)
AI(DNN)モデル(FaceDetectorYN)による顔検出
ここであらためて、以下のように言葉を定義しておきます。
– 顔検出(Face Detection):画像から顔を発見する。顔の特徴点(目・鼻・口・耳など)を発見する。
– 顔認識(Face Recognition):他の画像と比較して、その人を特定する。
OpenCVには、AIにもとづく顔検出クラスとして、FaceDetectorYNが用意されています。
YNはYuNetという顔検出モデル(AI)を指しています。
YuNet:opencv_zoo/yunet
FaceDetectorYNの使い方については、以下のサイトが大変参考になりました。ありがとうございました。
注意点としては、上の記事後にAIモデルが更新されているので、YuNetのサイトから face_detection_yunet_2023mar.onnx をダウンロードして使ってください。
このモデルをpythonの実行ディレクトリにおいたら、以下のような感じとなります。
# FaceDetectorYNの生成
face_detector = cv.FaceDetectorYN_create("face_detection_yunet_2023mar.onnx", "", (320, 320), 0.6, 0.3, 5000, cv.dnn.DNN_BACKEND_DEFAULT, target_id=cv.dnn.DNN_TARGET_CPU)
# 画像の読み込み
image = cv.imread("image.jpg")
# 画像サイズを設定する
face_detector.setInputSize((image.shape[1], image.shape[0]))
# 顔検出
_, faces = face_detector.detect(image)
バウンティングボックスに加えて、顔の特徴点(両目、鼻の頭、口の端)を描いてみました。
図5:FaceDetectorYNによる顔と特徴点の検出
Haar-Like特徴量+カスケード分類機で誤検出のあった画像もうまく検出てきています。
図6:FaceDetectorYNによる顔との検出(複数人)
Haar-Like特徴量+カスケード分類機では検出できなかった、横顔についても検出できています。
図7:FaceDetectorYNによる顔との検出(横顔)
YuNetのページ(opencv_zoo/yunet)には「light-weight, fast, accurate」と書いてあります。
ためしに、録画済みの動画の顔検出をしましたので掲載しておきます。
AI(DNN)モデル(FaceRecognizerSF)による顔認識
OpenCVには、AIにもとづく顔認識クラスとして、FaceRecognizerSF が用意されています。
SFは「SFace」という顔認識のための損失関数の名前のようです。
SFace:opencv_zoo/SFace
ArXive: SFace: Sigmoid-Constrained Hypersphere Loss for Robust Face Recognition
FaceDetectorYNについても(先と同じ著者の)以下のサイトが大変参考になりました。
流れはAmazon Rekognitionでユーザー検索をしたときに似ています。
Rekognitionでは、ユーザー(認識したい人)の顔の特徴量を「コレクション」というデータベースに登録しましたが、FaceDetectorYNはこれを「辞書(ディクショナリー)」と呼んでいます。
つまり、
1. ユーザーの特徴量を作成して辞書に登録しておく。
2. 画像から顔を検出して特徴量を計算し、辞書内の一番近い特徴量を探し出す。
3. 上(2)の結果から、ユーザー名を特定する。
という流れになります。
1と2で特徴量を計算するときには、YuNetで顔を切り出して精度を上げます。
辞書の作成は以下のような流れです。imageは登録用画像、userは登録する人の名前と思ってください。
# 顔を切り出すためのFaceDetectorYNの生成
face_detector = cv.FaceDetectorYN_create("face_detection_yunet_2023mar.onnx", "", (320, 320), 0.6, 0.3, 5000, cv.dnn.DNN_BACKEND_DEFAULT, target_id=cv.dnn.DNN_TARGET_CPU)
# 画像サイズの取得
height, width, _ = image.shape
face_detector.setInputSize((width, height))
# 顔を検出する
_, faces = face_detector.detect(image)
if len(faces) > 0:
# FaceRecognizerSF の生成
face_recognizer = cv.FaceRecognizerSF_create("face_recognizer_fast.onnx", "")
# 顔を切り抜き特徴を抽出する
aligned_face = face_recognizer.alignCrop(image, faces[0])
# 特徴量の抽出
face_feature = face_recognizer.feature(aligned_face)
# 特徴量ベクトルを user.npy に保管する
np.save(user, face_feature)
# https://docs.opencv.org/4.x/d0/dd4/tutorial_dnn_face.html
COSINE_THRESHOLD = 0.363
NORML2_THRESHOLD = 1.128
def match(recognizer, feature1, dictionary):
for element in dictionary:
user_id, feature2 = element
# FaceRecognizerSF でマッチする
score = recognizer.match(feature1, feature2, cv.FaceRecognizerSF_FR_COSINE)
if score > COSINE_THRESHOLD:
return True, (user_id, score)
return False, ('unknown', 0.0)
if __name__ == "__main__":
.....
# 画像からの顔の検出
result, faces = face_detector.detect(image)
faces = faces if faces is not None else []
_idx=0
_l=list()
for face in faces:
# 顔を切り抜き特徴を抽出する
aligned_face=face_recognizer.alignCrop(image, face)
_idx+=1
feature=face_recognizer.feature(aligned_face)
# 辞書とマッチングする
result, user=match(face_recognizer, feature, dictionary)
←「【ソラカメ】小型監視カメラAtom Cam2とAtom Cam Swingの違いとポイント」前の記事へ 次の記事へ「【お知らせ】イー・レンジャーかわら版(2024年4月号: NVIDIAの株価はなぜ高い )を発行しました。」→