Jetson Nano 2GB 系列文章(13):身份識別

在本系列上一篇文章中,給大家介紹瞭如何使用 face_recognition 庫裡的 face_locations () 函數,使用不到 20 行的 python 代碼,輕鬆地在圖像、視頻中定位出人臉,本文帶讀者進一步瞭解 face_recognition 庫的身份識別功能。


Face_recognition 庫是基於 dlib 機器學習算法的人臉識別應用庫,底層已經有非常優異且完善的人臉特徵算法,不僅函數調用十分簡單,要建立特徵數據庫也相當輕鬆,只要為每個身份準備”一張“清晰的正面圖像就可以,不像目前最流行的深度學習需要為每個身份收集數十張到上百張的圖像,光這部分就產生非常大的差距。


本實驗為避免肖像權的問題,依舊使用 NVIDIA GAN 對抗網絡技術的 Youtube 公開宣傳視頻(
https://www.youtube.com/watch?v=kSLJriaOumA)的部分片段(第 27 秒至 1 分 04 秒),並且只截取屬於 GAN 人工合成部分的人臉,這裡截取四張過程中的照片,如下,分別有一個兒童 ( Kit_A )、兩個女士( Miss_B 與 Miss_C )、一位男士( Mr_D )。


Jetson Nano 2GB 系列文章(13):身份識別


讀者可自行建立要識別的圖像庫,讓公司每個員工或者班上每位同學提供一張清晰的正臉照片,搭建一個臉部特徵庫即可。


身份識別的三個階段


1. 用 face_encodings() 函數建立“人臉特徵庫”,包括就是自己所建立的識別身份(known)圖像集,以及欲識別的輸入圖像等,都是使用這個函數來建立特徵庫,裡面包含臉部 9 個位置的特徵參數。


2. 用 face_locations() 在讀入的圖像或視頻幀中,找到人臉的位置


3. 用 compare_face() 將步驟 2 找到的人臉,與步驟 1 所建立的”已知特徵庫“進行比對,找出“近似度“在要求範圍( tolerance )內圖像。


關於 face_recognition 的函數調用細節,請參考
https://face-recognition.readthedocs.io/en/latest/face_recognition.html


剩下的任務就是將輸入圖形中所找到的人臉,進行標框與身份標示,然後輸出到顯示器上面就行。


整個作業其實就是調用 face_recognition 庫裡的 face_locations、face_encodings 與 compare_faces 這三個函數,就能完成整個身份識別的項目。


對單張圖像進行身份識別


前面我們已經在 NVIDIA GAN 人工合成視頻中,截取四個身份圖像( Kit_A、Miss_B、Miss_C、Mr_D ),因此一開始先讀入這四張圖片,並進行個別特徵編碼( face_encodings ),然後將所有編碼加成到 idEncodings 裡,並在 idNames 裡指定對應的身份名稱。


接下來讀入測試用 testImage,並且進行人臉定位、編碼工作,然後與 idEncodings 編碼庫進行逐一比對,整個邏輯相當直觀。


<code>

import

cv2

import

face_recognition as fr

faceKitA

=

cv2.imread('Kit_A.png')

faceMissB

=

cv2.imread('Miss_B.png')

faceMissC

=

cv2.imread('Miss_C.png')

faceMrD

=

cv2.imread('Mr_D.png')

encodeKitA

=

fr.face_encodings(faceKitA)[0]

encodeMissB

=

fr.face_encodings(faceMissB)[0]

encodeMissC

=

fr.face_encodings(faceMissC)[0]

encodeMrD

=

fr.face_encodings(faceMrD)[0]

idEncodings

=

[encodeKitA, encodeMissB, encodeMissC, encodeMrD]

idNames

=

['Kit_A','Miss_B','Miss_C','Mr_D']

testImage

=

cv2.imread('GAN_Pic03.png')

testLocation

=

fr.face_locations(testImage)

testEncodings

=

fr.face_encodings(testImage,testLocation)

for

(top,right,bottom,left), testFaceEncoding in zip(testLocation,testEncodings):

name

=

'Unknown'

matches

=

fr.compare_faces(idEncodings,testFaceEncoding)

if

True in matches: # 如果有找到比對庫裡的人臉,則找出對應的名字

first_match_index

=

matches.index(True)

name

=

idNames[first_match_index]

cv2.rectangle(testImage,(left,top),(right,bottom),(255,0,0),2)

top),(0,255,255),-1)

