- はじめに
- 今回のゴール
- 特徴点比較の下準備
- AKAZE特徴量を使った特徴点検出の準備
- 二つの顔領域の特徴点を取得
- BFMatcherを定義
- BFMatcherで総当たりマッチングを行う
- 特徴量の距離を出し、平均を取る
- 定数
- 分類器の指定
- faces以下の画像ファイルをリスト化
- 名前を入れとく配列
- 顔領域の画像を入れとく配列
- 画面に表示される名前
- カメラ映像取得
- 初期フレームの読込
- 変換処理ループ
- 終了処理
- カメラ映像取得
- 初期フレームの読込
- 変換処理ループ
- webカメラに映った顔領域の画像を入れとく
- 最後に
はじめに
最終回の投稿となる今回は、いよいよ顔照合機能を作っていきたいと思います。なお、この記事は顔認証システムの実装② (特徴点抽出機能)の続きとなっているので、そちらをまず見ることを推奨します。
今回のゴール
今回のゴールはズバリ、webカメラの映像からリアルタイムに顔照合する機能を完成させることです!
特徴点比較の下準備
前回取得した特徴点を元に、今回は二つ以上の顔画像を比較していきます。その関係上画像をファイルを複数扱っていくのですが、いちいち長いパスを直打ちするのもめんどくさいので、このプロジェクトの下に顔画像専用のフォルダーを作成しましょう!
まず、faceRecognitionプロジェクトの上で右クリックします。そして、「新規」→「フォルダー」をクリックします。
この画面が出たら、フォルダー名を入力します。なんでも良いですがここではfacesにしておきます。
↑↑このようにパッケージエクスプローラーに表示されていれば完了です!
次にこの中に顔画像をいくつかいれていきます。画像をフォルダーに入れるためにはfinderなどでフォルダーにいれたい画像ファイルを表示し、ドラックアンドドロップでEclipse上のfacesフォルダーの上に合わせることでできます。
それでは早速いれていきましょう!なお、画像のファイル名は基本画像ファイル上の人物の名前に合わせるようにしてください。
↑↑できました!!最初は5個くらい用意できればOKです。画像を増やす方法はわかったと思うので、必要な時にまた増やしましょう!
これで下準備は終わりです。
特徴点を比較する
[2]特徴点を比較してみる
まず単に特徴点を比較してみます。どのように比較するのかというと、顔領域内の特徴点の数とその距離を元に行います。詳しい解説はその処理の部分で説明します。
[2]特徴点を比較してみる まず単に特徴点を比較してみます。どのように比較するのかというと、顔領域内の特徴点の数とその距離を元に行います。詳しい解説はその処理の部分で説明します。 [2.1.1]実装 <200b> import cv2 <200b> # 切り取った顔領域の大きさを調整する時に使う値 IMG_SIZE = (200, 200) <200b> # カスケード機生成 face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml') # 画像ファイルの読み込み img1 = cv2.imread("faces/img1.jpg") img2 = cv2.imread("faces/img2.jpg") <200b> # グレースケール変換 img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) <200b> # 顔領域取得 img1_faces = face_cascade.detectMultiScale(img1_gray, minSize=(100, 100)) img2_faces = face_cascade.detectMultiScale(img2_gray, minSize=(100, 100)) <200b> # 画の顔領域の画像の座標を取得 img1_face_rect = img1_faces[0] img2_face_rect = img2_faces[0] x1, y1, w1, h1 = img1_face_rect[0],img1_face_rect[1],img1_face_rect[2],img1_face_rect[3] x2, y2, w2, h2 = img2_face_rect[0],img2_face_rect[1],img2_face_rect[2],img2_face_rect[3] <200b> # 画像から顔領域を抽出し大きさを200x200に変更 img1_face = img1[y1:y1+h1, x1:x1+w1] img2_face = img2[y2:y2+h2, x2:x2+w2] img1_face = cv2.resize(img1_face, IMG_SIZE) img2_face = cv2.resize(img2_face, IMG_SIZE) <200b> # AKAZE特徴量を使った特徴点検出の準備 akaze = cv2.AKAZE_create() <200b> # 二つの顔領域の特徴点を取得 (img1_face_kp, img1_face_des) = akaze.detectAndCompute(img1_face, None) (img2_face_kp, img2_face_des) = akaze.detectAndCompute(img2_face, None) <200b> # BFMatcherを定義 bf = cv2.BFMatcher(cv2.NORM_HAMMING) # BFMatcherで総当たりマッチングを行う matches = bf.match(img1_face_des, img2_face_des) <200b> #特徴量の距離を出し、平均を取る dist = [m.distance for m in matches] if len(dist) != 0: ret = sum(dist) / len(dist) print("二人の顔の類似度は" + str(ret) + "になります!!") <200b> comparing_img = cv2.drawMatches(img1_face, img1_face_kp, img2_face, img2_face_kp, matches[:10], None, flags=2) cv2.imwrite("compare.jpg", comparing_img)
実装の解説
それでは解説していきます。
# カスケード機生成 face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml') # 画像ファイルの読み込み img1 = cv2.imread("faces/img1.jpg") img2 = cv2.imread("faces/img2.jpg") <200b> # グレースケール変換 img1_gray = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) img2_gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) <200b> # 顔領域取得 img1_faces = face_cascade.detectMultiScale(img1_gray, minSize=(100, 100)) img2_faces = face_cascade.detectMultiScale(img2_gray, minSize=(100, 100))
- この部分ではカスケード機の生成、比較する二つの画像の読み込みを行います。また、カスケード機を用いた顔検出にはグレースケールされた画像が必要なので、読み込んだ画像をそれぞれグレースケール化します。その後顔領域を取得します。ここら辺は前回と似ていますね。
# 画の顔領域の画像の座標を取得 img1_face_rect = img1_faces[0] img2_face_rect = img2_faces[0] x1, y1, w1, h1 = img1_face_rect[0],img1_face_rect[1],img1_face_rect[2],img1_face_rect[3] x2, y2, w2, h2 = img2_face_rect[0],img2_face_rect[1],img2_face_rect[2],img2_face_rect[3]
- ここではまず、取得した顔領域の座標を取得します。x、y、w、hはそれぞれ、顔領域の左下の角のx座標、顔領域の左下の角のy座標、顔領域の横幅(wide)、顔領域の縦幅(height)を表します。この値を二つの画像から取得します。
# 画像から顔領域を抽出し大きさを200x200に変更
img1_face = img1[y1:y1+h1, x1:x1+w1]
img2_face = img2[y2:y2+h2, x2:x2+w2]
img1_face = cv2.resize(img1_face, IMG_SIZE)
img2_face = cv2.resize(img2_face, IMG_SIZE)
そして読み込んだ画像全体から顔領域のみの画像を抽出します。img1[y1:y1+h1, x1:x1+w1]で先ほど取得したx、y、w、hを元に縦幅y~(y+h)、横幅x~(x+w)という感じで抽出します。
cv2.resize(img1_face, IMG_SIZE)では、抽出した顔領域の画像を、比較しやすいようにあらかじめ決めといた大きさ(200x200)に調整しておきます。
AKAZE特徴量を使った特徴点検出の準備
akaze = cv2.AKAZE_create() 二つの顔領域の特徴点を取得
(img1_face_kp, img1_face_des) = akaze.detectAndCompute(img1_face, None) (img2_face_kp, img2_face_des) = akaze.detectAndCompute(img2_face, None)
- ここでは毎度おなじみAKAZE特徴量を使って読み込んだ二つの顔領域から特徴点を検出します。また、今回の特徴点の抽出は**detectAndCompute**によって行なっています。このようにすることで、特徴点の比較をする際に必要な特徴点記述子を受け取っています。(img1_face_des、img2_face_des)また、img1_face_kpとimg2_face_kpはそれぞれの画像の特徴点の座標を表します。
BFMatcherを定義
bf = cv2.BFMatcher(cv2.NORM_HAMMING)
BFMatcherで総当たりマッチングを行う
matches = bf.match(img1_face_des, img2_face_des)
- ここでは特徴点の総当たりマッチングを行います。名前から察した人もいるかと思いますが、1つ目の画像の特徴点が2つ目のどの特徴点に対応するのかを、特徴点の総当たりで調べます。総当たりマッチングを行うためにはBFMatcherオブジェクトというものが必要となるので**bf**として定義しています。そして先ほど受け取った**img1_face_des**と**img2_face_des**を使って総当たりマッチングを行います。
特徴量の距離を出し、平均を取る
dist = [m.distance for m in matches] if len(dist) != 0: ret = sum(dist) / len(dist) print("二人の顔の類似度は" + str(ret) + "になります!!")
- ここでは特徴点の差分の合計の計算をします。まず、matchesによって検出した特徴点ごとの差分をdist配列の中に入れます。そしてret = sum(dist) / len(dist)にあるように、差分の平均値を計算し、この値を一致度(どれくらい特徴点が似ているか)とします。
comparing_img = cv2.drawMatches(img1_face, img1_face_kp, img2_face, img2_face_kp, matches[:10], None, flags=2) cv2.imwrite("compare.jpg", comparing_img)
- ここでは**drawMatches()**を使用して結果となる画像を出力します。(結果画像は実行結果をご覧ください!)drawMatches()の引数にmatches[:10]と指定することで、一致度が高い順に10個書き込んでいます。最後に**compare.jpg**という名前で画像ファイルを作成します。 ### 実行結果 [f:id:iTD_GRP:20190624224117j:plain] [f:id:iTD_GRP:20190624224135p:plain] ↑これが実行結果になります!しっかり比較できていますね。 この類似度に関しては前述した通り対応する特徴点の距離を表しています。95と言う値が大きいのか少ないのかは微妙なところです。 ## webカメラからリアルタイムに特徴点比較してみる ### 実装
import cv2 import os from mailcap import show IMG_SIZE = (200, 200)
定数
bf = cv2.BFMatcher(cv2.NORM_HAMMING) detector = cv2.AKAZE_create() ESC_KEY = 27 # Escキー INTERVAL= 33 # 待ち時間 FRAME_RATE = 30 # fps DEVICE_ID = 0
分類器の指定
cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
faces以下の画像ファイルをリスト化
face_names = os.listdir("faces")
名前を入れとく配列
person_names =
顔領域の画像を入れとく配列
faces =
画面に表示される名前
show_name = "" for face_name in face_names: file_img = cv2.imread("faces/" + str(face_name), cv2.IMREAD_GRAYSCALE) face = cascade.detectMultiScale(file_img, minSize=IMG_SIZE) for (x, y, w, h) in face: faces.append(cv2.resize(file_img[y:y+h, x:x+w], IMG_SIZE)) person_names.append(str(face_name)) break
カメラ映像取得
cap = cv2.VideoCapture(DEVICE_ID)
初期フレームの読込
end_flag, c_frame = cap.read() height, width, channels = c_frame.shape
変換処理ループ
while end_flag == True: # 画像の取得と顔の検出 img = c_frame img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) face_list = cascade.detectMultiScale(img_gray, minSize=IMG_SIZE) # webカメラに映った顔領域の画像を入れとく target_faces = # webカメラから顔検出 for (x, y, w, h) in face_list: target_faces.append(cv2.resize(img_gray[y:y+h, x:x+w], IMG_SIZE)) cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2) cv2.putText(img, show_name, (x + int(w/2), y + h + 30), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 0, 0), thickness=1) if len(target_faces) > 0: (target_kp, target_des) = detector.detectAndCompute(target_faces[0], None) for face in faces: # 比較する写真の特徴点を取得する (comparing_kp, comparing_des) = detector.detectAndCompute(face, None) # BFMatcherで総当たりマッチングを行う matches = bf.match(target_des, comparing_des) # 一致度を入れとく配列 ret_list = #特徴量の距離を出し、平均を取る dist = [m.distance for m in matches] if len(dist) != 0: ret = sum(dist) / len(dist) ret_list.append(ret) # 特徴点の差分が一番小さい値を見つける min_ret_index = ret_list.index(min(ret_list)) show_name = person_names[min_ret_index] print(person_names[min_ret_index]) # フレーム表示 cv2.imshow("img", c_frame) # Escキーで終了 key = cv2.waitKey(INTERVAL) if key == ESC_KEY: break # 次のフレーム読み込み end_flag, c_frame = cap.read()
終了処理
cv2.destroyAllWindows() cap.release()
### 実装の解説 それでは解説していきます。
for face_name in face_names: person_names.append(str(face_name)) file_img = cv2.imread("faces/" + str(face_name), cv2.IMREAD_GRAYSCALE) face = cascade.detectMultiScale(file_img, minSize=IMG_SIZE) for (x, y, w, h) in face: faces.append(cv2.resize(file_img[y:y+h, x:x+w], IMG_SIZE)) break
- ここでは、faces以下の顔が映った画像から顔領域のみを画像にしてfaces配列に入れています。大きさを200x200に調整することであとで計算しやすくします。
カメラ映像取得
cap = cv2.VideoCapture(DEVICE_ID)
初期フレームの読込
end_flag, c_frame = cap.read() height, width, channels = c_frame.shape
変換処理ループ
while end_flag == True: # 画像の取得と顔の検出 img = c_frame img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) face_list = cascade.detectMultiScale(img_gray, minSize=IMG_SIZE)
- ここではwebカメラから映像を受け取る準備をしています。無限ループの中で1フレームごとに画像を受け取り、その画像をグレースケールしてからカスケードによって顔検出を行います。ここら辺の詳しい解説は第3回でも行なったので、詳しくはそちらで確認してみてください!
webカメラに映った顔領域の画像を入れとく
target_faces = []
# webカメラから顔検出
for (x, y, w, h) in face_list:
target_faces.append(cv2.resize(img_gray[y:y+h, x:x+w], IMG_SIZE))
cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
cv2.putText(img, show_name, (x + int(w/2), y + h + 30), cv2.FONT_HERSHEY_SIMPLEX, 1.0, (255, 0, 0), thickness=1)
- ここではwebカメラから受け取った顔領域の座標を元に顔領域を切り取り、その画像をtarget_facesに入れていきます。その後、webカメラの顔領域を四角で囲み、その四角の上にもっとも一致率の高い人名の名前を表示します。
if len(target_faces) > 0: (target_kp, target_des) = detector.detectAndCompute(target_faces[0], None)
- ここではwebカメラに映った顔領域に対して**detectAndCompute()**を行なっています。 **target_kp**と**target_des**を取得します。 - Python for face in faces: # 比較する写真の特徴点を取得する (comparing_kp, comparing_des) = detector.detectAndCompute(face, None) # BFMatcherで総当たりマッチングを行う matches = bf.match(target_des, comparing_des)
- ここではfacesディレクトリ以下の顔画像の顔領域に対してdetectAndCompute()を行なっています。その後にtarget_desを使ってcomparing_des**総当たりマッチングを行います。
# 一致度を入れとく配列 ret_list = [] #特徴量の距離を出し、平均を取る dist = [m.distance for m in matches] if len(dist) != 0: ret = sum(dist) / len(dist) ret_list.append(ret)
- ここでは前章の実装と同じように特徴点の差分の合計の計算をします。詳しい説明は省きますが、今回の実装では計算した一致度の値をret_listに入れていきます。
# 特徴点の差分が一番小さい値を見つける min_ret_index = ret_list.index(min(ret_list)) show_name = person_names[min_ret_index] print(person_names[min_ret_index])
- 前のforループで取ってきた一致度の中から最小の値を取得し、その値を持っている人名をperson_namesから探し、その名前でshow_nameを書き換えます。
実行結果
↓以下が実行結果になります。
この画像はwebカメラ起動中の時の映像をスクショしたものです。しっかり名前が表示されていますね!
と言うことで、ここまででwebカメラの映像からリアルタイムに顔照合する機能完成です!
最後に
お疲れ様でした!ここまでみてくれて本当にありがとうございます!!本ブログでは顔認証について書いてきたのですがいかがだったでしょうか?なるべく初心者の方にもわかることを心がけたのですが、理解しやすいブログになっていたらと幸いです。
引き続き別のブログも書いていこうと思っているので、またどこかでお会いできることを楽しみにしています。