Python – 顔認識ライブラリ Face Recognition で顔認証を行う方法

目次

概要

dlib を使った Python の顔認識ライブラリ Face Recognition を使って、顔認証を行う方法について紹介します。 顔認証は、予め保存されている個人の顔のデータと認証中の顔の画像とを照合し、その人物が誰であるかを識別する技術です。 スマートフォンのロック解除や入退室管理など、セキュリティが求められる分野で広く使われています。

環境

コード全体は pystyle/perform-face-recognition-with-python にあります。

このコードは、以下の環境で実行しました。

Ubuntu 18.04

  • ライブラリ
    • Face Recognition: 1.2.3
  • GPU の実行環境
    • GPU: GeForce GTX 1080
    • CUDA: 9.0
    • CuDNN: 7
  • CPU の実行環境
    • CPU: Intel(R) Core(TM) i7-6700K CPU @ 4.00GHz
    • メモリ: 16G

Windows 10

  • ライブラリ
    • Face Recognition: 1.2.3
  • CPU の実行環境
    • CPU: Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz
    • メモリ: 16G
検出に CNN を使う場合、CUDA 10.1/cudnn7 環境では、`CUDA Runtime API initialization failed.` というエラーが発生して動きませんでした。 CUDA 9/cudnn7 環境では動作したので、動作しない場合は CUDA のバージョンを確認してみてください。

インストール

Ubuntu 18.04 の場合

インストール時に Face Recognition が利用している dlib のビルドが必要になります。 インストール前に apt で GCC 及び CMake をインストールし、C++ のビルド環境を整えます。

apt install -y build-essential cmake

準備ができたら、pip で Face Recognition をインストールします。

pip install face_recognition

Windows 10 の場合

インストール前に Visual Studio 2019 CommunityCMake をインストールし、C++ のビルド環境を整えます。

準備ができたら、pip で Face Recognition をインストールします。

pip install face_recognition

顔認証の手順

以下の3人が予め保存されている人物の顔の画像とします。

known-face_01.jpg

known-face_02.jpg

known-face_03.jpg

以下を認証する人物の顔の画像とします。

face_to_check.jpg

顔画像を読み込む

load_image_file() で顔の画像を読み込みます。

In [1]:
import face_recognition
import matplotlib.pyplot as plt

# 保存されている人物の顔の画像を読み込む。
known_face_imgs = []
for path in ["known-face_01.jpg", "known-face_02.jpg", "known-face_03.jpg"]:
    img = face_recognition.load_image_file(path)
    known_face_imgs.append(img)

# 認証する人物の顔の画像を読み込む。
face_img_to_check = face_recognition.load_image_file("face_to_check.jpg")

顔の領域を検出する

face_locations() で読み込んだ顔の画像から、顔の領域を検出します。 返り値は顔の領域を表す (top, right, bottom, left) の tuple の list になっています。

model は検出に使用するモデルを指定します。 “hog” (デフォルト) を指定した場合、HOG 特徴量 ベースのモデルになります。 “cnn” を指定した場合、CNN ベースのモデルになります。

HOG CNN
計算量 少ない 多い
精度

CNN のほうが高精度ですが、計算量が多く、CPU で実行した場合は時間がかかってしまいます。GPU が使える PC ではこちらを選択するとよいでしょう。

GPU が利用可能な環境でないと、`model=”cnn”` はエラーとなる可能性があります。 その場合、`model=”hog”` に変更してください。
In [2]:
# 顔の画像から顔の領域を検出する。
known_face_locs = []
for img in known_face_imgs:
    loc = face_recognition.face_locations(img, model="cnn")
    assert len(loc) == 1, "画像から顔の検出に失敗したか、2人以上の顔が検出されました"
    known_face_locs.append(loc)

face_loc_to_check = face_recognition.face_locations(face_img_to_check, model="cnn")
assert len(face_loc_to_check) == 1, "画像から顔の検出に失敗したか、2人以上の顔が検出されました"

結果を確認するために、matplotlib で顔の領域を画像上に描画します。

In [3]:
def draw_face_locations(img, locations):
    fig, ax = plt.subplots()
    ax.imshow(img)
    ax.set_axis_off()
    for i, (top, right, bottom, left) in enumerate(locations):
        # 長方形を描画する。
        w, h = right - left, bottom - top
        ax.add_patch(plt.Rectangle((left, top), w, h, ec="r", lw=2, fill=None))
    plt.show()


for img, loc in zip(known_face_imgs, known_face_locs):
    draw_face_locations(img, loc)
    
draw_face_locations(face_img_to_check, face_loc_to_check)

画像から顔の領域が正しく検出できていることがわかりました。

顔の領域から特徴量を抽出する

face_encodings で顔の画像から識別に有用な特徴量を抽出します。