cv2.putText(testImage,name,(left,top-10),0,.75,(255,0,0),2)

cv2.imshow('myWindow',testImage)

cv2.moveWindow('myWindow',0,0)

if

cv2.waitKey(0)==ord('q'):

    cv2.destroyAllWindows()

/<code>



下面是我們在 NVIDIA GAN 宣傳視頻中,截取三張不同變化的圖片作為測試,這些人臉全部都是計算機合成,大家可以與前面的四張圖片進行肉眼比對。


Jetson Nano 2GB 系列文章(13):身份識別


Jetson Nano 2GB 系列文章(13):身份識別


Jetson Nano 2GB 系列文章(13):身份識別


這段代碼的執行過程,會發現計算過程中,在特徵編碼( face_encodings )這個部分耗費最多的時間,如果您的系統上有安裝 jetson-stats 這個工具,可以看到在編碼過程中,GPU 的執行率最高的,一旦完成特徵編碼之後,後面比對的時間倒是相對少的。


如果每次執行這個身份識別,都需要重新導入圖片、進行特徵編碼的話,是非常不划算的,並且如果圖片數量增加的話,這個時間浪費就更多了。因此接下去我們就要學會建立一個固定的身份特徵編碼文件,就能減少不必要的重複編碼工作。


建立身份特徵編碼文件


其實這個原理也是非常簡單,就是將前面代碼中的 idEncodings 與 idNames 寫入一個指定文件內就可以,只要欲識別的身份數量沒有增加,就不用更改這個文件。這個工作其實有點類似深度學習的模型訓練,但是不用像深度學習需要這麼多的圖像數據進行特徵提取,每個身份只需要一張清晰的正面照片就可以。


為了提高代碼通用性,便於未來要添加更多身份圖像時,不需要修改代碼,因此這裡使用“文件夾”與“文件名”的方式,來管理這個特徵編碼文件的內容。


首先,開啟一個 " known " 文件夾,將所有需要識別的圖像( Kit_A.png、Miss_B.png、Miss_C.png、Mr_D.png )都存放在這個文件夾,並且用文件名作為識別名( idName )。


接下來看看 face_recognition 能做什麼事情?

import cv2
import face_recognition as fr
import os
import pickle


image_dir='./known' # 存放圖片數據的目錄
idEncodings=[] # 存放編碼數據
idNames=[] # 存放身份名稱,用文件名列表


for root, dirs, files in os.walk(image_dir):
print('All files under this folder are '+ str(files) )
for file in files:
# 獲取文件名作為身份識別名
fullPath = os.path.join(root,file)
filename = os.path.splitext(file)[0]


print('This ID Name is ' + filename)
idNames.append(filename)
# 讀取圖像,進行特徵編碼
person = cv2.imread(fullPath)
encoding=fr.face_encodings(person)[0]
idEncodings.append(encoding)
print('Full idName list is ' + str(idNames) )
# 用pickle函數將數據序列化寫入指定文件,這裡是 ‘myId.pkl’
with open('myId.pkl','wb') as f:
pickle.dump(idNames,f)
pickle.dump(idEncodings,f)


執行結果如下,會逐個將目錄下的圖像都讀進來處理,最後生成一個存放身份名稱與特徵編碼的文件,本例為 " myId.pkl ",在其他代碼中直接讀入使用,不需要再重複執行高計算量的編碼作業。


Jetson Nano 2GB 系列文章(13):身份識別


使用特徵編碼文件識別視頻


最後就要將這個編碼文件,實際用到打卡識別的應用之上。同樣的,為了避免肖像權問題,這裡依舊使用 NVIDIA GAN 宣傳視頻中的片段進行實驗,使用者可以很輕鬆地將數據源從視頻文件更改為攝像頭,不管是 CSI 還是 USB。


