“ 本项目是一个基于OpenCV开源库使用python语言程序实现人脸检测的项目,该项目将从【项目基础知识】(即人脸识别的基本原理)、【项目实践】(人脸识别所需要的具体步骤及其python程序的代码实现,包括人脸的检测,数据的采集、存储、训练、识别)两个部分进行讲解,在讲解过程中同时还提供了很多扩展知识。
作者丨徐恩伟01
—
【项目简介】
本项目是一个基于OpenCV开源库使用python语言程序实现人脸检测的项目,该项目将从【项目基础知识】(即人脸识别的基本原理)、【项目实践擦欧总】(人脸识别所需要的具体步骤及其python程序的代码实现,包括人脸的检测,数据的采集、存储、训练、识别)两个部分进行讲解,在讲解过程中同时还提供了很多扩展知识。
本项目使用python+ OpenCV 完成,OpenCV是一个神奇的「开源计算机视觉库」,具备很强的计算效率,且专门用于实时应用,因此,它非常适合使用摄像头的实时人脸识别。
02
—
【项目基础知识】
(1)python 3 (python主程序)
(2)OpenCV库(开源的计算机视觉库)
Haar是哈尔特征(Haar-like features)的简称,是一种用于物体识别的一种数字图像特征。它们因为与哈尔小波转换极为相似而得名,是第一种即时的人脸检测运算。历史上,直接使用图像的强度(就是图像每一个像素点的RGB值)使得特征的计算强度很大。帕帕乔治奥等人提出可以使用基于哈尔小波的特征而不是图像强度,维奥拉和琼斯进而提出了哈尔特征。哈尔特征使用检测窗口中指定位置的相邻矩形,计算每一个矩形的像素和并取其差值,然后用这些差值来对图像的子区域进行分类。
Haar特征包含三种:边缘特征、线性特征、中心特征和对角线特征。每种分类器都从图片中提取出对应的特征。
比如上图中,横的黑道将人脸中较暗的双眼提取了出来,而竖的白道将人脸中较亮的鼻梁提取了出来。
如果能找到一个简单的方法能够检测某个窗口是不是人脸区域,如果该窗口不是人脸区域,那么就只看一眼便直接跳过,也就不用进行后续处理了,这样就能集中精力判别那些可能是人脸的区域。为此,有人引入了Cascade 分类器。它不是将6000个特征都用在一个窗口,而是将特征分为不同的阶段,然后一个阶段一个阶段的应用这些特征(通常情况下,前几个阶段只有很少量的特征)。如果窗口在第一个阶段就检测失败了,那么就直接舍弃它,无需考虑剩下的特征。如果检测通过,则考虑第二阶段的特征并继续处理。如果所有阶段的都通过了,那么这个窗口就是人脸区域。作者的检测器将6000+的特征分为了38个阶段,前五个阶段分别有1,10,25,25,50个特征(前文图中提到的识别眼睛和鼻梁的两个特征实际上是Adaboost中得到的最好的两个特征)。根据作者所述,平均每个子窗口只需要使用6000+个特征中的10个左右。
基于Haar特征的cascade级联分类器是Paul Viola和 Michael Jone在2001年的论文”Rapid Object Detection using a Boosted Cascade of Simple Features”中提出的一种有效的物体检测方法。这种机器学习方法基于大量正面、负面图像训练级联函数,然后用于检测其他图像中的对象。这里,我们将用它进行人脸识别。
详情参见:Cascade Classifier Training
在OpenCV 中包含很多预训练分类器,即提供了可以直接用于检测人脸、眼睛、笑容等的相关算法,以供开发者调用,相关的 XML 文件可从该目录下载:haarcascades
级联分类器的函数是通过大量带人脸
和不带人脸
的图片通过机器学习得到的。对于人脸识别来说,需要几万个特征,通过机器学习找出人脸分类效果最好、错误率最小的特征。训练开始时,所有训练集中的图片具有相同的权重,对于被分类错误的图片,提升权重,重新计算出新的错误率和新的权重。直到错误率或迭代次数达到要求。这种方法叫做Adaboost
。
在Opencv中可以直接调用级联分类器函数。
在opencv中通过CascadeClassifier检测模块(算法模块)能够实现基于Haar特征的人脸或人眼等其他部位Cascade级联分类器,这些算法被封装成xml文件进行存放,其中包含相应的检测特征值,特征size大小根据训练时的参数而定,检测的时候可以简单理解为就是将每个固定size特征(检测窗口)与输入图像的同样大小区域比较,如果匹配那么就记录这个矩形区域的位置,然后滑动窗口,检测图像的另一个区域,重复操作。由于输入的图像中特征大小不定,比如在输入图像中眼睛是50x50的区域,而训练时的是25x25,那么只有当输入图像缩小到一半的时候,才能匹配上,所以这里还有一个逐步缩小图像,也就是制作图像金字塔的流程.
由于人脸可能出现在图像的任何位置,在检测时用固定大小的窗口对图像从上到下、从左到右扫描,判断窗口里的子图像是否为人脸,这称为滑动窗口技术(sliding window)。为了检测不同大小的人脸,还需要对图像进行放大或者缩小构造图像金字塔,对每张缩放后的图像都用上面的方法进行扫描。
以512x512大小的图像为例,假设分类器窗口为24x24,滑动窗口的步长为1,则总共需要扫描的窗口数为:
即要检测一张图片需要扫描大于120万个窗口,这里我们不得不借助计算机编程的程序来实现。
CascadeClassifier.detectMultiScale的调用方法为如下,只需要我们掌握一些调用的参数意义即可。
class CascadeClassifier: detectMultiScale( const Mat& image, vector& objects,double scaleFactor, int minNeighbors,int flags, Size minObjectSize, Size maxObjectSize) {
vector fakeLevels; vector fakeWeights; detectMultiScale( image, objects, fakeLevels, fakeWeights, scaleFactor, minNeighbors, flags, minObjectSize, maxObjectSize, false ); }
参数意思:\1. const Mat& image:输入图像\2. vector& objects:输出的矩形向量组\3. double scaleFactor=1.1:这个是每次缩小图像的比例,默认是1.1\4. minNeighbors=3:匹配成功所需要的周围矩形框的数目,每一个特征匹配到的区域都是一个矩形框,只有多个矩形框同时存在的时候,才认为是匹配成功,比如人脸,这个默认值是3。\5. flags=0:可以取如下这些值: CASCADE_DO_CANNY_PRUNING=1, 利用canny边缘检测来排除一些边缘很少或者很多的图像区域 CASCADE_SCALE_IMAGE=2, 正常比例检测 CASCADE_FIND_BIGGEST_OBJECT=4, 只检测最大的物体 CASCADE_DO_ROUGH_SEARCH=8 初略的检测\6. minObjectSize maxObjectSize:匹配物体的大小范围
如果我们已经检测出了图片中的人脸,那么如何鉴别人脸呢?
为了实现对人脸的识别我们将使用一种面部特征点估计(face landmark estimation)的算法。其实还有很多算法都可以做到,但我们这次使用的是由瓦希德·卡奇米(Vahid Kazemi)和约瑟菲娜·沙利文(Josephine Sullivan)在 2014 年发明的方法。这一算法的基本思路是找到68个人脸上普遍存在的点(称为特征点, landmark)。
下巴轮廓17个点 [0-16]
左眉毛5个点 [17-21]
右眉毛5个点 [22-26]
鼻梁4个点 [27-30]
鼻尖5个点 [31-35]
左眼6个点 [36-41]
右眼6个点 [42-47]
外嘴唇12个点 [48-59]
内嘴唇8个点 [60-67]
有了这68个点,我们就可以轻松的将人脸进行准确的识别,但是如何确定不同人的脸之间有什么不同呢?
最简单的方法就是把我们第二步中检测的未知人脸与我们已知的人脸作对比,当我们发现未知的面孔与一个以前标注过的面孔看起来相似的时候,就可以认定他们是同一个人。
我们人类能通过眼睛大小,头发颜色等等信息轻松的分辨不同的两张人脸,可是电脑怎么分辨呢?我们得量化它们,让计算机通过不同的人脸特征数值进行自动识别。实际上最准确的方法是让计算机自己找出他要收集的测量值,深度学习比人类更懂得哪些面部测量值比较重要。所以,解决方案是训练一个深度卷积神经网络,训练让它为脸部生成128个测量值。
每次训练要观察三个不同的脸部图像:
加载一张已知的人的面部训练图像
加载同一个人的另一张照片
加载另外一个人的照片
然后,算法查看它自己为这三个图片生成的测量值。再然后,稍微调整神经网络,以确保第一张和第二张生成的测量值接近,而第二张和第三张生成的测量值略有不同。我们要不断的调整样本,重复以上步骤百万次,这确实是个巨大的挑战,但是一旦训练完成,它能攻轻松的找出人脸。
庆幸的是 OpenFace 上面的大神已经做完了这些,并且他们发布了几个训练过可以直接使用的网络,我们可以不用部署复杂的机器学习,开箱即用,直接在python中调用即可,感谢开源精神。
这128个测量值是什么鬼?其实我们不用关心,这对我们也不重要。我们关心的是,当看到同一个人的两张不同照片时,我们的网络需要能得到几乎相同的数值。
如果对上述的原理看不懂也没有关系,因为基于上述原理实现的操作代码别人已经写好了,我们在python中直接调用即可而不必过于关注其实现过程,所以可以直接进行下述的编程操作,在编写代码的过程中进行理解人脸识别的逻辑关系。
03
—
【项目实践】
以下所有步骤我均在Windows 电脑上进行了测试、运行很好。要创建完整的人脸识别项目,在安装好必要的环境与扩展库后,人脸的识别主要包括人脸的检测、人脸数据的收集并存储、对人脸数据的训练模型、人脸识别对比四个阶段。
本文采用pip进行安装,因此需要成功配置好python的安装环境。
(1)opencv-python 的安装,输入:pip install opencv-python。
注:安装openCV,不是pip install OpenCv,而是 pip install OpenCv-python
numpy与OpenCV绑定安装,无需自己输入命令。
(2) pillow的安装,输入:pip install pillow
注:pillow为图像处理包。
(3) contrib的安装,输入:pip instal opencv-contrib-python
注:contrib是用于训练自己的人脸模型的一个OpenCV扩展包
下图为三个包的安装过程截图,由于我之前已经安装过,会显示包已存在。首次安装会有进度条,成功后会有Successfully字样。
人脸识别的最基础任务是人脸检测,你必须首先判断是否有人脸才能在未来与捕捉到的新人脸对比时进而识别它。
下面,我们就开始用 OpenCV 创建人脸检测器吧!
"""功能:主要用于检测是否是人脸,注意是检测人脸而不是识别人脸版本:1.0"""import cv2def main(): # 导入人脸级联分类器引擎,'.xml'文件里包含训练出来的人脸特征 faceCascade = cv2.CascadeClassifier("enginer\haarcascade_frontalface_default.xml") # 开启摄像头,获取图像 cap = cv2.VideoCapture(0) #增加循环,使得摄像头获取的画面为实时画面而非静态的图像 while True: # 读取摄像头中的图像,ok为是否读取成功的判断参数,True为成功读取,否则为False sucess, img = cap.read() if sucess is True: # 如果摄像头成功读取图像,则将图像转换成灰度图像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) else: print("摄像头没有读取到任何图像") break # 用人脸级联分类器引擎进行人脸识别,返回的faces为人脸坐标列表(x,y,w,h) faces = faceCascade.detectMultiScale( gray, scaleFactor=1.2, minNeighbors=5, minSize=(32, 32) ) # 对每一张人脸都做画矩形标出 for (x, y, w, h) in faces: # 画出人脸框,蓝色(BGR色彩体系),画笔宽度为2 cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2) cv2.imshow("Video", img) k = cv2.waitKey(30) # cv2.waitKey(delay) delay 毫秒是屏幕刷新的时间 if k == 27: # press 'ESC' to quit 在ASCII码中esc按键为27,因此27对应的就是ESC退出键 break cap.release() cv2.destroyAllWindows()if __name__ == '__main__': main()
这行代码可以加载「分类器」(在C盘的python安装目录下,也可以将其拷贝到项目中直接调用)。然后,我们在在循环内部调用摄像头,并以 grayscale 模式加载我们的输入视频。现在,我们必须调用分类器函数,向其输入一些非常重要的参数,如比例因子、邻近数和人脸检测的最小尺寸。
faces = faceCascade.detectMultiScale( gray, scaleFactor=1.2, minNeighbors=5, minSize=(32, 32) )
其中:
gray 表示输入 grayscale 图像。
scaleFactor 表示每个图像缩减的比例大小。
minNeighbors 表示每个备选矩形框具备的邻近数量。数字越大,假正类越少。
minSize 表示人脸识别的最小矩形大小。
该函数将检测图像中的人脸。接下来,我们必须「标记」图像中的人脸,比如,用蓝色矩形。使用下列代码完成这一步:
for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
如果已经标记好人脸,则函数将检测到的人脸的位置返回为一个矩形,左上角 (x,y),w 表示宽度,h 表示高度 ==> (x,y,w,h)。详见下图。
得到这些位置信息后,我们可以为人脸创建一个「感兴趣区域」(绘制矩形),用 imshow() 函数呈现结果。
现在,我们需要创建一个简单的数据集,该数据集将储存每张人脸的 ID 和一组用于人脸检测的灰度图。
开始之前,需要做以下准备:
1.在运行该程序前,请先创建一个Facedata文件夹并和你的程序放在同一个根目录下。
2.程序运行时间可能会比较长,可能会有几分钟,如果嫌长,可以将 #得到100个样本后退出摄像 这个注释前的100,改为10。
3.如果觉得收集的实践比较长,实在等不及,可按esc退出,但可能会导致数据不够模型精度下降。
4.可以尝试变换不同的人脸姿势,如侧脸、笑脸等等,增强输入人脸信息的准确性
"""功能:收集人脸的数据信息,存储知道文件夹中版本1.0"""import cv2import json#定义函数,存储用户的信息,并将其以json的格式写入user_info.txtdef store_user_info(user_info): js = json.dumps(user_info) with open("user_info.txt", 'w',) as f: f.write(js)def main(): # 导入人脸级联分类器引擎,'.xml'文件里包含训练出来的人脸特征 face_detector = cv2.CascadeClassifier("enginer\haarcascade_frontalface_default.xml") # 调用笔记本内置摄像头,所以参数为0,如果有其他的摄像头可以调整参数为1,2 cap = cv2.VideoCapture(0) # 获取当前人脸的学号、姓名信息,并将其保存在user_info的字典中 user_info =dict() face_id = input("请输入你的学号(ID):") face_name= input("请输入你的姓名:") user_info[face_id]=face_name store_user_info(user_info) print("初始化摄像机,请注意看摄像头,开始获取头像信息,请耐心等待... ...") count = 0 while True: # 从摄像头读取图片 sucess, img = cap.read() if sucess is True: # 如果摄像头成功读取图像,则将图像转换成灰度图像 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) else: break # 用人脸级联分类器引擎进行人脸识别,返回的faces为人脸坐标列表(x,y,w,h) faces = face_detector.detectMultiScale(gray, 1.3, 5) # 如果检测出人脸,则对每一张人脸都做画矩形,并将其保存到同目录的Facedata文件夹中 for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x+w, y+w), (255, 0, 0)) count += 1 # 保存图像 cv2.imwrite("Facedata/User." + str(face_id) + '.'+ str(face_name) + '.'+ str(count) +'.jpg', gray[y: y + h, x: x + w]) cv2.imshow('image', img) # 保持画面的持续 k = cv2.waitKey(30) if k == 27: # 通过esc键退出摄像 break elif count >= 100: # 得到100个样本后退出摄像 break # 关闭摄像头 cap.release() cv2.destroyAllWindows()if __name__ == '__main__': main()
在训练模型阶段,我们需要从存储的数据集中抽取所有的用户数据,并训练 OpenCV 识别器对其继续识别,这一过程可由特定的 OpenCV 函数直接完成,将识别结果保存在程序根目录下的face_trainer目录中,格式为.yml 文件。
import numpy as npfrom PIL import Imageimport osimport cv2# 根据图像存储的文件路径,获取当前图像,然后对其进行一一的训练def getImagesAndLabels(path): detector = cv2.CascadeClassifier("enginer\haarcascade_frontalface_default.xml") imagePaths = [os.path.join(path, f) for f in os.listdir(path)] faceSamples = [] ids = [] for imagePath in imagePaths: PIL_img = Image.open(imagePath).convert('L') img_numpy = np.array(PIL_img, 'uint8') id = int(os.path.split(imagePath)[-1].split(".")[1]) #获取学号 id_num = int(os.path.split(imagePath)[-1].split(".")[3]) #获取当前学号的第几张图像 print("正在训练ID为{}号的第{}张人脸信息....".format(id,id_num)) faces = detector.detectMultiScale(img_numpy) for (x, y, w, h) in faces: faceSamples.append(img_numpy[y:y + h, x: x + w]) ids.append(id) return faceSamples,idsdef main(): # 获取人脸数据路径 path = 'Facedata' recognizer = cv2.face.LBPHFaceRecognizer_create() print("开始训练数据模型,这可能需要一些时间,请耐心等待......") faces, ids = getImagesAndLabels(path) recognizer.train(faces, np.array(ids)) recognizer.write(r'face_trainer\trainer.yml') print("总共存在{0}个人. 已完成识别".format(len(np.unique(ids))))if __name__ == '__main__': main()
我们将通过摄像头捕捉一个新人脸,如果这个人的面孔之前被捕捉和训练过,我们的识别器将会返回其预测的 id 和索引,并展示识别器对于该判断有多大的信心。
import cv2import json#定义函数获取用户的信息def get_user_info(): user_info = dict() with open("user_info.txt", 'r', ) as f: n = len(f.readlines()) with open("user_info.txt", 'r', ) as f: for i in range(n): line = f.readline() dic = json.loads(line) user_info.update(dic) return user_infodef main(): recognizer = cv2.face.LBPHFaceRecognizer_create() recognizer.read('face_trainer/trainer.yml') faceCascade = cv2.CascadeClassifier("enginer\haarcascade_frontalface_default.xml") font = cv2.FONT_HERSHEY_SIMPLEX # 名字是一个列表,与训练集中人的ID相对应,如"xuenwei":id=1,等等 #names = ['None','1xuenwei', '2Bob','3'] user_info=get_user_info() #初始化并开始使用摄像头获取图像 cam = cv2.VideoCapture(0) while True: ret, img = cam.read() gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces = faceCascade.detectMultiScale( gray, scaleFactor=1.2, minNeighbors=5, minSize=(32, 32) ) for (x, y, w, h) in faces: cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2) idnum, confidence = recognizer.predict(gray[y:y+h, x:x+w]) user_id=str(idnum) if user_id in list(user_info.keys()): if confidence < 100: idname = user_info[user_id] confidence = "{0}%".format(round(100 - confidence)) else: idname = "unknown" confidence = "{0}%".format(round(100 - confidence)) else: idname = "Not exist this people!" confidence=0 cv2.putText(img, str(idname), (x+5, y-5), font, 1, (0, 0, 255), 1) cv2.putText(img, str(confidence), (x+5, y+h-5), font, 1, (0, 0, 0), 1) cv2.imshow('camera', img) k = cv2.waitKey(10) if k == 27: break cam.release() cv2.destroyAllWindows()if __name__ == '__main__': main()
人脸识别是人工智能应用的一个方面,现今已经被应用在很多方面,比如:手机面部解锁、火车站进站检票、超市扫脸付款等。本项目希望有助于各位读者实现自己的人脸识别应用。
(本教程参考‘’用树莓派实现实时的人脸检测‘’以及网页资料。)
文章来源 :笔记整理微信编辑:徐恩伟
【实用软件推荐---混合式学习环境下实用教育技术与工具】
【教学】作为教师,我们最应该教给学生什么?
【13】项目案例—Python爬取链家网租房信息
【00】Python编程语言机房教学环境 安装指南
【12】Python与MySQL数据库的交互 笔记
【人工智能教学】人工智能将使中国教育仅存的优势荡然无存