face_encodings(face_image, known_face_locations=None)
  • 引数
    • face_image: 画像
    • known_face_locations: face_locations() の返り値 locations を渡します。

返り値は、顔の特徴量を表す1次元の numpy 配列の list となっています。

In [4]:
# 顔の領域から特徴量を抽出する。
known_face_encodings = []
for img, loc in zip(known_face_imgs, known_face_locs):
    (encoding,) = face_recognition.face_encodings(img, loc)
    known_face_encodings.append(encoding)

(face_encoding_to_check,) = face_recognition.face_encodings(
    face_img_to_check, face_loc_to_check
)

マッチする人物がいるかどうかを調べる

登録されている人物の顔の特徴量 known_face_encodings と認証する人物の顔の特徴量 face_encoding_to_checkcompare_faces に渡すことで、登録されている人物の中にマッチする人物がいるかどうかを調べられます。 tolerance はマッチするかどうかを判定する閾値で、この値を低くするほど判定が厳しくなり、高くするほど判定が緩くなります。

In [5]:
matches = face_recognition.compare_faces(known_face_encodings, face_encoding_to_check)
print(matches)  # [True, False, False]
[True, False, False]

認証する人物は登録されている1人目の人物とマッチしていると判定されました。 登録されている1人目の人物はオバマ大統領なので、正解しています。

特徴量同士の距離を計算する

内部的には、似ているかどうかの判定は特徴量同士の距離で判定しています。 距離が近いほど似ていて、遠いほど似ていないといえます。 この距離は face_distance で計算できます。

In [6]:
dists = face_recognition.face_distance(known_face_encodings, face_encoding_to_check)
print(dists)
[0.32466209 0.8858496  0.88910518]

認証する人物は一人目のオバマ大統領との距離が最も近い結果となりました。

コメント

