顔認証が顔を識別する仕組み

顔認証がどのようにして個別の顔を判別しているのかについてざっくりとご紹介します。

HOG の様子

顔検出および認識プロセス

カメラから入力された動画はフレームごとに分解され数値に置き換えられます。これらの各フレーム中に顔が存在するか顔検出器により処理されます。FACE01 GRAPHICSの場合、’HOG’あるいは’CNN’ の2種類の顔検出器を選択することが可能です。

集団の顔を検出する場合やマスクをはめたままの顔を検出する場合は ‘CNN’ 方式を用いることを推奨します。この場合ハードウェアは「高速な CPU 」と「高速な NVIDIA 製グラフィックカード」が必要になります。

バックエンドでは、アルゴリズムが「classified = false」のレコードを識別し、関数を使用してこの顔の属性を詳述する128次元のベクトルを生成します。アルゴリズムは、この新しい顔が記録上の顔と一致するかどうかを発見するために、ユークリッド距離を使用してデータベース内のすべての顔エントリとこのベクトルを相互参照します。

顔の比較

ユークリッド距離の計算後、アルゴリズムは未知のタイプの人物の新しい personID を生成するか(距離が閾値より大きい場合)、顔を分類済みとしてマークし、personID と一致させます(距離が閾値より小さい場合)。FACE01 GRAPHICSの場合、通常閾値は 0.45 を基本とします。

様々な顔画像に対して ResNet と呼ばれるアルゴリズムの深層学習(ディープラーニング)を介し 128 次元の特徴ベクトルの距離が同一人物で最小になるように学習してあります。ですので、同じ人物かどうかは各顔画像のベクトル間の距離を測り、閾値より小さくなるかどうかを計算することで実現します。

もの凄い勢いで活発に研究されている Deep Learning機械学習であるが、昨年 ILSVRC’2015 という学会のコンペで、一般物体認識で最高性能を叩き出した ResNet (Deep Residual Net)という学習・識別器がある。当時 Microsoft Research にいた Kaiming He 氏が開発した、152層!のニューラルネットである。

ディープラーニング ResNet のヒミツ, http://terada-h.hatenablog.com/entry/2016/12/13/192940

あわせて読みたい記事

下の図は triplet training の代表的なよく使われる画像です。3枚の画像を1セットにし、その中の一つの画像(Query)と似ている方を Positive、似ていない方を Negative という風に3枚ごとにラベル付けを行うトレーニングです。ちなみに Chad Smith はバンドのドラマー、Will Ferrell はコメディアンで、この二人はとても似ていると評判なので例としては最適です。下の図の学習では2つの Will Ferrell の画像の測定値がより近く、Chad Smith の測定値がより遠くなるように、ニューラルネットをわずかに微調整しています。

A single 'triplet' training step.
ディープメトリックラーニングによる顔認識には、「トリプレットトレーニングステップ」が含まれます。トリプレットは3つのユニークな顔画像で構成され、3人のうち2人は同じ人物です。 NNは、3つの顔画像のそれぞれに対して128-dベクトルを生成します。 同じ人物の2つの顔画像について、ニューラルネットワークの重みを微調整して、距離メトリックによってベクトルを近づけます。

ネットワーク自体は、約 300 万枚の画像のデータセットにてトレーニングされました。 Labeled Faces in the Wild(LFW)データセットでは、ネットワークは他の最先端の方法と比較して、 99.38%の精度に達します 。これらの成果物はDlibとして公開されており、FACE01 GRAPHICSではこの成果物を元に構成されています。

各顔画像の 128 次元ベクトルの配列
各顔画像の128次元ベクトルの配列

顔全体の特徴を利用しますので、メガネの有無・髪型の変化など「局所的な変化」に強いのが特長です。

FACE01 GRAPHICS内処理

顔登録用フォルダに集められたファイルは処理前に全て読み込まれます。この時人物名であるファイル名とベクトルデータはセットとしてnpKnown.npz圧縮バイナリデータとして保存されます。また新たに追加された場合はその追加された分のみを処理してデータが追加されます。

FACE01 GRAPHICS 1.2.5を呼び出す例です。fg.load_priset_image.load_priset_image()が圧縮バイナリデータを作成します。既に存在する場合はスキップされます。

# 変数設定 ==========================
tolerance=0.45
jitters=0
priset_face_images_jitters=10
upsampling=0
mode='cnn'
frame_skip=-1
movie='test.mp4'
rectangle=False
target_rectangle=True
show_video=False
frequency_crop_image=10
set_area='NONE'
face_learning_bool=False
how_many_face_learning_images=1
output_frame_data_bool=False
print_property=False
calculate_time=True
SET_WIDTH=500
# ===================================


import time
from concurrent.futures import ProcessPoolExecutor
import cv2
import PySimpleGUI as sg
import FACE01GRAPHICS125 as fg

kaoninshoDir, priset_face_imagesDir=fg.home()
    
known_face_encodings, known_face_names=fg.load_priset_image.load_priset_image(
    kaoninshoDir,
    priset_face_imagesDir, 
    jitters=priset_face_images_jitters, 
)

xs=fg.face_attestation(
    kaoninshoDir, 
    known_face_encodings, 
    known_face_names, 
    tolerance=tolerance, 
    jitters=jitters, 
    upsampling=upsampling,
    mode=mode, 
    model='small', 
    frame_skip=frame_skip, 
    movie=movie, 
    rectangle=rectangle, 
    target_rectangle=target_rectangle, 
    show_video=show_video,
    frequency_crop_image=frequency_crop_image, 
    set_area=set_area,
    face_learning_bool=face_learning_bool,
    how_many_face_learning_images=how_many_face_learning_images,
    output_frame_data=output_frame_data_bool,
    print_property=print_property,
    calculate_time=calculate_time,
    SET_WIDTH=SET_WIDTH
)

# window実装
layout=[
    [sg.Text('GUI実装例')],
    [sg.Image(filename='', pad=(25,25), key='cam1')]
]
window=sg.Window('window1', layout, alpha_channel=1)

# 並行処理
def multi(x):
    return x['img']

pool=ProcessPoolExecutor()
for x in xs:
    # <DEBUG>
    if x=={}:
        continue
    result=pool.submit(multi, x)
    event, _=window.read(timeout=1)
    if event == sg.WIN_CLOSED:
        break
    imgbytes=cv2.imencode('.png', result.result())[1].tobytes()
    window['cam1'].update(data=imgbytes)

# window close
window.close()
print('終了します')
実行中のウィンドウ

また処理した信号を標準出力に出力することでPython以外の言語で映像データを受け取ることも可能です。

以上です。
最後までお読み頂きありがとうございました。