385.顔検出、顔認識(2)
初回:2024/10/09
OpenCV を使用した、顔検出と顔認識について何回かに分けて述べてみたいと思います。
P子「前回は、cv2.CascadeClassifier を使用した顔検出だったわね」※1
『Haar-like特徴量を用いたカスケード型分類器による顔検出』と言われるもので、割と古い方式になります。今回は、新しい顔検出の方法について、述べたいと思います。
 ≪参考資料1≫
   https://qiita.com/UnaNancyOwen/items/f3db189760037ec680f3
   OpenCVの新しい顔検出を試してみる
   最終更新日 2023年06月27日 投稿日 2021年12月03日
   @UnaNancyOwen(Tsukasa Sugiura)
P子「最初から、新しい方式を解説すればよかったんじゃないの?」
私も顔検出を初めてラズパイで行ったのは、前回ご紹介した方式でした。この新しい方式では動かしたことがなかったので、実機で確認する必要がありました。
P子「要するに、時間稼ぎしたかったのね」
1.顔検出
OpenCV は前回インストール済みなので、必要なのは新しい方式(FaceDetectorYN)で使用するonnxファイルの準備です。ファイルは、YuNetのサイトから取得してきます。
 ≪参考資料2≫
   YuNet:opencv_zoo/yunet
   https://github.com/opencv/opencv_zoo/tree/main/models/face_detection_yunet
   face_detection_yunet_2023mar.onnx
いきなりですが、ソースコードを示します。
import time
import cv2
GREEN = (0, 255, 0)                 # BGR形式
if __name__ == '__main__':
    face_detector = cv2.FaceDetectorYN.create(FaceFilter.ONNX_PATH, "", (320, 320), 0.6, 0.3)
    cap = cv2.VideoCapture(0)
    try:
        while True:
            ret, frame = cap.read()
            if ret:
                face_detector.setInputSize((frame.shape[1], frame.shape[0]))
                _, faces = face_detector.detect(frame)						# ① 顔検索
                if faces is not None:                                       # ② 未検出時は、None になる
#               #   検出値はfloatなので int に変換する必要がある。
#                   for (x, y, w, h, *_) in faces:
#                        cv2.rectangle(frame, (int(x), int(y)), (int(x + w), int(y + h)), GREEN, 2, cv2.LINE_AA)
                    for face in faces:
                        (x, y, w, h, *_) = map(int, face)                   # ③ 検出値はfloat
                        cv2.rectangle(frame, (x, y), (x + w, y + h), GREEN, 2, cv2.LINE_AA)
                cv2.imshow('Face Detect Frame', frame)
            k = cv2.waitKey(10)                 # ミリ秒単位で表されるキーボード入力待ち時間
            if k == ord('q') or k == 27:        # q または、ESC で終了
                break
            time.sleep(0.1)                     # CPU処理の軽減化
    except KeyboardInterrupt:                   # Ctl+Cが押されたらループを終了
        print("\nCtl+C Stop")
    except Exception as ex:
        print(ex)                               # 例外処理の内容をコンソールに表示
        import traceback
        traceback.print_exc()                   # Exception のトレース
    finally:
        if cap is not None:
            cap.release()
        cv2.destroyAllWindows()
        print("終了")
前回のソースと見比べられるように、同じ形式で書いています。
 ① 顔検索
   retval, faces のタプルが返ってきます。retval の使い道が判りませんが、faces のみで判断していきます。
  ② 未検出時は、None になる
   この値は、未検出時はNone になります。長さゼロの配列ではないので注意しましょう。
  ③ 検出値はfloat
   戻り値は、検出結果の配列ですが、個々の中身は少し複雑です。
  faces
   0-1: x, y of bbox top left corner
   2-3: width, height of bbox
   4-5: x, y of right eye (blue point in the example image)
   6-7: x, y of left eye (red point in the example image)
   8-9: x, y of nose tip (green point in the example image)
   10-11: x, y of right corner of mouth (pink point in the example image)
   12-13: x, y of left corner of mouth (yellow point in the example image)
   14: face score
 0-1、2-3 は、x, y, w, h ですが、float値なので、int に変換する必要があります。cv2.rectangle にパラメータ指定する時に、int にキャストする方法と、map で一括変換する方法を書いておきます。0-1、2-3 以降を、*_ 受け取っていますが、face をスライスで切り取っても構いません。
  4-13 は、各ポイントの位置を示していますが、このサンプルでは未使用です。
  14は、顔検出時のスコアを表しています。
こちらの方が新しいのと、検出が早く、精度が高いと言われていますが、ラズパイレベルでは検出速度にあまり差は出ませんでした。ただ、検出精度に関しては、今回の新しい方式の方がよさそうです。
2.まとめ
とりあえず、カメラ映像からの顔検出が出来ました。前回同様、本当にお手軽ですね。
顔が検出出来れば、それ以降、色々と応用ができます。顔部分だけ切り抜く、モザイクをかける、別の顔に入れ替えるなどです。また、ある程度のリアルタイム処理もできそうなので、楽しみが増えます。
次回は、本命の顔認識に取り組んでみたいと思います。
P子「時間稼ぎは不要なの?」
場合によっては、別の話題で時間稼ぎするかもしれませんが、そこはご愛嬌という事で。
ほな、さいなら
======= <<注釈>>=======
※1 P子「前回は、cv2.CascadeClassifier を使用した顔検出だったわね」
  P子とは、私があこがれているツンデレPythonの仮想女性の心の声です。