コメント一覧 (0件)

  • プログラムコードを参考にさせていただきました。
    一つ、質問なのですが、認証する画像と比べる画像の一部を別の画像に変えた時に
    loc = face_recognition.face_locations(img, model=”cnn”)、
    face_loc_to_check = face_recognition.face_locations(face_img_to_check, model=”cnn”)
    二つのコードにエラーが出たのですが、このコードは具体的にはどのような内容のこーどなどでしょうか?
    また、何かアドバイスがあればアドバイスももらえたらうれしいです。
    回答よろしくお願いします。

    • コメントありがとうございます。
      もしよろしければ、使用している OS とエラー内容を教えてください。
      face_recognition.face_locations(img, model=”cnn”) は画像から顔の領域を検出するための処理です。
      CNN を使用する場合、GPU が搭載されていて、適切なバージョンの CUDA/CuDNN がお使いの PC にインストールされていないとエラーになるかもしれません。
      当方では Ubuntu、GeForce GTX 1080、CUDA 10.1/cudnn7 で動作確認しております。Windows や Mac の場合は試したことがないので、動くかどうかはわからないです。

      CNN でなく HOG 特徴量ベースの検出であれば CPU でも動くので、model=”cnn” となっている箇所を model=”hog” に置き換えると動作するかもしれません。

      変更方法
      face_recognition.face_locations(img, model=”cnn”)

      face_recognition.face_locations(img, model=”hog”)

      • 返信ありがとうございます。
        使用しているものがPCではなく、raspberry pi4でOSがRaspbian10です。
        コードをそのままコピーしたら動くのでGPUは搭載されていると思います。
        また、pystyleさんの言う通りに変更したら、別のエラーが出ました。
        /前の変更箇所 ここから/
        #保存されている人物の顔の画像を読み込む
        for path in [“test1.jpg(変更箇所)”, “known-face_02.jpg”, “known-face_03.jpg”]:

        #認証する人物の顔の画像を読み込む
        face_img_to_check = face_recognition.load_image_file(“test.jpg(変更箇所)”)
        /前の変更箇所 ここまで/
        で画像を変えて、”cnn”を”hog”に変更したら、
        /今回の変更箇所/
        (encoding,) = face_recognition.face_encodings(img, loc)にエラーが出ました。
        再度質問してしまい、失礼します。ご回答よろしくお願いします。

        • 確認に使用したコード及び画像は以下になります。
          Ubuntu18.04 (GPU環境) と Windows 10 (CPU環境) で確認しました。
          https://github.com/nekobean/pystyle/tree/master/perform-face-recognition-with-python

          認証対象の画像を変更されたとのことなので、まず `face_recognition.face_locations()` による顔の検出自体は成功しているかどうかを確認してみてください。

          > 使用しているものがPCではなく、raspberry pi4でOSがRaspbian10です。
          > (encoding,) = face_recognition.face_encodings(img, loc)にエラーが出ました。

          エラー内容をスタックトレースも含めて全部記載していただけますでしょうか。

          手元には Windows と Ubuntu マシンしかないので、ラズパイ固有の環境の問題であれば、すみませんが解決方法はこちらではわからないです。
          face_encodings() も CNN を使う処理だと思うので、顔の検出は問題なく、face_encodings() でエラーになるのだとすると、環境の問題の可能性が高いです。
          「raspberry pi face_recognition」などのキーワードで検索すると、情報が得られるかもしれません。

          • さらに返信ありがとうございます。
            pystyleさんのアドバイスや他のサイト、友達たちと話し合ったりしていたら、無事解決しました。質問をしていたのにも関わらず、一方的に終わってしまいすみません。
            最後の軽く聞きたいことがあるのですが、マッチングに使う画像ですが、画像サイズは同じサイズだったり、比較的近いサイズでないとダメなのでしょうか?
            この前のエラーもそれが原因だと個人的に思っています。
            何度も何度もすみませんが、回答よろしくお願いします。

            • > 画像サイズは同じサイズだったり、比較的近いサイズでないとダメなのでしょうか?

              同じサイズでなくてもよいですが、基本的にはおおよそ同じ大きさの画像一杯に認証対象の顔が大きく写っているという状況が望ましいです。
              画像に対して写っている顔が小さいと上手くいかないかもしれません。

          • 返信ありがとうございます。何度も何度もご質問本当にすみません。
            自分たちはこのコードを基に登校管理システムを作成しているのですが、
            顔認証で取得した画像とフォルダ内の複数画像を見比べて、登校管理をするようなシステムを目指しています。
            ———————————————————————————————-
            import cv2
            import face_recognition
            import matplotlib.pyplot as plt
            import numpy as np
            import glob
            from PIL import Image
            #ファイルから拡張子が ”img” のものを取得する
            files = glob.glob(“*.jpg”)
            —–フォルダ内の画像をループ文で読み込む—–
            #保存されている人物の顔の画像を読み込む
            known_face_imgs = []
            while True:
            for path in files:
            Image = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
            img = face_recognition.load_image_file(path)
            known_face_imgs.append(img)
            break
            —–フォルダ内の画像をループ文で読み込む—–
            #認証する人物の顔の画像を読み込む
            face_img_to_check = face_recognition.load_image_file(“img1.jpg”)
            #顔の画像から顔の領域を検出する
            known_face_locs = []
            for img in known_face_imgs:
            loc = face_recognition.face_locations(img, model=”hog”)
            known_face_locs.append(loc)

            face_loc_to_check = face_recognition.face_locations(face_img_to_check, model=”hog”)
            #顔の領域から特徴量を抽出する
            known_face_encodings = []
            for img, loc in zip(known_face_imgs, known_face_locs):
            (encoding,) = face_recognition.face_encodings(img, loc)
            known_face_encodings.append(encoding)

            (face_encoding_to_check,) = face_recognition.face_encodings(
            face_img_to_check, face_loc_to_check
            )
            matches = face_recognition.compare_faces(known_face_encodings, face_encoding_to_check)
            print(matches) # [True, False, False]
            dists = face_recognition.face_distance(known_face_encodings, face_encoding_to_check)
            print(dists)
            ———————————————————————————————-
            のように、コードを少し変更し、実行してみたのですが、
            (encoding,) = face_recognition.face_encodings(img, loc)
            というコードにエラーが発生してしまいました。この部分は具体的にはどのような処理なのでしょうか。また、何かアドバイスがございましたら、お教えください。何度も何度もすみません。お時間があれば返答よろしくお願いします。

          • 2020年11月9日 10:28に質問したのですが、おそらく画像から特徴量を取得することができずにエラーが出ていたと思います。問題の画像を消したら無事成功しました。質問しておきながら、本当にすみません。
            (encoding,) = face_recognition.face_encodings(img, loc)
            は特徴量をうまく取得することができなかった場合にエラーを出すのでしょうか?

            • > 特徴量をうまく取得することができなかった場合にエラーを出すのでしょうか?

              どのようなエラーが出ましたか?
              ある特定の画像でだけエラーが起こるということは、その画像で顔の検出に失敗している可能性があります。
              顔認証の順序としては以下の2段階になっているため、1が上手く行かなかった場合は2でエラーになります。
              1. loc = face_recognition.face_locations(img) で画像から顔の位置を検出する。
              2. face_recognition.face_encodings(img, loc) で顔の特徴を計算する。

              記事のコードは1枚の画像に顔が1つだけ写っているということを前提としている (画像に顔が写っていない、または顔が2つ以上写っているのはNG) ため、face_recognition.face_locations() の返り値は [(x, y, w, h)] という要素が1つのリストになっている必要があります。特定の画像だけ上手くいかないということはこの段階で失敗している可能性があります。
              上手くいかない画像で face_recognition.face_locations() の返り値を print() して要素が [(x, y, w, h)] となっているかを確認してみてください。

              • 返信ありがとうございます。いろいろ質問しておりましたが、pysytleさんのおかげでエラーもなくなりうまくいっています。ありがとうございます。
                おそらく最後になるのですが、特徴量を計算して、その結果を0.〇〇〇〇〇で表示すると思うのですが、この結果を利用して、
                –ここには簡単に書きますが–
                if dists < 0.3:
                print('OK')
                –ここまで–
                のようなif文を書きたいのですが、"dists"は配列で値が入ってるのですか?
                また、どのようにすればif文がうまく動くと思われますか?

              • 度々申し訳ありません。2020年11月16日に再度質問をしたのですが、
                for i in dists:
                if i < 0.3:
                print( 'OK')
                とコードを書いたところうまくいきました。何度も質問をしたのにまたもや一方的に終わってしまい、本当にすみません。
                それでもpystyleさんのおかげで自分たちが作りたいものに近づくことができました。ありがとうございました。
                また何か分からないことがあれば、質問させても大丈夫でしょうか?もし可能であれば、その時にお力を貸していただければ嬉しいです。

              • 無事解決したようでよかったです。
                またなにか記事で不明な点がありましたら、お聞きください。

  • プログラムを参考にしようと思い、丸ごとコピーして実行したところ
    RuntimeError: Unable to open C:\Users\(自分の名前)\AppData\Local\Programs\Python\Python38\lib\site-packages\face_recognition_models\models\shape_predictor_68_face_landmarks.dat
    とエラーが発生しました。
    当初はshape_predictor_68_face_landmarks.datが上記のディレクトリ内にないのかと思い調べましたが、しっかりと存在してました。
    お手数ですが、解決策がありましたらご連絡お願い致します。

  • (encoding,) = face_recognition.face_encodings(img, loc)
    のところで encoding = ・・・とせず、(encoding, ) =・・・と敢えてタプル化?しているのはなぜでしょうか?
    試しに( )を外してみるとencoding自体はできるみたいですが、その後のface_compareができずエラーが出るのも不思議です(compare_facesに与えているのは結局タプルの中身のリストなのでタプル化しなくても同じことでは??と思ってしまいます)

    よろしくお願いいたします。

    • コメントありがとうございます。
      `face_recognition.face_encodings()` は画像に写っているすべての顔の特徴量をリストで返します。今回のように画像に顔が1つの場合でも [顔の特徴量] といったように要素が1つのリストになっているため、`(encoding, ) = face_recognition.face_encodings()` として返り値を受け取ることにより、変数 encoding の中身をリストではなく、顔の特徴量となるようにしています。

      参考: face_recognition.api.face_encodings() の API
      https://face-recognition.readthedocs.io/en/latest/face_recognition.html#face_recognition.api.face_encodings

      • ご回答ありがとうございます

        要するに
        face_recognition.face_encodings() を実行すると画像中の「すべての顔」を検出して
        [ [顔1の特徴量], [顔2の特徴量], ・・・[顔nの特徴量] ]という「リストを要素とするリスト」が返されるという事だと理解しました。

        ここで今回の例だと画像に顔が1つしかないので[ [顔1の特徴量] ]という形で1つのリストを要素とするリストが得られるため、compare_facesに代入する場合には一度

        (encoding, ) = face_recognition.face_encodings()

        というタプルの形で値を格納して、その後に
        compare_facesの引数にタプルの中身である encoding を代入することで「顔の特徴量のリスト」を引き渡す
        という理解で正しいでしょうか?

        であるならば、この場合に限り、例えば

        encoding = face_recognition.face_encodings()

        とencodingsという変数に格納して、
        compare_facesにはencoding[0]を引き渡しても同じ事でしょうか?

        よろしくお願いいたします。

        • > という理解で正しいでしょうか?

          その理解で合っています。

          > compare_facesにはencoding[0]を引き渡しても同じ事でしょうか?

          どのようなコードを意図しているかわからないので合っているかどうか断言できないのですが、

          “`
          face_recognition.compare_faces([顔1の特徴量, 顔2の特徴量, ・・・顔nの特徴量],
          顔Aの特徴量)
          # 各特徴量は数値のリスト
          “`

          と渡して、[顔1は顔Aと同じかどうか, 顔2は顔Aと同じかどうか, …] という bool 型のリストを得るのが最終目標であり、
          そのため、顔の特徴量を抽出するところで、

          既知の顔の特徴量の一覧: [顔1の特徴量, 顔2の特徴量, ・・・顔nの特徴量]
          未知の顔の特徴量: 顔Aの特徴量

          という「特徴量のリスト」と「特徴量」の2つを用意するのが記事のコードの意図になります。

コメントする

目次