軽量な顔認証アプリケーションの作成

GPUカードのついていないノートPCや事務用のパソコンで顔認証アプリケーションを構築したい時、どのようにしたら良いでしょうか。今回の記事では実際に作成したアプリケーションを紹介します。ソースコードを見れば基本的な構築方法の勘所がつかめるかと思います。

軽量な顔認証アプリケーションの作成

軽量顔認証アプリケーションの動作

実際の動作の様子をご覧ください。分かりやすくする為に必要最低限の機能しかつけていません。

キャプチャボタンを押すと画面がキャプチャされます。キャプチャされた画像に対してFACE01 IMAGERが顔認証を行い、その結果を標準出力に出力します。

test.mp4 :バッファリング中…
lightweight_sample2021,10,06,11,47,09,754293.png に顔を検出できませんでした。 mode を hog から cnn にして再試行...
mode = 'cnn' で顔探索中…
顔を検出しました mode='hog' に戻します 
...OK
安倍晋三 
         99.85% 
         2021,10,06,11,47,11,802225 
 ------------- 

安倍晋三 
         99.76% 
         2021,10,06,11,47,16,488129 
 ------------- 

lightweight_sample2021,10,06,11,47,20,059332.png に顔を検出できませんでした。 mode を hog から cnn にして再試行...
mode = 'cnn' で顔探索中…
顔を検出しました mode='hog' に戻します 
...OK
安倍晋三 
         99.66% 
         2021,10,06,11,47,20,103594 
 ------------- 

lightweight_sample2021,10,06,11,47,24,006728.png に顔を検出できませんでした。 mode を hog から cnn にして再試行...
mode = 'cnn' で顔探索中…
顔を検出しました mode='hog' に戻します 
...OK
安倍晋三 
         99.58% 
         2021,10,06,11,47,24,050191 
 ------------- 

安倍晋三 
         99.82% 
         2021,10,06,11,47,27,238597 
 ------------- 

安倍晋三 
         99.61% 
         2021,10,06,11,47,30,070379 
 ------------- 

安倍晋三 
         99.85% 
         2021,10,06,11,47,37,112671 
 ------------- 
出力顔画像

実際の顔認証アプリケーションを作成する場合、出力された情報とカードから得られた情報を照合して鍵を開閉したりするわけです。作成したサンプルの場合約650人の顔情報が入っています。ワンフロアをカバーする人数としては十分でしょう。カードリーダーが読み込まれたタイミングで画面をキャプチャするとかタッチパネルを使うとか画面上でボタンを表示するとか、現場に合わせて改変すると良いと思います。

ソースコード

顔認証のロジックは殆どFACE01 IMAGERが行ってしまうので、ここで行っているのは画面やボタンを表示させるコードとFACE01 IMAGERを呼び出す事だけです。GUIには簡単に扱えるPySimpleGUIを用いています。一度全コードを載せて、その後に個々の説明を行います。

import os
from configparser import ConfigParser
from datetime import datetime

import cv2
import PySimpleGUI as sg

import FACE01IMAGER129 as fi
from face01lib129.video_capture import video_capture

# iniファイル読み込み
conf=ConfigParser()
conf.read('config_FACE01GRAPHICS129.ini', 'utf-8')
similar_percentage= float(conf.get('DEFAULT','similar_percentage'))
jitters=            int(conf.get('DEFAULT','jitters'))
upsampling=         int(conf.get('DEFAULT','upsampling'))
mode=               'hog'
movie=              conf.get('DEFAULT','movie')

kaoninshoDir, priset_face_imagesDir, check_images = fi.home()

known_face_encodings, known_face_names = fi.load_priset_image.load_priset_image(
    kaoninshoDir,
    priset_face_imagesDir, 
    jitters
)

sg.theme('Reddit')

layout = [
    [sg.Text('軽量アプリケーションのサンプル')],
    [sg.Image(key='display')],
    [sg.Button('キャプチャ', key='capture_button', expand_x=True)],
    [sg.Button('終了', key='terminate', button_color='red')]
]

window = sg.Window('lightweight_sample', layout)

vcap = video_capture(kaoninshoDir, movie)

while True:
    ret, frame = vcap.read()
    if ret == False:
        print('入力動画の信号がないため終了します')
        break

    height = vcap.get(cv2.CAP_PROP_FRAME_HEIGHT)
    width  = vcap.get(cv2.CAP_PROP_FRAME_WIDTH)
    HEIGHT = int(height * (500 / width))
    small_frame = cv2.resize(frame, (500, HEIGHT))

    event, _ = window.read(timeout=1)

    imgbytes=cv2.imencode(".png", small_frame)[1].tobytes()
    window["display"].update(data=imgbytes)

    if event=='terminate':
        break

    if event=='capture_button':
        date = datetime.now().strftime("%Y,%m,%d,%H,%M,%S,%f")

        filename = 'check_images/lightweight_sample' + date + '.png'
        
        os.chdir(kaoninshoDir)

        cv2.imwrite(filename, small_frame)

        con = fi.Face_attestation

        xs = con.face_attestation(
            kaoninshoDir,
            check_images, 
            known_face_encodings, 
            known_face_names, 
            similar_percentage=similar_percentage,
            jitters=jitters,
            upsampling=upsampling,
            mode=mode
        )

        for x in xs:
            name, date, percentage = x['name'], x['date'], x['percentage']
            print(
                name, "\n",
                "\t", percentage, "\n",
                "\t", date, "\n",
                '-------------', "\n",
            )
            break

