【画像認識】世界の要人を顔認識してみる【Python】

はじめに

※この記事は苫小牧高専アドベントカレンダー2019 8日目の記事です。

ことみんさん(@Kotomi1338)のつぶやきで このアドベントカレンダー企画を知った秋素直です。 学生時代は寮暮らしで、学科展総括兼ネトゲプログラマーをやっていました。 サークルは情処、RPG(現:卓ゲ)、アマ無に所属していました。

何をやろうか

「即位礼正殿の儀」の前日にニュースを見て、 世界の要人を画像から自動判別出来たら面白いのではと思い、 要人リストを作りました。が、そのまま放置していたので、 今回それを使って画像認識系のプログラムを組んでみることにしました。

環境&使用技術

Linux系端末の方が環境構築は楽ですが、 慣れ親しんだWindows環境で環境構築をしました。 案の定環境構築でだいぶ苦労しました。Linuxで構築してればよかった。。。

項番 項目 環境
1 OS Windows 10 Pro (64bit)
2 CPU Intel Core i5-7300U 2.6GHz
3 RAM 8GB
4 Graphic Intel HD Graphics 620

教師データが大量に必要となるディープラーニングは今回は使用しません。 使用アプリ/サービス/ライブラリは下記です。

項番 技術名 コメント
1 Python3 プログラミング言語はコードがシンプルでライブラリも豊富なPythonを使用。
2 Git for Windows ソース管理のためのクライアントソフトとして使用。
3 GitHub ソース管理のためのプラットフォーム側サービスとして使用。
4 Visual Studio Code 統合開発環境。軽量で使用しやすい。Git連携も完璧。
5 OpenCV 有名な画像処理ライブラリ。各種画像処理に利用。
6 FaceNet 顔特徴量抽出ライブラリ。128次元の情報抽出。

データ収集

今回の取り組みの中で正直一番時間がかかった部分。 休日を2日まるまる費やした。休みの日に何をやっているのだろう。

収集方法

以下の手順で各要人ごとに1枚の画像データを収集。

  1. 招待客の特定(日本語記事より)
  2. 招待客のwikipedia記事の特定
  3. 招待客の画像収集

データ収集の課題もろもろ

  • 日本語Wikiが存在しない → 英語wikipedia
  • 名前が英語ですらない → スペイン語wikipediaまで探索、似たアルファベットで整理
  • Google先生に聞いても出てこない → さすがに厳しかった数人は諦め
  • アブドゥラさん、フセインさん、ムハマドさんが世界中に居すぎ問題 → 頑張って特定
  • 夫妻で招待されている → 同伴者は対象外に

収集結果

最終的に131名の要人の画像を収集。

f:id:akisunao:20191201173231j:plain
参列者リスト
f:id:akisunao:20191201173307j:plain
要人画像

肩書別に統計すると、国王13人、王族9人、大統領48人、首相8人とそうそうたるメンツが来ていたことがわかる。

f:id:akisunao:20191201173459j:plain
肩書内訳

特徴量抽出

まずは人物の顔検出と顔切り出しをOpenCVで行う。(131枚→126枚) ネクタイを顔と誤認識するものが結構あったため手動で削除。(126枚→99枚) FaceNetを使用して128次元(正規化された64か所のxy座標)の顔特徴量をとる。(99枚→98枚) 本当は手作業でより多くの教師データを救うべきだが時間がなかったので特徴量が取れない画像等は見捨てました。

項番 項目 枚数
1 手動収集後 131
2 自動顔検出 126
3 手動顔以外除去 99
4 自動特徴量抽出 98

f:id:akisunao:20191208031434j:plain
2 自動顔検出
f:id:akisunao:20191208031500j:plain
3 手動顔以外除去

顔検出コード

顔をOpenCVで検出して切り出すコードは下記。

"""
main.py
"""
import os
import time
from pathlib import Path
import glob
import cv2

# face detect
def face_detect(file_name):
    #open image
    image = cv2.imread(file_name)

    #gray scale convert
    #Don't use "_" as the first character of filename
    #Don't use multi byte character as the filename 
    image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

    #features extraction
    cascade_path = "./model/haarcascade_frontalface_default.xml"
    assert os.path.isfile(cascade_path), 'haarcascade_frontalface_default.xml not exists'
    cascade = cv2.CascadeClassifier(cascade_path)

    #face detect
    facerect = cascade.detectMultiScale(image_gray, scaleFactor=1.1, minNeighbors=2, minSize=(30, 30))

    if len(facerect) > 0:
        #rect = (x, y, w, h)
        max_wxh = 0 
        for rect in facerect:
            if(max_wxh < rect[2] * rect[3]):
                max_rect = rect
            
        return (max_rect[1], max_rect[1] + max_rect[3], max_rect[0], max_rect[0] + max_rect[2])
    return (0, 0, 0, 0)

# 顔切り出し
def cut_face(file_name, cut_rect):
    """cut_face
        cut_rect = [top, bottom, left, right]
    """
    output_file = "." + os.sep + "work" + os.sep + "face" + os.sep
    temp_path = Path(file_name)
    output_file += (temp_path.parts)[1] + ".jpg"

    print("output : " + output_file)
    img = cv2.imread(file_name)
    img1 = img[cut_rect[0] : cut_rect[1], cut_rect[2] : cut_rect[3]]
    cv2.imwrite(output_file, img1)

