一、主要功能需求
- 做成手持设备
- 可离线进行人脸比对,并检索出最相似的人
-
可录入人脸信息及脸纹(最终在输入信息时还是少不了要用键盘)
二、运行环境
(一)硬件环境
-
树莓派3b
-
7寸触摸屏及含电池的外壳
购买地址:https://item.taobao.com/item.htm?spm=a1z09.2.0.0.23a52e8dFyKSDy&id=536297301483&_u=e1neig4087d
(二)软件环境
- 操作系统--RASPBIAN
- python 3.6
- dlib 19
- OpenCV 3.4
(三)GUI
- python3.6自带的tkinter
三、实现思路
1、使用tkinter作为UI库。
2、UI主程序启动后,主线程负责图形界面刷新和操作响应,所有业务逻辑均以线程的方式实现。这样在进行耗时的功能运算时也不会在界面上停顿。
3、设置一个“模式切换”按钮用于在“识别模式”和“信息录入模式”间进行切换
4、拍照按钮根据当前所处的模式进行“识别”或“信息录入”操作
5、“代表队”和“姓名”在识别模式下用于显示识别出的信息。信息录入模式下用于输入所对应的信息
四、程序开发中的主要技术点说明和体会
(一)用tkinter制作UI
关于tkinter网上一抓一大把,这里说一下使用的体会:
1、python 3 import的时候用:import tkinter,而python 2 import的时候应该用:import Tkinter
2、frame控件用width和height设置大小没卵用,实际大小还得看frame上放置的其他可见控件(如:label、entry、text等)的实际大小。
3、除了主窗口外,其他可见或不可见的空间在定义后要用pack方法,这样才能在界面上出现。假如一个frame没有用pack方法,那么放在这个frame上的所有控件都不可见。
4、mainloop()必不可少。这个方法可以在调用的时候显示调用,也可以在类的init()方法里调用。这个循环的作用就是不断的检测UI上的中断,以及刷新界面。
(二)人脸识别
其实人脸识别是一个笼统的说法,它包括这几个过程:图像采集-->人脸检测-->图像预处理-->特征提取(获取脸纹)-->比对识别。
1、图像采集
本项目用OpenCV,通过摄像头拍摄进行采集。OpenCV用read方法取出的帧是BGR通道的,而dlib只能处理rgb和gray图片,所以OpenCV取出帧之后,一定要用cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)或者cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)进行转换。
2、人脸检测
用dlib的detector(rgbImage, 1)方法就可以检测出图片中的人脸了,这个方法返回矩形特征框的坐标,faces数据结构见下图:
3、图像预处理
shape_predictor = dlib.shape_predictor(predictor_path)定义了一个预处理器,使用这个预处理器把图片中的人脸进行预处理。每调用一次预处理一个人脸:
face_shape = shape_predictor(rgbImage, faces[Index])
4、特征提取(获取脸纹)
model = dlib.face_recognition_model_v1(MODEL_PATH)定义了一个模型提取器,使用这个提取器就能提取“脸纹”特征,每调用一次获取一张图片中的一个脸纹,参数中的face_shape就是上一步预处理所返回的结果
face_fingerprint = model.compute_face_descriptor(rgbImage, face_shape)
5、将特征保存到npy文件
上一步获取到的特征是一个多维向量(可能叫这个名字吧),可以保存到文件中,供后续使用的。保存方法直接看源码:
def saveFaceFingerprintToFile(faceFingerprint, npyFilePath):
vectors = np.array([])
for i, num in enumerate(faceFingerprint):
vectors = np.append(vectors, num)
np.save(npyFilePath, vectors)
if os.path.exists(npyFilePath):
return True
else:
return False
6、特征比对
用欧式向量法比对两个人脸的128维特征,差越小就越“像”。一般小于0.4可以认为是同一个人,当然也可以限定得更严格。这里要说明一下,本项目进行人脸特征比对的时候,用的是遍历法。也就是要与特征库中所有的人脸进行比对,把最“像”的人脸出来。
def featureCompare(self, faceFingerprint_1, faceFingerprint_2):
feature_1 = np.array(faceFingerprint_1)
feature_2 = np.array(faceFingerprint_2)
dist = np.sqrt(np.sum(np.square(feature_1 - feature_2)))
return dist
(三)主要方法/函数的思路
1、APP类的初始化过程
1、初始化窗体,并设置其参数
2、初始化一些全局变量
3、初始化人脸识别器
4、初始化摄像头
5、初始化界面布局
6、调用 start_core_thread 方法启动“视频显示”(执行video_loop方法)和“图片显示”(执行display_pic方法)线程。
7、mainloop()循环
2、界面布局的设计
1、左右各放1个frame,左边的frame用于放置显示视频的控件,右边的frame放置信息显示和操作控件。
2、程序逻辑上,先把一层层的frame布放好,然后再按“从左到右,从上到下”的顺序逐个定义frame上摆放的各个控件。
3、识别模式下“拍摄”按钮的实现逻辑
1、使2个按钮失效
2、生成执行“start_recognizer”方法的线程,由线程去执行识别操作,执行完后恢复按钮。
4、信息录入模式下“拍摄”按钮的实现逻辑:
1、获取输入框内容并去除首位空格
2、判断代表队和姓名两项是否有其中一项为空
3、如果均不为空,则判断根据代表队和姓名所构成的npy文件是否已存在。文件名格式为:“代表队-姓名.npy”,
4、同时生成将要保存的图片文件的文件名,格式为:"代表队-姓名.jpg"。
5、使2个按钮失效后发送信号。
6、生成执行“handle_picture”方法的线程,由线程去执行图片处理操作。
5、人脸识别线程“start_recognizer”的实现逻辑:
1、线程启动后若信号被激活,把当前帧缩小一半后另存为需处理图片
2、BGR转换为RGB格式
3、计算需处理帧中有多少个人脸
4、如果人脸数大于1,调用recognizer类的getMaxFaceFingerprint方法,获得帧里最大人脸的“脸纹”
5、根据获得的“脸纹”,调用searchSimilarPlayer方法遍历比对已载入的人脸脸纹信息。
6、若相似度大于阀值在log控件中显示代表队和姓名,并根据代表队和姓名,把存在目录中的照片换为ImageTk格式的图片,放入图片显示队列。由“图片显示”线程显示出来
7、5秒后,把默认图片放入图片显示队列。由“图片显示”线程显示出来
8、若相似度小于阀值,则在log控件中显示识别失败字样
9、恢复2个按钮为可用。
6、图片处理线程“handle_picture”的实现逻辑:
1、线程启动后若信号被激活,先获取摄像头当前的原始帧,另存为需处理帧,以这个需处理帧作为后续识别处理的基准
2、需处理帧缩小到原始大小的一半
3、因为用CV2捕获的图片是BGR格式的,所以把BGR转换为RGB
4、计算需处理帧中有多少个人脸
5、如果人脸数大于1,调用recognizer类的getMaxFaceFingerprint方法,获得帧里最大人脸的“脸纹”
6、把“脸纹”保持到npy文件,把需处理帧保存为jpg文件
7、把需处理帧转换为ImageTk格式的图片,放入图片显示队列。由“图片显示”线程显示出来
8、5秒后,把默认图片放入图片显示队列。由“图片显示”线程(类初始化的时候已经运行了)显示出来。
7、用label控件显示实时视频“video_loop”的实现逻辑
1、用cv2.cvtColor方法将BGR格式图片转换为RGB格式
2、用Image.fromarray方法转换为PIL适用的格式
3、用ImageTk.PhotoImage方法转换为tkinter适用的格式
4、把tkinter适用的格式让label显示
5、用self.root.after(1, self.video_loop)方法做循环,处理并显示下一帧。
8、用label控件按需显示图片的实现逻辑(display_pic方法)
1、这是一个死循环方法,被调用后会一直运行,知道父线程退出。
2、一开始就判断队列是否为空。这个队列是专门传递imgtk类型图片的。
3、如果不为空,则取出。如果为空(就是没有其他程序往队列里放图片),就进入下一次循环。
4、label的image属性设置为刚取出的imgtk类型的图片
5、然后就显示了
6、再然后就进入下一次循环了
其他函数或方法在需要显示图片的时候,先用put_nowait()方法往队列里面塞你想显示的图片,sleep几秒后,再用put_nowait()方法往队列里塞默认图片。这样的效果就是,没有操作的时候都显示默认图片,只有操作成功了才显示需要显示的图片。
六、源码及说明
- main.py 不用说,主程序了。
- configuration.py 一些全局变量存在这。因为之前写的版本是把信息录入和识别分开成2个程序的,所以把两个程序都用到的参数放在这个文件里。后来合并后,就懒得改这个文件了。一直沿用。
- recognizer.py 定义了名为recognizer的人脸识别器类。把dlib库中人脸识别的一些函数和方法进行了封装,作出了自己的方法。比如获取图片中占地面积最大的脸,计算图片中最大人脸边框的左上角和右下角的位置(一般画框的时候需要用到)、获取rgb图片中最大人脸的“脸纹”等。
- audio_player.py 用于播放wav文件的,调用后可以生成另一个线程播放指定WAV文件。
- source目录 存放了一些外部依赖文件,包括dlib人脸识别模型文件,面部特征点文件,音频文件,默认图片文件
- registered_players目录 存放已登记了信息的人的脸纹和照片的目录,脸纹文件存放在face_description下,照片存放在images下。因为一开始是要做一个给裁判员使用的人脸识别相机,在运动员上场前检录的时候使用的。所以就用了“registered_players”。
- 所有源码都在码云 -- https://gitee.com/eyiyluo_081/iSeeU