首先,從 " myId.tkl " 讀入特徵編碼與識別名:

<code>import cv2
import face_recognition as fr
import pickle


 

with

open

(

'myId.pkl'

,

'rb'

)

as

f: idNames=pickle.load(f) idEncodings=pickle.load(f) cap = cv2.VideoCapture(

'./GAN_Video6.mp4'

)

while

True

: isRead, frame = cap.read()

if

not

isRead : break faceLocations = fr.face_locations(frame) faceEncodings = fr.face_encodings(frame,faceLocations)

for

(top,

right

,bottom,

left

), faceOneEncoding

in

zip(faceLocations, faceEncodings):

name

=

'Unknown'

matches = fr.compare_faces(idEncodings,faceOneEncoding)

if

True

in

matches: matchIndex = matches.index(

True

)

name

=idNames[matchIndex] cv2.rectangle(frame,(

left

,top),(

right

,bottom),(

255

,

0

,

0

),

2

) cv2.rectangle(frame, (

left

,top

-30

),(

left

+

120

, top),(

0

,

255

,

255

),

-1

) cv2.putText(frame,

name

,(

left

,top

-10

),

0

,

.75

,(

255

,

0

,

0

),

2

) cv2.imshow(

'mywindow'

, frame)

if

cv2.waitKey(

1

)==

27

: break cap.release() cv2.destroyAllWindows()/<code>


到這裡已經完成整個應用了?其實還沒有,與上一篇最後面出現相同的問題,就是用視頻文件或者攝像頭導入數據後,發現識別性能還是比較慢,原因其實是一樣的,解決方法也是相同的,就是將導入的數據先縮小尺寸後,再進行比對,這樣會節省很多時間。


這裡同樣導入 rate 變量,用來調整身份比對時的圖形大小,然後從數據源(視頻文件或者攝像頭)讀入 frame 之後,用 cv2.resize() 根據 rate 去改變尺寸比例,最後在畫框之前,將座標乘以 rate 以還原大小。


調整完的代碼如下:

<code>import cv2
import face_recognition as fr
import pickle


 

with

open

(

'myId.pkl'

,

'rb'

)

as

f: idNames=pickle.load(f) idEncodings=pickle.load(f) cap = cv2.VideoCapture(

'./GAN_Video6.mp4'

)

while

True

: isRead, frame = cap.read()

if

not

isRead : break faceLocations = fr.face_locations(frame) faceEncodings = fr.face_encodings(frame,faceLocations)

for

(top,

right

,bottom,

left

), faceOneEncoding

in

zip(faceLocations, faceEncodings):

name

=

'Unknown'

matches = fr.compare_faces(idEncodings,faceOneEncoding)

if

True

in

matches: matchIndex = matches.index(

True

)

name

=idNames[matchIndex] cv2.rectangle(frame,(

left

,top),(

right

,bottom),(

255

,

0

,

0

),

2

) cv2.rectangle(frame, (

left

,top

-30

),(

left

+

120

, top),(

0

,

255

,

255

),

-1

) cv2.putText(frame,

name

,(

left

,top

-10

),

0

,

.75

,(

255

,

0

,

0

),

2

) cv2.imshow(

'mywindow'

, frame)

if

cv2.waitKey(

1

)==

27

: break cap.release() cv2.destroyAllWindows()/<code>


試試看調整以後的性能應該有明顯的改善!這裡在 Jetson Nano 2GB 上執行,視頻尺寸為 1024 x 576,在 rate = 4 的時候,能得到大約 8~10 FPS 的性能。


執行效果如下 ( GAN_Face.gif )


Jetson Nano 2GB 系列文章(13):身份識別



GTC 2021 免費註冊現已開放,歡迎所有在 AI 時代乘風破浪的有志之士加入,共同探討當今世界的巨大挑戰,以真知灼見會英雄,以對科技的共同熱愛建立新聯繫!


Jetson Nano 2GB 系列文章(13):身份識別


分享到:


相關文章: