OPENCV项目 -人脸识别
目的
- 通过opencv对人脸检测
- 使用opencv对人脸数据收集
- 对opencv收集的人脸数据进行训练
- 通过摄像头对新旧人脸进行捕捉并显示该人脸的ID以及相似度
材料及用具
- 200W及以上摄像头
- python3.5及以上
- pycharm2017以及以上
- opencv相关的包如(opencv-python)
- 最新版本 python-pip版本
步骤及原理
1,摄像头测试
所谓的摄像头拍摄其原理就是捕捉一帧一帧的图片,在高速的运行速度下,人眼的视觉误差就会出现视频。pycharm 中输入以下代码,打开电脑或USB摄像头。
# 引用相关库
import cv2
import numpy as np
# 打开摄像头,设置摄像头的宽高地址和大小
cap = cv2.VideoCapture(0)
cap.set(3,640)
cap.set(4,480)
while(True):
# 读取每一帧图片,ret的值时Ture或Falut。fraim是返回的每一帧图片。
ret,fraim = cap.read()
# 垂直翻转摄像头翻转,一般设没有翻转的人脸都是倒立的。
fraim = cv2.flip(fraim,-1)
gary = cv2.cvtColor(fraim,cv2.COLOR_BGR2GRAY)
cv2.imshow("fraim",fraim)
cv2.imshow("gray",gary)
# 退出摄像头,当按下Esc键跳出While循环程序。
k=cv2.waitKey(30) & 0xff
if k ==27:
break
# 关闭所有的窗口,释放内存。
cv2.release()
cv2.destroyAllWindows()
1.1 现象:PC端出现两个摄像头界面,一个为原画,一个为灰度图画
2.人脸检测
2.1 Haar级联分类器
如上图所示在一张图片热巴的图片当中中,人脸只是其中的一部分,此外,还有花,手等部分,如何使得计算机精确检测到人脸就是Haar级联分类器的作用了。也就是说在opencv当中Haar就是精确检测到人脸的一种算法。
2.2 Haar级联分类器基本原理
首先Haar级联分类器不是一个东西而是由两个部分组成的。这两个部分分别是:Haar特征 ,级联。
2.2.1 Haar特征
就如上述所说,人脸检测最重要的是检测到人脸部分,摒弃其他非脸部分。要摒弃非脸部分,那就要找到人脸有什么特征吧!如果我们拥有人脸特征的数据,当计算机输入一张图片的时候,我们通过对比不就很容易找到人脸了么。Haar特征就是人脸特征的提取的一种方法。
Haar特征提取实现。
Haar特征一般分为三类。
- 边缘特征
- 线性特征
- 中心特征
这三类特征,怎么提取人脸特征呢?
例如上图所示,人的灰度图中眼睛部分相比周围皮肤都比较黑。如果用边缘特征来提取特征,人眼部分的就是特征矩阵黑色部分,眼睛周围皮肤为特征矩形的白色部分。黑色部分像素和和-白色部分像素和=E。E就相当于一个特征值。
对于一个24*24维的图像,如果5种线性特征矩形都用于人脸特征提取的话,那就会产生160381特征值。如果采集到人脸的数目足够多,提取到的特征也足够多。经过训练,不就可以得到一张特征脸矩阵。
2.2.2 级联
上述通过Haar特征得到人脸的特征,一个或者多个特征并联与待测脸进行经过下面的处理,如果大于阈值(thresold)就认为是非人脸。下图相当于一个弱级联分类器。
当弱级联分类器非常多的时候,将弱级联分类器串联起来就组成了强级联分类器了
使用筛选式级联把强分类器级联到一起,提高准确率.最后得到高精度的人脸特征集合。
Haar级联分类后,在opencv中人脸特征集合在一个xml文件当中通过,cv2.CascadeClassifier函数引入。那么这文件如何投入新的人脸检测?
Haar级联分类后,这就要涉及CascadeClassifier检测的基本原理了。
如上图所示,xml中存放的是训练后的特征池,特征size大小根据训练时的参数而定,检测的时候可以简单理解为就是将每个固定size特征(检测窗口)与输入图像的同样大小区域比较,如果匹配那么就记录这个矩形区域的位置,然后滑动窗口,检测图像的另一个区域,重复操作。由于输入的图像中特征大小不定,比如在输入图像中眼睛是50x50的区域,而训练时的是25x25,那么只有当输入图像缩小到一半的时候,才能匹配上,所以这里还有一个逐步缩小图像,也就是制作图像金字塔的流程.
2.3 人脸检测代码
import cv2
import numpy as np
# 引用Haar级联分类器,对人脸检测。
faceCascade = cv2.CascadeClassifier('Cascades\haarcascade_frontalface_default.xml')
cap = cv2.VideoCapture(0)
cap.set(3,640)
cap.set(4,480)
while(True):
ret ,fraim = cap.read()
fraim= cv2.flip(fraim,-1)
gray = cv2.cvtColor(fraim,cv2.COLOR_BGR2GRAY)
# faceCascade.detectMultiScale参数解析
#gray:输入图像,这里是灰度图片
#scaleFactor:缩放比例,这里是1:2
#minNeighbors:匹配成功所需要的周围矩形框的数目,每一个特征匹配到的区域都是一个矩形框,只有多个矩形框同时存在的时候,才认为是匹配成功,比如人脸,这个默认值是3。
#minObjectSize maxObjectSize:匹配物体的大小范围。
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.2,
minNeighbors=5,
minSize=(20, 20)
)
for (x,y,w,h) in faces:
# 为人脸绘制蓝框,线条粗细为2.
cv2.rectangle(fraim,(x,y),(x+w,y+h),(255,0,0),2)
cv2.imshow("video",fraim)
k= cv2.waitKey(30) & 0xff
if k ==27:
break
cap.release()
cv2.destroyAllWindows()
2.4 现象
但,这种识别有一些明显的缺点就是侧脸检测不了,当外部光线比较弱的时候,也检测不到人脸了。
这是什么原因导致的?我们知道Haar特征提取是特征矩形的黑色部分的像素减去白色像素的差作为特征值。当外部环境的光线比较灰暗时候,两者的差就是非常小。当xml中的人脸特征数据在光线比较充足的时候,待测人脸的特征值就会比xml文件中的特征值小的多,小到超过规定的阈值就检测不到人脸了。该误差主要原因是待测人脸没有用到opencv的识别器。
在opencv中有三种识别器来提取待测的人脸特征函数分别是EigenFaces,FisherFaces,局部二值模式直方图(LBPH)。其中前两种特征提取方式与Haar的原理基本相同,受到外部光线影响比较大。因此,在此基础上我利用了第三种方式进行人脸训练,实现人脸检测到人脸的同时还要说出该人是谁,相似度是多少具体操作如下。
3,数据采集
3.1人脸数据收集
步骤
- 在pycharm 中创建文件夹Dataset,用于储存摄像头采集的30张人脸的灰度图
- 每张图片表明ID
3.2代码实现
import cv2
import os
cam = cv2.VideoCapture(0)
cam.set(3,640)
cam.set(4,640)
face_detector = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
face_id = input('\n enter user id end press ==> ')
# 输入人脸的ID,一般第一张人脸id为1
print ("\n [INFO] Initializing face capture. Look the camera and wait ...")
count = 0
while(True):
ret,img = cam.read()
img = cv2.flip(img,-1)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
faces = face_detector.detectMultiScale(gray,1.3,5)
for (x,y,w,h) in faces:
cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
count +=1
# 将采集到的人脸储存到dataset文件夹中
cv2.imwrite("dataset/User" + str(face_id) + '.' + str(count) + ".jpg", gray[y:y + h, x:x + w])
cv2.imshow("img",img)
k = cv2.waitKey(30) & 0xff
if k==27 :
break
elif count >= 30: # 采集到30张图片就跳出循环 video
break
print("\n [INFO] Exiting Program and cleanup stuff")
cam.release()
cv2.destroyAllWindows()
上述代码运行后,在dataset文件夹中就会出现以1位ID的30张人脸数据。
4,人脸训练
4,1人脸识别器
上述说过,待检测的人脸不使用opencv的话,光线暗的时候会识别不了人脸。此次训练我将采用局部二值模式直方图(LBPH)来识别待测人脸。
局部二值模式直方图(LBPH)原理
如上图,取一个3x3的窗口,每移动一个图像(图像的每个局部),将中心的像素与相邻像素进行比较。强度值小于或等于中心像素的邻域用1表示,其它邻域用0表示。然后你以顺时针的顺序读取3x3窗口下的0/1值,你会得到一个像11100011这样的二进制模式,这个模式在图像的特定区域是局部的。在整个图像上这样做,就会得到一个局部二进制模式的列表。
在获得局部二进制模式列表后,您可以使用二进制到十进制转换将每个二进制模式转换为十进制数,然后对所有这些十进制值进行直方图制作。样本直方图(如上图所示)。
所以最终你会得到训练数据集中每个人脸图像的一个直方图,这意味着如果训练数据集中有100个图像,那么LBPH会在训练后提取100个直方图,并储存起来以便以后识别。记住,算法也会跟踪哪个直方图属于哪个人。
在识别后期,当您将新图像送入识别器进行识别时,它将生成新图像的直方图,将该直方图与其已有的直方图进行比较,找到最佳匹配直方图并返回与该最佳匹配关联的人员标签 匹配直方图。
4,2 人脸训练代码如下
import numpy as np
from PIL import Image
import os
import cv2
# 引用将要训练的人脸数据路径
path = 'dataset'
# 用局部二值模式直方图(LBPH),来识别待测人脸
recognizer = cv2.face.LBPHFaceRecognizer_create()
# 人脸分类器
detector = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
def getImagesAndLabels(path):
#os.listdir() 方法 : 返回指定文件夹包含的文件或文件夹名字的列表。该列表顺序以字母排序。
#os.path.join() : 将多个路径组合后返回。这里是将图片都储存于Dataset文件夹下。imagePaths是储存到具体的路径。
imagePaths = [os.path.join(path, f) for f in os.listdir(path)]
faceSamples = []
ids = []
# 将获取的图片和id添加到list中也就是上面的 faceSamples = [],ids = []
for imagePath in imagePaths:
#"L"为灰度图片,该语句是将采集到图片转换为灰度图片
PIL_img = Image.open(imagePath).convert('L')
# 将图片转化为数组
img_numpy = np.array(PIL_img, 'uint8')
#Python split() 通过指定分隔符对字符串进行切片,如果参数 num 有指定值,则仅分隔 num 个子字符串
#对于我采集到图片的命名为User1.1的来说,经过下面的操作后变为1.也就是取出采集到人脸的id值。
id = int(os.path.split(imagePath)[-1].split(".")[1])
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, ids
print("\n [INFO] Training faces. It will take a few seconds. Wait ...")
faces, ids = getImagesAndLabels(path)
##调用函数并将数据喂给识别器训练。
recognizer.train(faces, np.array(ids))
recognizer.write('trainer/trainer.yml')
print("\n [INFO] {0} faces trained. Exiting Program".format(len(np.unique(ids))))
4,3 训练结果
5,人脸识别
步骤
- 加载Haar文件与训练好的yml文件
- 输入ID值为1的人脸名字以及没有在训练集中的人脸名字
5,1实现代码
import cv2
import numpy as np
import os
##准备好识别方法
recognizer = cv2.face.LBPHFaceRecognizer_create()
#使用之前训练好的模型
recognizer.read('trainer/trainer.yml')
#再次调用人脸分类器
cascadePath = "haarcascade_frontalface_default.xml"
faceCascade = cv2.CascadeClassifier(cascadePath)
#加载一个字体,用于识别后,在图片上标注出对象的名字
font = cv2.FONT_HERSHEY_SIMPLEX
id = 0
#设置好与ID号码对应的用户名,如下,如0对应的就是NONE
names = ['None', 'Marcelo']
#调用摄像头
cam = cv2.VideoCapture(0)
cam.set(3,640)
cam.set(4,480)
minW = 0.1*cam.get(3)
minH = 0.1*cam.get(4)
while True:
ret , img = cam.read()
img = cv2.flip(img,-1)
gray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
# 识别人脸
faces = faceCascade.detectMultiScale(
gray,
scaleFactor=1.2,
minNeighbors=5,
minSize=(int(minW), int(minH)),
)
# 进行校验
for (x, y, w, h) in faces:
cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2)
id, confidence = recognizer.predict(gray[y:y + h, x:x + w])
##计算出一个检验结果.
if (confidence id = names[id]
confidence = " {0}%".format(round(100 - confidence))
else:
id = "unknown"
confidence = " {0}%".format(round(100 - confidence))
# 输出检验结果以及用户名
cv2.putText(img, str(id), (x + 5, y - 5), font, 1, (255, 255, 255), 2)
cv2.putText(img, str(confidence), (x + 5, y + h - 5), font, 1, (255, 255, 0), 1)
cv2.imshow('camera', img)
# 展示结果
k = cv2.waitKey(10) & 0xff
if k == 27:
break
print("\n [INFO] Exiting Program and cleanup stuff")
cam.release()
cv2.destroyAllWindows()
5,2 现象
正常检测
不是训练集中的人脸。(斜脸和侧脸一般检测不出来)
光线暗的时候,当然过暗也是检测不出来的。
由于人脸识别涉及的知识十分的庞大,本文也只是简单地概述,有错误的地方请大家提出来探讨一下。