NVIDIA Jetson Nano 2GB 系列文章(13):身份识别
本帖最后由 jetson小助理 于 2021-7-6 10:24 编辑在本系列上一篇文章中,给大家介绍了如何使用 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 )。
读者可自行建立要识别的图像库,让公司每个员工或者班上每位同学提供一张清晰的正脸照片,搭建一个脸部特征库即可。
#### **身份识别的三个阶段**
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 编码库进行逐一比对,整个逻辑相当直观。
```
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)
encodeMissB = fr.face_encodings(faceMissB)
encodeMissC = fr.face_encodings(faceMissC)
encodeMrD = fr.face_encodings(faceMrD)
# 建立编码列表与名称
idEncodings=
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'
# compare_faces()返回一个包含”True“与”False”的阵列
matches = fr.compare_faces(idEncodings,testFaceEncoding)
if True in matches: # 如果有找到比对库里的人脸,则找出对应的名字
first_match_index = matches.index(True)
name = idNames
cv2.rectangle(testImage,(left,top),(right,bottom),(255,0,0),2)
cv2.rectangle(testImage,(left,top-30),(left+120, 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()
```
下面是我们在 NVIDIA GAN 宣传视频中,截取三张不同变化的图片作为测试,这些人脸全部都是计算机合成,大家可以与前面的四张图片进行肉眼比对。
这段代码的执行过程,会发现计算过程中,在特征编码( 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)
print('This ID Name is ' + filename)
idNames.append(filename)
# 读取图像,进行特征编码
person = cv2.imread(fullPath)
encoding=fr.face_encodings(person)
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 ",在其他代码中直接读入使用,不需要再重复执行高计算量的编码作业。
#### **使用特征编码文件识别视频**
最后就要将这个编码文件,实际用到打卡识别的应用之上。同样的,为了避免肖像权问题,这里依旧使用 NVIDIA GAN 宣传视频中的片段进行实验,使用者可以很轻松地将数据源从视频文件更改为摄像头,不管是 CSI 还是 USB。
首先,从 " myId.tkl " 读入特征编码与识别名:
```
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
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()
```
到这里已经完成整个应用了?其实还没有,与上一篇最后面出现相同的问题,就是用视频文件或者摄像头导入数据后,发现识别性能还是比较慢,原因其实是一样的,解决方法也是相同的,就是将导入的数据先缩小尺寸后,再进行比对,这样会节省很多时间。
这里同样导入 rate 变量,用来调整身份比对时的图形大小,然后从数据源(视频文件或者摄像头)读入 frame 之后,用 cv2.resize() 根据 rate 去改变尺寸比例,最后在画框之前,将坐标乘以 rate 以还原大小。
调整完的代码如下:
```
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
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()
```
试试看调整以后的性能应该有明显的改善!这里在 Jetson Nano 2GB 上执行,视频尺寸为 1024 x 576,在 rate = 4 的时候,能得到大约 8~10 FPS 的性能。
执行效果如下 ( GAN_Face.gif )
厉害
页:
[1]