if __name__ == "__main__":
    print("begin")

    INPUT_PATH = "." + os.sep + "data" + os.sep
    input_list = glob.glob(INPUT_PATH + "*" + os.sep + "*.jpg")

    for input_file in input_list:
        rect = face_detect(input_file)
        # print("rect : " + str(rect))

        cut_face(input_file, rect)

    print("end")

特徴量抽出

下記の記事を参考にFaceNetを使用しました。

  1. GitHubからFaceNetをダウンロード
  2. モデルは上記GitHubREADME.mdに記載の20180408-102900のzipをDL
  3. DLしたモデルはFaceNet以下の./model/配下に配置
  4. FaceNet以下の./src/配下に作成したfeature_extract.pyを配置
  5. FaceNet以下の./data/images/配下に教師画像を格納
  6. コマンドpython src/feature_extract.py ./model/20180408-102900 ./data/images/*を実行
  7. FaceNet以下にimg_facenet.pklが出力されるので、学習モデルとしてimg_dataset_facenet.pklとリネーム

【参考】

Windows環境だったためか、パス内のワイルドカードが展開できなかったため、 image_paths = glob.glob(image_paths[0])を追加。 作成したfeature_extract.pyは長くなるので割愛。 ソースコードGitHubに格納しています。(末尾にリンクを記載)

顔認識

ここまででやっと教師データのデータ化が終わりました。 以下の手順で任意の画像に対して人物名が表示されます。 内部ではテスト画像の特徴量を取得し一番特徴量が近い教師データを探索しています。

  1. FaceNet以下に作成した下記check_distance.pyを配置
  2. FaceNet以下の./data/images/配下に認識したいテスト画像を格納
  3. コマンドpython check_distance.pyを実行
  4. コマンドラインにテスト画像ごとの認識結果が出力される
"""
check_distance.py
"""
import pickle
import subprocess
from scipy import spatial

pkl_dataset_path = "img_dataset_facenet.pkl"
pkl_input_path = "img_facenet.pkl"

# make input pkl
res = subprocess.call('python src/feature_extract.py ./model/20180408-102900 ./data/images/*')

# read dataset
with open(pkl_dataset_path, 'rb') as f:
    data = pickle.load(f)

key_list = []
for key in data.keys():
    key_list.append(key)

# read input_data
with open(pkl_input_path, 'rb') as f:
    input_data = pickle.load(f)

key_input_list = []
for key in input_data.keys():
    key_input_list.append(key)

# compare distance
for input_key in key_input_list:
    output_name = ""
    min_distance = 1000000000
    for key in key_list:
        current_data = data[key]
        assert(len(key_input_list))
        check_data = input_data[input_key]
        distance = spatial.distance.euclidean(current_data, check_data)
        if(min_distance > distance):
            min_distance = distance
            output_name = key
    
    print("input_data is " + input_key)
    print(" -> answer is " + output_name[:-4])

# print(spatial.distance.euclidean(A, B))

評価

98人学習したのですが、テスト画像を集める時間がなかったので以下5名分のテスト画像で評価を行いました。 テスト画像は教師データとは異なる画像を1枚入力しました。 結果は5人中4人正解という結果となりました。 エストニアのカリユライド大統領は、カメルーンのディオン・ングテ首相に誤認識されています。 ディオン・ングテ首相の教師データが顔が半分に切られた中途半端なものだったことが影響している可能性があります。

項番 テスト画像 役職 国家地域 認識結果 判定
1 チャオ 国務長官 アメリ チャオ OK
2 ドゥテルテ 大統領 フィリピン ドゥテルテ OK
3 アウンサンスーチー 国家最高顧問 ミャンマー アウンサンスーチー OK
4 カリユライド 大統領 エストニア ディオン・ングテ NG
5 林鄭月娥 行政長官 香港 林鄭月娥 OK

f:id:akisunao:20191208230252j:plain
テスト画像
f:id:akisunao:20191208231237j:plain
認識結果(教師データ)
f:id:akisunao:20191208230307j:plain
テスト結果

まとめ

ひとまず世界の要人の顔認識をある程度行うことができました。 今回は時間がなかったため試せませんでしたが、 画像から顔の位置を特定する「顔検出」、 顔画像の特徴量を抽出する「顔特徴量抽出」は以下のような新しいモデルも出ており、 差し替えることにより精度が向上すると思われます。

顔検出

項番 要素技術名 コメント 今回実施
1 OpenCV 鼻や目などの識別機の合わせ技
2 DLib DLベースの顔検出。モデルがやや重い
3 RatinaFace 検出精度が高いらしい
4 Ultra-Light-Fast-Generic-Face-Detector モデルが1MBで超高速らしい

顔特徴量抽出

項番 要素技術名 コメント 今回実施
1 FaceNet 128次元の特徴量取得
2 OpenFace 128次元の特徴量取得
3 InsightFace 512次元の特徴量取得

教師データがもっと枚数が用意できれば特徴量をDeepLeaningの入力として、 より汎化性能の高い認識ができると思われます。

参考情報

日テレが放送業務で似たような取り組みを実施していたようです。 海外要人の顔を即時見分けた、日テレ「即位礼正殿の儀」中継の舞台裏

ソースコード

今回作成したソースはこちらGitHubに公開しています。 記事、ソースにコメントがありましたらフィードバックお願いします!