实验环境:python3。6opencvpython3。4。14。51建议使用anaconda配置相同环境背景人脸识别步骤 图1:人脸识别流程图人脸采集 采集人脸图片的方法多种多样,可以直接从网上下载数据集,可以从视频中提取图片,还可以从摄像头实时的采集图片。人脸检测方法 人脸检测在实际中主要用于人脸识别的预处理,即在图像中准确标定出人脸的位置和大小。人脸图像中包含的模式特征十分丰富,如直方图特征、颜色特征、模板特征、结构特征及Haar特征等。人脸检测就是把这其中有用的信息挑出来,并利用这些特征实现人脸检测。人脸图像预处理 对于人脸的图像预处理是基于人脸检测结果,对图像进行处理并最终服务于特征提取的过程。系统获取的原始图像由于受到各种条件的限制和随机干扰,往往不能直接使用,必须在图像处理的早期阶段对它进行灰度校正、噪声过滤等图像预处理。对于人脸图像而言,其预处理过程主要包括人脸图像的光线补偿、灰度变换、直方图均衡化、归一化、几何校正、滤波以及锐化等。人脸特征提取 人脸识别系统可使用的特征通常分为视觉特征、像素统计特征、人脸图像变换系数特征、人脸图像代数特征等。人脸特征提取就是针对人脸的某些特征进行的。人脸特征提取,也称人脸表征,它是对人脸进行特征建模的过程。人脸特征提取的方法归纳起来分为两大类:一种是基于知识的表征方法;另外一种是基于代数特征或统计学习的表征方法。匹配与识别 提取的人脸图像的特征数据与数据库中存储的特征模板进行搜索匹配,通过设定一个阈值,当相似度超过这一阈值,则把匹配得到的结果输出。人脸识别就是将待识别的人脸特征与已得到的人脸特征模板进行比较,根据相似程度对人脸的身份信息进行判断。这一过程又分为两类:一类是确认,是一对一进行图像比较的过程,另一类是辨认,是一对多进行图像匹配对比的过程。关于OpenCv Opencv是一个开源的的跨平台计算机视觉库,内部实现了图像处理和计算机视觉方面的很多通用算法,对于python而言,在引用opencv库的时候需要写为importcv2。其中,cv2是opencv的C命名空间名称,使用它来表示调用的是C开发的opencv的接口 目前人脸识别有很多较为成熟的方法,这里调用OpenCv库,而OpenCV又提供了三种人脸识别方法,分别是LBPH方法、EigenFishfaces方法、Fisherfaces方法。本文采用的是LBPH(LocalBinaryPatternsHistogram,局部二值模式直方图)方法。在OpenCV中,可以用函数cv2。face。LBPHFaceRecognizercreate()生成LBPH识别器实例模型,然后应用cv2。faceFaceRecognizer。train()函数完成训练,最后用cv2。faceFaceRecognizer。predict()函数完成人脸识别。 CascadeClassifier,是Opencv中做人脸检测的时候的一个级联分类器。并且既可以使用Haar,也可以使用LBP特征。其中Haar特征是一种反映图像的灰度变化的,像素分模块求差值的一种特征。它分为三类:边缘特征、线性特征、中心特征和对角线特征。程序设计人脸识别算法: 图2:人脸识别模块图1。准备工作 图3:准备阶段 首先读取config文件,文件中第一行代表当前已经储存的人名个数,接下来每一行是二元组(id,name)即标签和对应的人名读取结果存到以下两个全局变量中。iddict{}字典里存的是idname键值对Totalfacenum999已经被识别有用户名的人脸个数,复制代码 definit():将config文件内的信息读入到字典中 加载人脸检测分类器Haar,并准备好识别方法LBPH方法加载OpenCV人脸检测分类器Haarfacecascadecv2。CascadeClassifier(haarcascadefrontalfacedefault。xml)准备好识别方法LBPH方法recognizercv2。face。LBPHFaceRecognizercreate()复制代码 然后打开标号为0的摄像头cameracv2。VideoCapture(0)摄像头success,imgcamera。read()从摄像头读取照片复制代码2。录入新面容 图4:录入人脸2。1采集面容 创建文件夹data用于储存本次从摄像头采集到的照片,每次调用前先清空这个目录。 然后是一个循环,循环次数为需要采集的样本数,摄像头拍摄取样的数量,越多效果越好,但获取以及训练的越慢。 循环内调用camera。read()返回值赋给全局变量success,和img用于在GUI中实时显示。 然后调用cv2。cvtColor(img,cv2。COLORBGR2GRAY)用于将采集到的图片转为灰度图片减少计算量。 然后利用加载好的人脸分类器将每一帧摄像头记录的数据带入OpenCv中,让Classifier判断人脸。其中gray为要检测的灰度图像,1。3为每次图像尺寸减小的比例,5为minNeighborsfacesfacecascade。detectMultiScale(gray,1。3,5)复制代码 faces为在img图像中检测到的人脸,然后利用cv2。rectangle在人脸一圈画个矩形。并把含有人脸的区域储存进入data文件夹注意这里写入时,每个图片的标签时Totalfacenum即当前共有多少个可识别用户(在录入之前加一),亦即当前用户的编号cv2。rectangle(img,(x,y),(xw,yw),(255,0,0))cv2。imwrite(。dataUser。str(T)。str(samplenum)。jpg,gray〔y:yh,x:xw〕)复制代码 然后在循环末尾最后打印一个进度条,用于提示采集图像的进度主要原理就是每次输出不换行并且将光标移动到当前行的开头,输出内容根据进度不断变化即可,同时在控件的提示框也输出进度信息print(r{:。1f}。format(samplenumpicturnum100)lr,end)var。set({:。1f}。format(samplenumpicturnum100))控件可视化进度信息window。update()刷新控件以实时显示进度复制代码2。2训练识别器 读取data文件夹,读取照片内的信息,得到两个数组,一个faces存的是所有脸部信息、一个ids存的是faces内每一个脸部对应的标签,然后将这两个数组传给recog。train用于训练训练模型将输入的所有图片转成四维数组recog。train(faces,np。array(ids))复制代码 训练完毕后保存训练得到的识别器到。yml文件中,文件名为人脸编号。ymlrecog。save(str(Totalfacenum)。yml)复制代码2。3修改配置文件 每一次训练结束都要修改配置文件,具体要修改的地方是第一行和最后一行。第一行有一个整数代表当前系统已经录入的人脸的总数,每次修改都加一。这里修改文件的方式是先读入内存,然后修改内存中的数据,最后写回文件。fopen(config。txt,r)flistf。readlines()flist〔0〕str(int(flist〔0〕)1)f。close()fopen(config。txt,w)f。writelines(flist)f。close()复制代码 还要在最后一行加入一个二元组用以标识用户。格式为:标签空格用户名空格,用户名默认为Userx(其中x标识用户编号)f。write(str(T)Userstr(T))复制代码3。人脸识别(刷脸) 图5:刷脸流程图 由于这里采用多个。yml文件来储存识别器(实际操作时储存在一个文件中识别出错所以采用这种方式),所以在识别时需要遍历所有的。yml文件,如果每一个都不能识别才得出无法识别的结果,相反只要有一个可以识别当前对象就返回可以识别的结果。而对于每一个文件都识别十次人脸,若成功五次以上则表示最终结果为可以识别,否则表示当前文件无法识别这个人脸。 识别过程中在GUI的控件中实时显示拍摄到的内容,并在人脸周围画一个矩形框,并根据识别器返回的结果实时显示在矩形框附近。idnum,confidencerecognizer。predict(gray〔y:yh,x:xw〕)加载一个字体用于输出识别对象的信息fontcv2。FONTHERSHEYSIMPLEX输出检验结果以及用户名cv2。putText(img,str(username),(x5,y5),font,1,(0,0,255),1)cv2。putText(img,str(confidence),(x5,yh5),font,1,(0,0,0),1)复制代码多线程: 程序的两个功能之间可以独立运行,就需要采用多线程的方法,但当遇到临界资源的使用时,多个进程线程之间就要互斥的访问以免出错,本程序中具体的设计方法:本程序采用多线程的方法实现并行。程序的三个按钮对应着三个功能,分别是录入人脸、人脸检测、退出程序。由于程序中的用户界面是利用python中的tkinter库做的,其按钮的响应函数用command指出,所以这里在每个command跳转到的函数中设置多线程,每敲击一次就用threading。Thread创建一个新的线程,然后在新的线程的处理函数target中实现按钮原本对应的功能。pthreading。Thread(targetfscanfacethread)复制代码 在涉及到摄像头的访问时,线程之间需要互斥的访问,所以设置了一个全局的变量systemstatelock来表示当前系统的状态,用以实现带有优先级的互斥锁的功能。锁状态为0表示摄像头未被使用,1表示正在刷脸,2表示正在录入新面容。程序在实际执行的过程中如果状态为0,则无论是刷脸还是录入都能顺利执行,如果状态为1表示正在刷脸,如果此时敲击刷脸按钮则,系统会提示正在刷脸并拒绝新的请求,如果此时敲击录入面容按钮,由于录入面容优先级比刷脸高,所以原刷脸线程会被阻塞,globalsystemstatelockwhilesystemstatelock2:如果正在录入新面孔就阻塞pass复制代码 新的录入面容进程开始执行并修改系统状态为2,录入完成后状态变为原状态,被阻塞的刷脸进程继续执行,录入人脸线程刚执行完录入阶段现在正在训练,此时有两个线程并行,以此来保证训练数据的同时不影响系统的使用。 对于退出的功能,直接在函数内调用exit(),但是python的线程会默认等待子线程全部结束再退出,所以用p。setDaemon(True)将线程设置为守护线程,这样在主线程退出之后其它线程也都退出从而实现退出整个程序的功能。GUI设计: 程序采用python中的tkinter库做可视化,优点是占用资源小、轻量化、方便。首先创建一个窗口命名为window然后设置其大小和标题等属性。然后在界面上设定一个绿底的标签,类似于一个提示窗口的作用然后分别创建三个按钮,并设置响应函数和提示字符,放置在window内部。然后设置一个label类型的控件用于动态的展示摄像头的内容(将摄像头显示嵌入到控件中)。具体方法:创建videoloop()函数,在函数内访问全局的变量img,img是从摄像头读取到的图像数据。然后把img显示在label内。使用window。after方法,在给定时间后调用函数一次,实现固定时间刷新控件,从而达到实时显示摄像头画面在GUI中的效果。window。after(1,videoloop)这句的意思是一秒以后执行videoloop函数因为这一句是写在videoloop函数中的所以每过一秒函数执行一次。复制代码运行测试说明 测试环境:python3。6opencvpython3。4。14。51需要的包: 图6:需要的包录入人脸 从数据集录入 从摄像头录入 人脸识别 代码实现:实验环境:python3。6opencvpython3。4。14。51importcv2importnumpyasnpimportosimportshutilimportthreadingimporttkinterastkfromPILimportImage,ImageTk首先读取config文件,第一行代表当前已经储存的人名个数,接下来每一行是(id,name)标签和对应的人名iddict{}字典里存的是idname键值对Totalfacenum999已经被识别有用户名的人脸个数,definit():将config文件内的信息读入到字典中fopen(config。txt)globalTotalfacenumTotalfacenumint(f。readline())foriinrange(int(Totalfacenum)):linef。readline()idnameline。split()iddict〔int(idname〔0〕)〕idname〔1〕f。close()init()加载OpenCV人脸检测分类器Haarfacecascadecv2。CascadeClassifier(haarcascadefrontalfacedefault。xml)准备好识别方法LBPH方法recognizercv2。face。LBPHFaceRecognizercreate()打开标号为0的摄像头cameracv2。VideoCapture(0)摄像头success,imgcamera。read()从摄像头读取照片Wsize0。1camera。get(3)Hsize0。1camera。get(4)systemstatelock0标志系统状态的量0表示无子线程在运行1表示正在刷脸2表示正在录入新面孔。相当于mutex锁,用于线程同步以上是初始化defGetnewface():print(正在从摄像头录入新人脸信息)存在目录data就清空,不存在就创建,确保最后存在空的data目录filepathdataifnotos。path。exists(filepath):os。mkdir(filepath)else:shutil。rmtree(filepath)os。mkdir(filepath)samplenum0已经获得的样本数whileTrue:从摄像头读取图片globalsuccessglobalimg因为要显示在可视化的控件内,所以要用全局的success,imgcamera。read()转为灰度图片ifsuccessisTrue:graycv2。cvtColor(img,cv2。COLORBGR2GRAY)else:break检测人脸,将每一帧摄像头记录的数据带入OpenCv中,让Classifier判断人脸其中gray为要检测的灰度图像,1。3为每次图像尺寸减小的比例,5为minNeighborsfacedetectorfacecascadefacesfacedetector。detectMultiScale(gray,1。3,5)框选人脸,for循环保证一个能检测的实时动态视频流for(x,y,w,h)infaces:xy为左上角的坐标,w为宽,h为高,用rectangle为人脸标记画框cv2。rectangle(img,(x,y),(xw,yw),(255,0,0))样本数加1samplenum1保存图像,把灰度图片看成二维数组来检测人脸区域,这里是保存在data缓冲文件夹内TTotalfacenumcv2。imwrite(。dataUser。str(T)。str(samplenum)。jpg,gray〔y:yh,x:xw〕)picturnum30表示摄像头拍摄取样的数量,越多效果越好,但获取以及训练的越慢cv2。waitKey(1)ifsamplenumpicturnum:breakelse:控制台内输出进度条lint(samplenumpicturnum50)rint((picturnumsamplenum)picturnum50)print(r{:。1f}。format(samplenumpicturnum100)lr,end)var。set({:。1f}。format(samplenumpicturnum100))控件可视化进度信息tk。Tk()。update()window。update()刷新控件以实时显示进度defTrainnewface():print(正在训练)cv2。destroyAllWindows()pathdata初始化识别的方法recogcv2。face。LBPHFaceRecognizercreate()调用函数并将数据喂给识别器训练faces,idsgetimagesandlabels(path)print(本次用于训练的识别码为:)调试信息print(ids)输出识别码训练模型将输入的所有图片转成四维数组recog。train(faces,np。array(ids))保存模型ymlstr(Totalfacenum)。ymlrecfopen(yml,w)recf。close()recog。save(yml)recog。save(aaa。yml)创建一个函数,用于从数据集文件夹中获取训练图片,并获取id注意图片的命名格式为User。id。sampleNumdefgetimagesandlabels(path):imagepaths〔os。path。join(path,f)forfinos。listdir(path)〕新建连个list用于存放facesamples〔〕ids〔〕遍历图片路径,导入图片和id添加到list中forimagepathinimagepaths:通过图片路径将其转换为灰度图片imgImage。open(imagepath)。convert(L)将图片转化为数组imgnpnp。array(img,uint8)ifos。path。split(imagepath)〔1〕。split(。)〔1〕!jpg:continue为了获取id,将图片和路径分裂并获取idint(os。path。split(imagepath)〔1〕。split(。)〔1〕)调用熟悉的人脸分类器detectorcv2。CascadeClassifier(haarcascadefrontalfacedefault。xml)facesdetector。detectMultiScale(imgnp)将获取的图片和id添加到list中for(x,y,w,h)infaces:facesamples。append(imgnp〔y:yh,x:xw〕)ids。append(id)returnfacesamples,idsdefwriteconfig():print(新人脸训练结束)fopen(config。txt,a)TTotalfacenumf。write(str(T)Userstr(T))f。close()iddict〔T〕Userstr(T)这里修改文件的方式是先读入内存,然后修改内存中的数据,最后写回文件fopen(config。txt,r)flistf。readlines()flist〔0〕str(int(flist〔0〕)1)f。close()fopen(config。txt,w)f。writelines(flist)f。close()以上是录入新人脸信息功能的实现defscanface():使用之前训练好的模型foriinrange(Totalfacenum):每个识别器都要用i1ymlstr(i)。ymlprint(本次:yml)调试信息recognizer。read(yml)aveposs0fortimesinrange(10):每个识别器扫描十遍times1curposs0globalsuccessglobalimgglobalsystemstatelockwhilesystemstatelock2:如果正在录入新面孔就阻塞print(r刷脸被录入面容阻塞,end)passsuccess,imgcamera。read()graycv2。cvtColor(img,cv2。COLORBGR2GRAY)识别人脸facesfacecascade。detectMultiScale(gray,scaleFactor1。2,minNeighbors5,minSize(int(Wsize),int(Hsize)))进行校验for(x,y,w,h)infaces:globalsystemstatelockwhilesystemstatelock2:如果正在录入新面孔就阻塞print(r刷脸被录入面容阻塞,end)pass这里调用Cv2中的rectangle函数在人脸周围画一个矩形cv2。rectangle(img,(x,y),(xw,yh),(0,255,0),2)调用分类器的预测函数,接收返回值标签和置信度idnum,confidencerecognizer。predict(gray〔y:yh,x:xw〕)confconfidence计算出一个检验结果ifconfidence100:可以识别出已经训练的对象直接输出姓名在屏幕上ifidnuminiddict:usernameiddict〔idnum〕else:print(无法识别的ID:{}。format(idnum),end)usernameUntaggeduser:str(idnum)confidence{0},format(round(100confidence))else:无法识别此对象,那么就开始训练usernameunknownprint(检测到陌生人脸)cv2。destroyAllWindows()globalTotalfacenumTotalfacenum1Getnewface()采集新人脸Trainnewface()训练采集到的新人脸writeconfig()修改配置文件recognizer。read(aaa。yml)读取新识别器加载一个字体用于输出识别对象的信息fontcv2。FONTHERSHEYSIMPLEX输出检验结果以及用户名cv2。putText(img,str(username),(x5,y5),font,1,(0,0,255),1)cv2。putText(img,str(confidence),(x5,yh5),font,1,(0,0,0),1)展示结果cv2。imshow(camera,img)print(confstr(conf),end)if15conf0:curposs1表示可以识别elif60conf35:curposs1表示可以识别else:curposs0表示不可以识别kcv2。waitKey(1)ifk27:cam。release()释放资源cv2。destroyAllWindows()breakaveposscurpossifaveposs5:有一半以上识别说明可行则返回returnireturn0全部过一遍还没识别出说明无法识别以上是关于刷脸功能的设计deffscanfacethread():使用之前训练好的模型recognizer。read(aaa。yml)var。set(刷脸)ansscanface()ifans0:print(最终结果:无法识别)var。set(最终结果:无法识别)else:ansname最终结果:str(ans)iddict〔ans〕print(ansname)var。set(ansname)globalsystemstatelockprint(锁被释放0)systemstatelock0修改systemstatelock,释放资源deffscanface():globalsystemstatelockprint(当前锁的值为:str(systemstatelock))ifsystemstatelock1:print(阻塞,因为正在刷脸)return0elifsystemstatelock2:如果正在录入新面孔就阻塞print(刷脸被录入面容阻塞)return0systemstatelock1pthreading。Thread(targetfscanfacethread)p。setDaemon(True)把线程P设置为守护线程若主线程退出P也跟着退出p。start()deffrecfacethread():var。set(录入)cv2。destroyAllWindows()globalTotalfacenumTotalfacenum1Getnewface()采集新人脸print(采集完毕,开始训练)globalsystemstatelock采集完就可以解开锁print(锁被释放0)systemstatelock0Trainnewface()训练采集到的新人脸writeconfig()修改配置文件recognizer。read(aaa。yml)读取新识别器globalsystemstatelockprint(锁被释放0)systemstatelock0修改systemstatelock,释放资源deffrecface():globalsystemstatelockprint(当前锁的值为:str(systemstatelock))ifsystemstatelock2:print(阻塞,因为正在录入面容)return0else:systemstatelock2修改systemstatelockprint(改为2,end)print(当前锁的值为:str(systemstatelock))pthreading。Thread(targetfrecfacethread)p。setDaemon(True)把线程P设置为守护线程若主线程退出P也跟着退出p。start()tk。Tk()。update()systemstatelock0修改systemstatelock,释放资源deffexit():退出按钮exit()以上是关于多线程的设计windowtk。Tk()window。title(CheneyFacerec3。0)窗口标题window。geometry(1000x500)这里的乘是小x在图形界面上设定标签,类似于一个提示窗口的作用vartk。StringVar()ltk。Label(window,textvariablevar,bggreen,fgwhite,font(Arial,12),width50,height4)说明:bg为背景,fg为字体颜色,font为字体,width为长,height为高,这里的长和高是字符的长和高,比如height2,就是标签有2个字符这么高l。pack()放置l控件在窗口界面设置放置Button按键并绑定处理函数buttonatk。Button(window,text开始刷脸,font(Arial,12),width10,height2,commandfscanface)buttona。place(x800,y120)buttonbtk。Button(window,text录入人脸,font(Arial,12),width10,height2,commandfrecface)buttonb。place(x800,y220)buttonbtk。Button(window,text退出,font(Arial,12),width10,height2,commandfexit)buttonb。place(x800,y320)paneltk。Label(window,width500,height350)摄像头模块大小panel。place(x10,y100)摄像头模块的位置window。config(cursorarrow)defvideoloop():用于在label内动态展示摄像头内容(摄像头嵌入控件)success,imgcamera。read()从摄像头读取照片globalsuccessglobalimgifsuccess:cv2。waitKey(1)cv2imagecv2。cvtColor(img,cv2。COLORBGR2RGBA)转换颜色从BGR到RGBAcurrentimageImage。fromarray(cv2image)将图像转换成Image对象imgtkImageTk。PhotoImage(imagecurrentimage)panel。imgtkimgtkpanel。config(imageimgtk)window。after(1,videoloop)videoloop()窗口循环,用于显示window。mainloop()以上是关于界面的设计复制代码