基本的な考え方

ノートPCや事務用パソコンでの使用を前提として考えましたので、GPUカードが必須なFACE01 GRAPHICSではなく、軽量に動作するFACE01 IMAGERを使っていきます。

import FACE01IMAGER129 as fi
from face01lib129.video_capture import video_capture

FACE01 IMAGERには映像を扱うために便利なライブラリもついてきます。2行目は映像データを適切に処理するライブラリをインポートしています。今回はmp4データを使っていますがHLSやWebカメラからの映像データも扱うことが出来ます。

from configparser import ConfigParser

conf=ConfigParser()
conf.read('config_FACE01GRAPHICS129.ini', 'utf-8')

similar_percentage= float(conf.get('DEFAULT','similar_percentage'))
jitters=            int(conf.get('DEFAULT','jitters'))
upsampling=         int(conf.get('DEFAULT','upsampling'))
mode=               'hog'
movie=              conf.get('DEFAULT','movie')

ConfigParserライブラリを用いてiniファイルからの情報を取得しています。iniファイルはこの間使ったFACE01 GRAPHICS ver.1.2.9のものを使いまわしました。SETTING MANAGERから内容を変更することが出来ます。ここで設定するべきは「映像入力元」「顔検出解像度」「類似度」です。その他の項目、例えば映像サイズとかはサンプルなので固定にしました。

kaoninshoDir, priset_face_imagesDir, check_images = fi.home()

known_face_encodings, known_face_names = fi.load_priset_image.load_priset_image(
    kaoninshoDir,
    priset_face_imagesDir, 
    jitters
)

FACE01 IMAGERのインスタンスを使って約650人の保存された顔データと対応する人物名をメモリに展開しています。(顔データから顔は復元できません。セキュリティ的に。)

sg.theme('Reddit')

layout = [
    [sg.Text('軽量アプリケーションのサンプル')],
    [sg.Image(key='display')],
    [sg.Button('キャプチャ', key='capture_button', expand_x=True)],
    [sg.Button('終了', key='terminate', button_color='red')]
]

window = sg.Window('lightweight_sample', layout)

PySimpleGUIを使ったウィンドウの作成をこの部分で行っています。サンプルなので非常に簡素な作りです。

vcap = video_capture(kaoninshoDir, movie)

while True:
    ret, frame = vcap.read()
    if ret == False:
        print('入力動画の信号がないため終了します')
        break

    height = vcap.get(cv2.CAP_PROP_FRAME_HEIGHT)
    width  = vcap.get(cv2.CAP_PROP_FRAME_WIDTH)
    HEIGHT = int(height * (500 / width))
    small_frame = cv2.resize(frame, (500, HEIGHT))

ライブラリのインスタンスに引数を渡してオブジェクトを作り、whileブロックで連続して映像データを読み出しています。各々のフレームは横幅500pxにリサイズする処理を挟んでいます。サンプルなので500pxに決め打ちしました。

event, _ = window.read(timeout=1)

windowは1μ秒毎に更新させます。イベントは変数eventで受け取ります。

    imgbytes=cv2.imencode(".png", small_frame)[1].tobytes()
    window["display"].update(data=imgbytes)

PySimpleGUIでは多分png画像といくつかしか使えないので画像形式を変換してからwindowに貼り付けて更新しています。

    if event=='terminate':
        break

映像が途切れるとwhileブロックから抜けてプログラムを終了します。

    if event=='capture_button':
        date = datetime.now().strftime("%Y,%m,%d,%H,%M,%S,%f")

        filename = 'check_images/lightweight_sample' + date + '.png'
        
        os.chdir(kaoninshoDir)

        cv2.imwrite(filename, small_frame)

キャプチャボタンが押されると、画像を保存します。顔認証処理が終わるとこの画像データは別フォルダに移されるのでファイル名が被らない為と後々日時が分かるようにdatetimeライブラリで日時を取得してファイル名に使っています。

        con = fi.Face_attestation

        xs = con.face_attestation(
            kaoninshoDir,
            check_images, 
            known_face_encodings, 
            known_face_names, 
            similar_percentage=similar_percentage,
            jitters=jitters,
            upsampling=upsampling,
            mode=mode
        )

FACE01 IMAGERのFace_attestationクラスからインスタンスを作りface_attestationメソッドに必要な引数を渡してジェネレーターオブジェクトを受け取ります。顔認証のためのロジックは実質この2行だけです。

        for x in xs:
            name, date, percentage = x['name'], x['date'], x['percentage']
            print(
                name, "\n",
                "\t", percentage, "\n",
                "\t", date, "\n",
                '-------------', "\n",
            )
            break

結果として受け取ったジェネレーターオブジェクトには辞書形式でいくつかの情報が格納されています。name, date, percentageを標準出力に出力しています。

最後に

いかがだったでしょうか。事務用パソコンやノートPCでも問題なく使用できる顔認証アプリケーションがこんなに簡単に開発できます。是非サンプルアプリケーションダウンロードからFACE01 IMAGERをダウンロードして、今回作成したソースコードを試してみてください。

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