この記事は coins Advent Calendar 2023 10日目の記事です。
どうも、Nameless です。最近は AI が流行りのようですね。今回は割と実用的な AI の使い方を紹介します。
導入
AI を活用したい事例として、顔認識があると思います。ここでは、「ある写真から写っている人物を顔から判別する」ことを考えます。
しかしこんなことを聞いたことがあるでしょう。「AI の学習には、大量のデータ(この場合は学習させたい人物の写真) が必要である!」と。
これは確かに正しいのですが、今回のようなタスクだと実は必要ありません。問題設定を適切にすることで、追加学習なしでたった一枚の画像から人物を識別できるようになります。
今回想定するユースケース
- AI を利用して在室検知したい状況を考えます。
- web カメラから部屋を定期的に撮影し、誰が写っているのかを判別して記録します。
- 検知したい人物の顔写真は 1 枚程度しか持っていないものとします。
実装
目標は 1 枚の画像から、そこに映る人物が誰なのかを特定することです。しかし、私たちは判別したい人物の顔写真を何万枚も持っていません (逆にそれだけの写真を持っていたら怖いですね(笑))。よって、分類問題として AI にゼロから学習させるのは不可能でしょう。では、回帰問題としてならどうでしょうか?ここで、顔をベクトルに変換することを考えます。
ここで、同じ人物の顔画像から変換されたベクトル同士のコサイン類似度が 1 になるように、また別の人物の顔画像から変換されたベクトル同士のコサイン類似度が 0 になるように学習させるとどうなるでしょうか?こうすると、AI は渡された画像から人物ごとの普遍的な特徴を精確に捉えたベクトルを出力できるようになります。こうすることで、データセットに含まれていない人物に対してもたった 1 枚の画像を事前に用意すれば同一人物かどうかの判定が出来てしまうのです。
形的には、事前に用意した識別したい人物の顔写真から出来たベクトルと、推論させたい顔写真から出来たベクトルを照合します。これが 1 に近ければ同一人物、0 に近ければ別人として判定します。
とはいえ自分で 0 から学習させるのは骨が折れるので、事前学習済みモデルを使います。今回は insightface というモデルを使用しました。(ちなみにインストールがちょっと大変です)
以下はwebカメラから画像を取得して顔認識し、json に書き込むコードです。
import numpy as np
import cv2
from insightface.app import FaceAnalysis
import db
import time
import os
app = FaceAnalysis()
app.prepare(ctx_id=1, det_size=(640, 640))
def cos_sim(feat1, feat2):
return np.dot(feat1, feat2) / (np.linalg.norm(feat1) * np.linalg.norm(feat2))
faces_dir = './faces'
folders = {}
with os.scandir(faces_dir) as fs:
for entry in fs:
if entry.is_dir():
folders[entry.name] = []
with os.scandir(faces_dir+'/'+entry.name) as folder:
for face in folder:
img = cv2.imread(faces_dir+'/'+entry.name+'/'+face.name)
tmp = app.get(img)
vec = tmp[0].embedding
folders[entry.name].append(vec)
def match(vec):
ans = [0, -1]
for name, faces in folders.items():
for face in faces:
if cos_sim(vec, face) > ans[1]:
ans = [name, cos_sim(vec, face)]
if ans[1] > 0.5:
return ans[0]
else:
return -1
capture = cv2.VideoCapture(0)
while True:
ret, flame = capture.read()
if(ret == False):
break
embs = app.get(flame)
for emb in embs:
vec = emb.embedding
m = match(vec)
if m != -1:
db.write(m, int(time.time()))
print(m, int(time.time()))
detect = app.draw_on(flame, embs)
cv2.imshow("flame", detect)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
import json
import time
import datetime
import zoneinfo
import os
db_path = './db/'
def write(user_id: str, date: int):
d = datetime.datetime.fromtimestamp(date, datetime.timezone.utc).astimezone(datetime.timezone(datetime.timedelta(hours=9)))
file_name = db_path+f'{str(d)[:10]}.json'
if not os.path.isfile(file_name):
with open(file_name, mode='w') as f:
f.write(r'{}')
with open(file_name) as f:
db = json.load(f)
if user_id not in db.keys():
db[user_id] = []
db[user_id].append(date)
with open(file_name, 'w') as f:
json.dump(db, f, indent=2)
検出したい人物の登録には特に難しい操作は必要なく、faces / [検出したい人物の名前] / hogehoge.jpg
を作成することで簡単にいくらでも顔認識の対象者を登録できるようにしています。以下は実際のフォルダです。(私以外の名前は伏せています)
db
フォルダに自動で日付ごとに json ファイルが作成されます。
以下は実際の json ファイルの中身です。UNIX 時間で記録しています。(私以外の名前は伏せています)
ここまでの実装を、以下に置いています。
あとはこの json ファイルをパースしていい感じに表示するアプリを作れば、無事目標達成です。
おわりに
今回は、追加学習なしで 1 枚の画像から顔認識をする方法を紹介しました。問題設定次第で目的を比較的簡単に達成できるようになるのはとても面白いですね。