基于树莓派3b的离线人脸识别相机-i See U

一、主要功能需求

  • 做成手持设备
  • 可离线进行人脸比对,并检索出最相似的人
  • 可录入人脸信息及脸纹(最终在输入信息时还是少不了要用键盘)


    识别模式

    信息录入模式

二、运行环境

(一)硬件环境

(二)软件环境

  • 操作系统--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数据结构见下图:


image.png

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348