在计算机视觉领域中,人脸检测或者物体检测一直是一个非常受关注的领域,而在人脸检测中,Viola-Jones人脸检测算法可以说是非常经典的一个算法,所有从事人脸检测研究的人,都会熟悉了解这个算法。Viola-Jones算法在2001年的CVPR上提出,因为其高效而快速的检测即使到现在也依然被广泛使用,OpenCV 和 Matlab中都将这个算法写进了函数库可以很方便的直接调用。虽然VJ人脸检测算法最初都是用来检测正面的人脸图像,对于侧脸图像的检测不是很稳健,不过这个算法依然有值得研究的价值。
(1) 利用Haar特征描述人脸的共有属性
Haar特征是一种反映图像的灰度变化的,像素分模块求差值的一种特征。它分为四类:边缘特征、线性特征、中心特征和对角线特征。用黑白两种矩形框组合成特征模板,在特征模板内用黑色矩形像素和减去白色矩形像素和来表示这个模版的特征值。例如:脸部的一些特征能由矩形模块差值特征简单的描述,如:眼睛要比脸颊颜色要深,鼻梁两侧比鼻梁颜色要深,嘴巴比周围颜色要深等。但矩形特征只对一些简单的图形结构,如边缘、线段较敏感,所以只能描述在特定方向(水平、垂直、对角)上有明显像素模块梯度变化的图像结构。
(1) 利用Haar特征描述人脸的共有属性
Haar特征是一种反映图像的灰度变化的,像素分模块求差值的一种特征。它分为四类:边缘特征、线性特征、中心特征和对角线特征。用黑白两种矩形框组合成特征模板,在特征模板内用黑色矩形像素和减去白色矩形像素和来表示这个模版的特征值。例如:脸部的一些特征能由矩形模块差值特征简单的描述,如:眼睛要比脸颊颜色要深,鼻梁两侧比鼻梁颜色要深,嘴巴比周围颜色要深等。但矩形特征只对一些简单的图形结构,如边缘、线段较敏感,所以只能描述在特定方向(水平、垂直、对角)上有明显像素模块梯度变化的图像结构。
图 1 四种基本的Haar特征
(2) 建立积分图像特征,并且基于积分图像,快速获取几种不同的矩形特征。
对于一幅灰度的图像,积分图像中的任意一点(x, y)的值是指从图像的左上角到这个点的所构成的矩形区域内所有的点的灰度值之和。例如,计算图中D区域内像素之和sum(D),利用积分图像可表示如下:
对于一幅灰度的图像,积分图像中的任意一点(x, y)的值是指从图像的左上角到这个点的所构成的矩形区域内所有的点的灰度值之和。例如,计算图中D区域内像素之和sum(D),利用积分图像可表示如下:
sum(D) = sum(A+B+C+D) -sum(A+B)-sum(A+C)+sum(A)
图2 利用积分图计算s(D)
(3) 利用Adaboost算法进行训练。
AdaBoost将一系列的人脸识别弱分类器通过线性组合,构成一个强分类器,如下所示:
AdaBoost将一系列的人脸识别弱分类器通过线性组合,构成一个强分类器,如下所示:
h(x)是一个强分类器,hj(x)是一个弱分类器,其为一个简单的阈值函数:
θj为阈值,sj∈{-1, 1},αj为权重。
对弱分类器和强分类器进行训练后,即可获得用于人脸识别的算法模型。
(4) 建立级联分类器。
在正常的图像中,人脸区域只是占了很小的一部分,如果使用所有的特征进行训练的话,运算量非常大。级联为了简化任务,把若干个Adaboost分类器级联起来,一开始使用少量的特征将大部分的非人脸区域剔除掉,后面再利用更复杂的特征将更复杂的非人脸区域剔除掉。排序原则是简单的放在前边(如图中分类器1),因为通常来说人脸只占一小部分,所以可以很放心地在前几层分类器就拒绝掉大部分非人脸区域。只要前一级拒绝了,就不在进入下一级分类器,这可以大大提高速度。其本质是一颗退化决策树。
对弱分类器和强分类器进行训练后,即可获得用于人脸识别的算法模型。
(4) 建立级联分类器。
在正常的图像中,人脸区域只是占了很小的一部分,如果使用所有的特征进行训练的话,运算量非常大。级联为了简化任务,把若干个Adaboost分类器级联起来,一开始使用少量的特征将大部分的非人脸区域剔除掉,后面再利用更复杂的特征将更复杂的非人脸区域剔除掉。排序原则是简单的放在前边(如图中分类器1),因为通常来说人脸只占一小部分,所以可以很放心地在前几层分类器就拒绝掉大部分非人脸区域。只要前一级拒绝了,就不在进入下一级分类器,这可以大大提高速度。其本质是一颗退化决策树。
图3 级联分类器
附:Python调用OpenCV中Viola-jones算法的源代码:
from PyQt5 import QtCore,QtGui,QtWidgets
import sys
import cv2
class Ui_MainWindow(QtWidgets.QWidget):
def __init__(self,parent=None):
super().__init__(parent) #父类的构造函数
self.timer_camera = QtCore.QTimer() #定义定时器,用于控制显示视频的帧率
self.cap = cv2.VideoCapture() #视频流
self.CAM_NUM = 0 #为0时表示视频流来自笔记本内置摄像头
self.set_ui() #初始化程序界面
self.slot_init() #初始化槽函数
'''程序界面布局'''
def set_ui(self):
self.__layout_main = QtWidgets.QHBoxLayout() #总布局
self.__layout_fun_button = QtWidgets.QVBoxLayout() #按键布局
self.__layout_data_show = QtWidgets.QVBoxLayout() #数据(视频)显示布局
self.button_open_camera = QtWidgets.QPushButton('打开相机') #建立打开摄像头的按键
self.button_close = QtWidgets.QPushButton('退出') #建立用于退出程序的按键
self.button_open_camera.setMinimumHeight(50) #设置按键大小
self.button_close.setMinimumHeight(50)
self.button_close.move(10,100) #移动按键
'''信息显示'''
self.label_show_camera = QtWidgets.QLabel() #定义显示视频的Label
self.label_show_camera.setFixedSize(641,481) #给显示视频的Label设置大小为641x481
'''把按键加入到按键布局中'''
self.__layout_fun_button.addWidget(self.button_open_camera) #把打开摄像头的按键放到按键布局中
self.__layout_fun_button.addWidget(self.button_close) #把退出程序的按键放到按键布局中
'''把某些控件加入到总布局中'''
self.__layout_main.addLayout(self.__layout_fun_button) #把按键布局加入到总布局中
self.__layout_main.addWidget(self.label_show_camera) #把用于显示视频的Label加入到总布局中
'''总布局布置好后就可以把总布局作为参数传入下面函数'''
self.setLayout(self.__layout_main) #到这步才会显示所有控件
'''初始化所有槽函数'''
def slot_init(self):
self.button_open_camera.clicked.connect(self.button_open_camera_clicked) #若该按键被点击,则调用button_open_camera_clicked()
self.timer_camera.timeout.connect(self.show_camera) #若定时器结束,则调用show_camera()
self.button_close.clicked.connect(self.close) #若该按键被点击,则调用close(),注意这个close是父类QtWidgets.QWidget自带的,会关闭程序
'''槽函数之一'''
def button_open_camera_clicked(self):
if self.timer_camera.isActive() == False: #若定时器未启动
flag = self.cap.open(self.CAM_NUM) #参数是0,表示打开笔记本的内置摄像头,参数是视频文件路径则打开视频
if flag == False: #flag表示open()成不成功
msg = QtWidgets.QMessageBox.warning(self,'warning',"请检查相机于电脑是否连接正确",buttons=QtWidgets.QMessageBox.Ok)
else:
self.timer_camera.start(30) #定时器开始计时30ms,结果是每过30ms从摄像头中取一帧显示
self.button_open_camera.setText('关闭相机')
else:
self.timer_camera.stop() #关闭定时器
self.cap.release() #释放视频流
self.label_show_camera.clear() #清空视频显示区域
self.button_open_camera.setText('打开相机')
def show_camera(self):
flag,self.image = self.cap.read() #从视频流中读取
gray = cv2.cvtColor(self.image, cv2.COLOR_BGR2GRAY)
xmlfile = r'C:\Users\XXX\AppData\Local\Programs\Python\Python37\Lib\site-packages\cv2\data\haarcascade_frontalface_alt.xml'
face_cascade = cv2.CascadeClassifier(xmlfile)
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.15,
minNeighbors=5,
minSize=(5, 5),
)
for (x, y, w, h) in faces:
cv2.rectangle(self.image, (x, y), (x + w, y + w), (0, 255, 0), 2)
show = cv2.resize(self.image,(640,480)) #把读到的帧的大小重新设置为 640x480
show = cv2.cvtColor(show,cv2.COLOR_BGR2RGB) #视频色彩转换回RGB,这样才是现实的颜色
showImage = QtGui.QImage(show.data,show.shape[1],show.shape[0],QtGui.QImage.Format_RGB888) #把读取到的视频数据变成QImage形式
self.label_show_camera.setPixmap(QtGui.QPixmap.fromImage(showImage)) #往显示视频的Label里 显示QImage
if __name__ =='__main__':
app = QtWidgets.QApplication(sys.argv) #固定的,表示程序应用
ui = Ui_MainWindow() #实例化Ui_MainWindow
ui.show() #调用ui的show()以显示。同样show()是源于父类QtWidgets.QWidget的
sys.exit(app.exec_()) #不加这句,程序界面会一闪而过