OpenCV是一个强大的图像处理库,包含各种功能。人脸识别只是其众多应用的其中一个。
本篇文章与上一篇所讲述的知识有很大的重合 ,请按需观看。
理论知识
计算机的视觉系统,计算机通过摄像头看到的,简单来说,就是一堆由数字组成的矩阵。这些数字表明了物体发出的光的强弱,摄像头的光敏元件将光信号转化成数字信号,将其量化为矩阵。
计算机中的彩色图片都是由若干个色彩通道累积出来的,比如RGB模式的图片,这三个通道都是灰度图,比如一个点由8位来表示,则一 个通道可以表示2^8=256个灰度。那样三个通道进行叠加以后可以表3*8=24位种色彩。
对这样的图片做处理,无疑是一件很复杂的事,所以有必要先将彩色图转为灰度图,那样可以减少数据量(比如RGB模式,可以减少到原图片的1/3),同时可 以去掉一些噪声信号。先将图片转化为灰度图,然后将这个灰度图的对比度增高,这样可以使得图片本来暗的地方更暗,亮的地方更亮一些。这样处理以后,图片就更容易被算法识别出来了。
Haar特征
Haar特征包含三种:边缘特征、线性特征、和中心围绕特征。
Haar特征提取流程图
在OpenCV人脸检测中,Haar特征分类器就是一个XML文件,该文件中会描述人体各个部位的Haar特征值。包括人脸、眼睛、嘴唇等等。
HOG (Histograms of Oriented Gradients)
对于一张待检测图片,我们分析每个像素以及其周围的像素,根据明暗度画一个箭头,箭头的指向代表了像素逐渐变暗的方向,如果我们重复操作每一个像素,最终像素会被箭头取代。这些箭头被称为梯度(gradients),它们能显示出图像从明亮到黑暗流动的过程。
分析每个像素对我们来说太过细节化了,像素过多不利于整体的分析,我们应该从更高的角度观察明暗的流动。为此我们将图像分割成16x16像素的小方块。在每个小方块中,计算出每个主方向有多少个梯度(有多少指向上,指向右上,指向右等)。然后用指向性最强的那个方向箭头来代替原来那个小方块。
最终结果,我们把原始图像转换成非常简单的HOG图像,它可以很轻松的捕获面部的基本结构。为了在HOG图像中找到脸部,我们需要做的是,与已知的一些HOG图案中,来寻找看起来最相似的部分。
这些HOG图案都是从其他面部训练数据中提取出来的。
级联分类器
对于本次的设计人脸检测,可以说是一次机器学习的过程。级联分类器中有一个级联分类函数,此函数当中的一些参数就是由机器训练得到的。对于人脸识别,计算机通过大量带人脸和不带人脸的图片,对人脸上的几万个特征,通过机器学习找出人脸分类效果最好、错误率最小的特征。训练开始时,所有训练集中的图片具有相同的权重,对于被分类错误的图片,提升权重,重新计算出新的错误率和新的权重。直到错误率或迭代次数达到要求。这种训练方法叫做Adaboost。
上文提到的hog图像,正是由adaboost训练方法对大量图片进行训练后所提取出来的。
Cascade级联分类器
Cascade 译为级联,阶梯。cascade级联分类器是一种基于Haar特征的有效的物体检测方法。
一张图片绝大部分的区域都不是人脸。如果对一张图片的每个角落都提取6000个特征,将会浪费巨量的计算资源。
如果能找到一个简单的方法能够检测某个窗口是不是人脸区域,如果该窗口不是人脸区域,那么就只看一眼便直接跳过,也就不用进行后续处理了,这样就能集中精力判别那些可能是人脸的区域。为此,有人引入了Cascade 分类器。它不是将6000个特征都用在一个窗口,而是将特征分为不同的阶段,然后一个阶段一个阶段的应用这些特征(通常情况下,前几个阶段只有很少量的特征)。如果窗口在第一个阶段就检测失败了,那么就直接舍弃它,无需考虑剩下的特征。如果检测通过,则考虑第二阶段的特征并继续处理。如果所有阶段的都通过了,那么这个窗口就是人脸区域。
比如上图中,横的黑道将人脸中较暗的双眼提取了出来,而竖的白道将人脸中较亮的鼻梁提取了出来。
由于事先不太可能知道要检测的目标的大小,这就要求我们的级联表中的分类器具有按比例增大(或者缩小)的能力,这样,当小的窗口移动完整个待检测图片没有发现目标时,我们可以调整分类器的大小,然后继续检测,直到检测到目标或者窗口与待检测图片的大小相当为止。人脸检测流程图见图
人脸检测具体实现
环境搭建
本项目采用Python作为编辑器,pycharm作为解释器。
OpenCV是一个可跨平台的计算机视觉和机器学习的软件库,实现了图像处理和计算机视觉方面的很多通用算法。
安装OpenCV时,有以下几种方法。
一,首先可以利用的是pycharm来进行引入
file -- settings --project interpreter-- +
选择OpenCV--python ---install
如果安装成功,随后你可以在project interpreter里来进行版本的查看。
二,利用常规命令
Win+r后输入cmd,随后输入pip install OpenCV-python
如果显示install successfully,则安装完成。
如果报错,read time error ,则可能是库在下载时有延迟的问题,需要在命令里加入一个代理,这里用到的是豆瓣的,在install 后面加入以下命令即可。
- -index-url https://pypi.douban.com/simple opencv-python
OpenCV可以说是人脸物体检测识别中比较常用的一个库,利用import cv2来进行调用。
项目代码
对于识别本地图片中的人脸,我们需要进行以下几个步骤的操作:
1)把图像转换为灰度图,这样可以去除色彩对目标检测的影响。同时可以降低图像的噪声。
import cv2 #引入OpenCV模块
#规定图像的路径。
filepath = "./image1.png"
# ./为当前文件夹下的相对路径,我们需要将检测图片与程序文件放于同一文件夹下。
img = cv2.imread(filepath)
#cv2.IMread就是利用OpenCV模块来读取此路径下的图片文件 imread=image-read
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
#img为原始彩色图片,这里我们用到了cv2模块的图像颜色转换功能,将转换后的图像命名为gray
cv2.imshow("Image", gray)
#显示灰色图像
2)在灰色图像上进行矩形框的绘制
cv2.imshow("Image-gray", img)
x = y = 100 # 任意规定一个坐标w =h= 135 # 矩形的宽、高color = (0,255,0) # 定义绘制颜色,遵循bgr模式cv2.rectangle(img, (x, y), (x + w, y + h),color , 3) #调用cv2里的rectangle来 绘制矩形,rectangle(图像,左上角坐标,右下角坐标,颜色,线粗)cv2.imshow("Image-gray-square", img) # 显示灰色方框图像
3)使用分类器查找人脸
CascadeClassifier是Opencv中做人脸检测时候的一个级联分类器。数据结构包括Data和FeatureEvaluator两个主要部分。Data中存储的是从训练获得的xml文件中载入的分类器数据;而FeatureEvaluator中是关于特征的载入、存储和计算。这里采用的训练文件是OpenCV中默认提供的haarcascade_frontalface_default.xml。至于Haar,LBP的具体原理,可以参考opencv的相关文档,简单地,可以理解为人脸的特征数据。
face_engine=cv2.CascadeClassifier(cv2.data.haarcascades+'haarcascade_frontalface_default.xml')
# 导入人脸级联分类器引擎,'.xml'文件里包含已经训练出来的人脸特征
faces=face_engine.detectMultiScale(img,scaleFactor=1.3,minNeighbors=5)
# 用人脸级联分类器引擎进行人脸识别,返回的faces为人脸坐标列表,1.3是放大比例,5是重复识别次数
整体代码如下:
importcv2
img=cv2.imread('image1.jpg',1)
face_engine=cv2.CascadeClassifier(cv2.data.haarcascades+'haarcascade_frontalface_default.xml')
faces=face_engine.detectMultiScale(img,scaleFactor=1.3,minNeighbors=5)
for(x,y,w,h)infaces:
img =cv2.rectangle(img,(x,y),(x+w,y+h),(255,0,0),2)
cv2.imshow('img2',img)
cv2.waitKey(0)
cv2.destroyAllwindows()
cv2.imwrite('output.jpg',img)
注:
* cv2.waitKey等待键盘输入,单位为毫秒,即等待指定的毫秒数看是否有键盘输入,若在等待时间内按下任意键则返回按键的ASCII码,程序继续运行。若没有按下任何键,超时后返回-1。参数为0表示无限等待。不调用waitKey的话,窗口会一闪而逝,看不到显示的图片。
* cv2.destroyAllWindow()销毁所有窗口
* cv2.destroyWindow(wname)销毁指定窗口
运行结果图
由此可以看出此代码运行结果良好,符合预期结果。
代码改进
上述代码为引入本地图片来进行人脸测量。现在改进下代码,可以通过摄像头进行实时的人脸检测。
在源代码的基础上,增加以下语句:
cap=cv2.VideoCapture(0)
while(True):
# 获取摄像头拍摄到的画面
ret, frame=cap.read()
faces=face_cascade.detectMultiScale(frame,1.3,5)
#frame是摄像头捕捉到的画面
.....
#每隔5毫秒监听一次键盘,当按下q键时退出摄像窗口
ifcv2.waitKey(5)&0xFF==ord('q'):
break
# 关闭所有窗口
cap.release()
cv2.destroyAllWindows()
利用手机播放视频,可进行实时人脸检测,运行结果良好。结果如图
注:
1)在用cv2.rectangle进行画框时,原始定义为(图像,左上角,右下角)。但由于疏忽,有一次我将左上角和右下角的坐标顺序写反,即(右下角,左上角)。但是结果却依然正确。在后来我将数据改为(右上角,左下角)(左下角,右上角),结果依然一样。因此得知,rectangle是利用对角线来进行矩形的框定。
在此附上此次验证的相关代码:
import cv2
# filepath = "./men-face.jpg"
image = cv2.imread('./men-face.jpg',cv2.IMREAD_GRAYSCALE)
h, w = image.shape[:2]
h, w = map(int, [h/4, w/4])
draw_0 = cv2.rectangle(image, (2*w, 2*h), (3*w, 3*h), (255, 0, 0), 2)
draw_1 = cv2.rectangle(image, (2*w, 3*h), (3*w, 2*h), (255, 0, 0), 2)
draw_2 = cv2.rectangle(image, (3*w, 2*h), (2*w, 3*h), (255, 0, 0), 2)
draw_3 = cv2.rectangle(image, (3*w, 3*h), (2*w, 2*h), (255, 0, 0), 2)
cv2.imshow("origin.jpg", draw_0)
cv2.imshow("change1.jpg", draw_1)
cv2.imshow("change2.jpg", draw_2)
cv2.imshow("change3.jpg", draw_3)
cv2.waitKey(0)
cv2.destroyAllWindows()
2)HOG的发明者是Navneet Dalal,在2005年其在CVPR上发表了Histograms of Oriented Gradients forHuman Detection这一篇论文。
HOG算法思想:
在计算机视觉以及数字图像处理中梯度方向直方图(HOG)是一种能对物体进行检测的基于形状边缘特征的描述算子,它的基本思想是利用梯度信息能很好的反映图像目标的边缘信息并通过局部梯度的大小将图像局部的外观和形状特征化。一些研究者利用梯度HOG特征并结合其他特征对人体进行检测得到了较好的结果。
HOG特征的提取可以用下图所示的过程表示: 颜色空间的归一化是为了减少光照以及背景等因素的影响;划分检测窗口成大小相同的细胞单元(cell),并分别提取相应的梯度信息;组合相邻的细胞单元成大的相互有重叠的块(block),这样能有效的利用重叠的边缘信息,以统计整个块的直方图;并对每个块内的梯度直方图进行归一化,从而进一步减少背景颜色及噪声的影响;最后将整个窗口中所有块的HOG特征收集起来,并使用特征向量来表示其特征。在这一过程中,不同尺度的参数模板、梯度方向的选择、重叠块及单元格的大小还有归一化因子等因素都会影响最终的检测结果。最终通过SVM或cascade分类器分离出正确的行人目标。
HOG的整体流程图如下所示。
颜色空间归一化:
在现实的情况,图像目标会出现在不同的环境中,光照也会有所不一样,颜色空间归一化就是对整幅图像的颜色信息作归一化处理从而减少不同光照及背景的影响,也为了提高检测的鲁棒性,引入图像Gamma校正和颜色空间归一化来作为特征提取的预处理手段。ND等人也对不同的图像像素点的表达方式包括灰度空间等进行了评估,最终验证RGB还有LAB色彩空间能使检测结果大致相同且能起到积极的影响,且另一方面,ND等人在研究中分别在每个颜色通道上使用了两种不同的Gamma归一化方式,取平方根或者使用对数法,最终验证这一预处理对检测的结果几乎没有影响,而不能对图像进行高斯平滑处理,因平滑处理会降低图像目标边缘信息的辨识度,影响检测结果。
梯度计算:
边缘是由图像局部特征包括灰度、颜色和纹理的突变导致的。一幅图像中相邻的像素点之间变化比较少,区域变化比较平坦,则梯度幅值就会比较小,反之,则梯度幅值就会比较大。梯度在图像中对应的就是其一阶导数。模拟图像f(x,y)中任一像素点(x,y)的梯度是一个矢量:
其中,Gx是沿x方向上的梯度,Gy是沿y方向上的梯度,梯度的幅值及方向角可表示如下:
数字图像中像素点的梯度是用差分来计算的:
一维离散微分模板在将图像的梯度信息简单、快速且有效地计算出来,其公式如下:
式中,Gx,Gy,H(x,y)分别表示的是像素点(x,y)在水平方向上及垂直方向上的梯度以及像素的灰度值,其梯度的幅值及方向计算公式如下:
ND等人也验证,不同的梯度运算模板在其检测效果上也不一样,如下表,可以看出,使用简单的一维离散微分模板[-1,0,1]进行的梯度运算得到的检测效果是最好的,而使用其他形式的梯度运算模板如Prewitt和Sobel等算子,如下图所示,不仅增加运算量而同时也降低了其检测效果。
计算细胞单元的梯度直方图:
对于整个目标窗口,我们需要将其分成互不重叠大小相同的细胞单元(cell),然后分别计算出每个cell的梯度信息,包括梯度大小和梯度方向。ND大神等人实验指出,将像素的梯度方向在0-180°区间内平均划分为9个bins,超过9个时不仅检测性能没有明显的提高反而增加了检测运算量, 每个cell内的像素为其所在的梯度方向直方图进行加权投票,加权的权值可以是像素本身的梯度幅值,也可以是幅值的平方或平方根等,而若使用平方或平方根,实验的检测性能会有所降低,ND等人也验证,使用梯度幅值的实验效果更可靠。
梯度直方图的计算
对组合成块的梯度直方图作归一化:
从梯度计算公式中可以看出,梯度幅值绝对值的大小容易受到前景与背景对比度及局部光照的影响,要减少这种影响得到较准确的检测效果就必须对局部细胞单元进行归一化处理。归一化方法多种多样,但整体思想基本上是一致的:将几个细胞单元(cell)组合成更大的块(block),这时整幅图像就可看成是待检测窗口,将更大的块看成是滑动窗口,依次从左到右从上到下进行滑动,得到一些有重复细胞单元的块及一些相同细胞单元(cell)在不同块(block)中的梯度信息,再对这些块(block)信息分别作归一化处理,不同的细胞单元尺寸大小及不同块的尺寸大小会影响最终的检测效果。
假设64×64像素是检测的窗口的尺寸,分成 4×4=16 个细胞单元(cell),如下图中黑色的小框(1-16),16×16 像素是每个细胞单元的大小,块(block)是由相邻的 2×2=4 个细胞单元组成的,如图紫色的小框,滑动窗口的大小为一个块的大小,依次将滑动窗口从左到右从上到下进行滑动来获得整个待测窗口的边缘信息,得到9个块,统计这9个块在9个不同方向上的梯度信息,在整个窗口中得到的梯度特征是9×9=81 维的向量。在实际情况中我们的检测窗口的大小为128×64像素,一个细胞单元的大小为8×8像素,由2×2个细胞单元组成大小为16×16像素的块,一个细胞单元的梯度直方图化成9个bins,块的移动步长是8个像素,则检测窗口在图像中移动的步长也为8个像素,这样检测窗口就有((128-16)/8+1)×((64-16)/8+1)= 105个块,一个块有4个细胞单元,每个细胞单元的HOG特征向量长度是9,则最终的HOG特征描述符大小就是105×4×9 = 3780维。
对于块的梯度直方图向量的归一化,ND等人使用了不同方法,并对结果进行了比较,假设V是未归一化的向量,是一个很小的必要的常数,下式是定义的范数的函数表达式:
L1范数:
L2范数:
归一化计算公式为:
L2常规:
L1常规:
L1平方根:
ND等人指出,用L1常规方法进行向量归一化比用L2常规和L1平方根方法而得到的检测效果降低5%。
来源:六月的日记 作者:小胖鸭
End
声明:部分内容来源于网络,仅供读者学习、交流之目的。文章版权归原作者所有。如有不妥,请联系删除。