viola jones人脸检测原理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了viola jones人脸检测原理相关的知识,希望对你有一定的参考价值。

参考技术A

Viola-jones人脸检测算法是一种基于滑动窗口的目标检测算法,但它却克服了滑动窗口检测带来的低效问题,可以用于实时人脸检测,主要归功于以下三点:

我参考论文[1]实现了Viola Jones中提到的attention cascade检测框架,此处是 github传送门 。

下面进一步详细介绍整个检测原理。

基于滑窗的目标检测基本原理很简单,首先构建一个classifier(分类器),以人脸检测为例,分类器的工作是判断给定大小的图像的是否为人脸,用该分类器从左至右从上到下扫描整幅图像,扫描获取的部分图像称为子窗(文章中子窗大小为24x24像素),当分类器判断子窗是人脸时,即完成了人脸检测。

这样处理有个问题,如果图像中包含的人脸变大了,此时采用固定大小的子窗就无法进行检测。通常有两种解决方法,1. 采用image-pyramid(图像金字塔),也就是通过resize获得多种不同大小图像并堆叠在一起,用固定大小分类器同时对所有图像进行扫描;2. 采用不同大小的分类器进行扫描。文章中用到的是第二种方法,尽管如此,虽然避免了调整图像大小带来的计算开销,但不同大小的分类器意味着有更多子窗需要进行处理。

如何构建一个足够快的分类器来对每个子窗进行快速判断。

分类器的构建有两种方式,一种是pixel-based(基于像素),另一种是feature-based(基于特征)。当把神经网络作为图像分类器时,输入是图像的像素值,即基于像素的分类器。用图像像素作为输入不包含任何和待分类目标有关的特定信息,往往训练这种分类器需要大量数据,并且分类过程较慢。基于特征的分类器就是先针对图像进行特征提取(HOG特征,SIFT特征等),再利用获取的特征进行分类。这种分类器不需要大量训练数据,且计算量一般会在特征计算部分,相对较小。

文章采用的是基于特征的分类器,选取了一种较为简单的特征即haar-like特征。利用矩形个数对可以将haar-like特征分为三类,分别由两个,三个,和四个 大小相同 的矩形组成。全部列举出来可以分为以下(a)(b)(c)(d)(e)五类(注意是五类不是五个,具体有多少个haar-like特征是由子窗大小决定的)。如下图所示(文章[1]中的图)。

当子窗大小给定后,我们可以用五个参数唯一确定 一个 haar-like特征,即特征种类(a/b/c/d/e),左上角x轴坐标,左上角y轴坐标,矩形的长,矩形的宽。对应的特征值等于位于白色矩形框中像素值总和减去位于黑色矩形框中的像素值总和。文章中用到的子窗大小为24x24像素,可以计算出来总共有162336个特征(把在子窗中所有可能位置和可能大小的特征全部列举出来)。利用haar-like特征进行分类只需两步:

haar-like特征有两个优点,第一是它是scale-invariant(不随图片大小而改变)的,第二是可以通过积分图像快速计算。简单的说下第一点的含义,例如我们用24x24像素的训练样本训练获取一组haar-like特征和对应的门限值,当对图像进行扫描时,子窗大小调整为SxS像素,此时只需将特征中的矩形大小按同样比例进行缩放(门限值同样需要缩放),计算所得的特征值依然是有效的。
积分图像是受卷积的微分性质启发而定义一种数据结构。积分图像定义:

其中 为积分图像, 为原图像。积分图像中 位置处的像素值等于原图中位于 的左侧和上方的所有像素值之和。有了积分图像我们就可以快速计算haar-like特征,以特征(a)为例,如下图所示。

S1到S6是积分图像在这六个顶点上的值。该特征值等于位于A中的像素总和减去位于B中的像素总和,而A中像素总和等于S5+S1-S2-S4,B中像素总和等于S6+S2-S3-S5,并且无论矩形多大,我们总能在固定时间内计算出特征值(6次索引操作和少量的加法乘法计算)。积分图像只需计算一次后续可以一直使用,事实上在算法实现时,我们只需保存样本的积分图像,原图像反而不用保存。

现在找到了一类特征用于构建分类器,和快速计算该类特征的方法。分类器是由一组特征构成的,而不是一个,如何找到一组有效的特征。

文章列举了前人的一些特征选取方法(此处就不列举了),它们虽然取得了一定的效果,但最终选出来的特征数量还是太多。文章将adaBoost算法用于特征选取(创新点),即每次训练的弱分类器的过程看做特征选取的过程,一次从162336个特征中选取一个特征(同时还包括了对应的门限值,极性,加权误差)。

adaboost算法就不详细介绍了,它的基本思想是训练一系列“弱”分类器,组成一个committee(即每个弱分类器都有投票权,但是权重不同,加权误差越小的弱分类器权重越大)。adaboost采用迭代训练方式,给定一个t阶committee,如何寻找第t+1个弱分类器和对应的权重,以最小化在一定分布下的训练样本的加权指数损失。这个优化过程可以转换为对训练样本的分布进行调整(即增大上一轮错误判断的样本的权重,减小正确判断的样本权重),在调整后的样本分布下寻找最小化加权0-1损失的弱分类器并计算对应的加权0-1损失。

可以利用adaboost找到一组特征构成分类器,使得该分类器有极高的准确率和召回率(这种分类器势必会有较大的计算量),这样会导致图像中的每一个子窗都享有同等的计算量,扫描一整幅图会有几十万甚至上百万子窗,总体计算量依然很大。实际上一幅图像中只有极少可能包含人脸的位置是我们感兴趣的,其他不包含人脸的子窗我们希望能够快速筛除,将更精细的计算用于包含人脸的子窗。

文章引入了attention-cascade的机制(注意力级联),即训练多个分类器进行级联,替代单一的分类器。结构如下图所示(文章[3]中的图)。

上图所示的分类器有三级,上一级的输出是下一级的输入,只有预测为正的样本才能传递给下一级,预测为负的样本直接舍弃。大部分非人脸子窗往往在前几级分类器就被舍弃,这样使得扫描每个子窗所需的平均计算量大大减小。

分类器是一级一级训练之后级联起来的,训练分类器时,整个级联分类器的假负率(fpr_overall)有一个训练目标(文章[1]中设置为10e-7),同时每一级有一对训练目标,即假正率和假负率。每级分类器训练的思想是在假负率极低的情况下(文章[1]中设置为0.005)尽量得到一个较低的假正率(文章中[1]中设置为0.5),即我们保证在正样本尽可能多的通过该级分类器的情况下尽量筛除更多的负样本。文章[3]通过一个松弛量来调节假正率和假负率。

下一级用到的训练数据是由所有的正样本和上一级输出的假正样本组成的,这样训练的好处是可以让处于级联后半部分的分类器“看到”更多负样本数据,缺点是训练后期假正样本很难获取,训练时间会比较长。

尽管我们获取了一个级联分类器,但依然不能保证对同一幅图中的一张人脸只会检测到一次(子窗相对人脸有所便宜或者缩放子窗大小都可能导致重复检测),如何消除重复检测,获得更好的检测效果。

文章[3]中说的较为简略,主要是针对检测框构建并查集,并对并查集中的候选框求平均得出最终的检测框。

文章[1]中是采用连通分量算法,计算每种大小检测框的置信度,根据置信度选取最终结果,但前提是检测器在图像中扫描的步进必须是1个像素,处理时间可能会比较长。

只能用于正脸检测,如果人脸朝屏幕内外或者在屏幕平面上旋转均有可能失效
在背景较亮,人脸较暗的情况下可能失效。
在有遮挡的情况下大概率失效。

Python中的Viola-Jones,带有openCV,检测嘴巴和鼻子

【中文标题】Python中的Viola-Jones,带有openCV,检测嘴巴和鼻子【英文标题】:Viola-Jones in Python with openCV, detection mouth and nose 【发布时间】:2016-11-11 18:55:31 【问题描述】:

我在Python 中有一个算法Viola-Jones。我正在使用haarcascade xml,它是从openCV 根文件加载的。但是openCV中没有任何嘴巴和鼻子的xml文件,所以我从EmguCV下载了这些文件。人脸检测结果还可以,但是眼睛检测不好,鼻子和嘴巴检测很差。我尝试更改face_cascade.detectMultiScale中的参数,但完全没有帮助。


我的代码:

import cv2
import sys

def facedet(img):
    face_cascade = cv2.CascadeClassifier('/home/kattynka/opencv/data/haarcascades/haarcascade_frontalface_default.xml')
    eye_cascade = cv2.CascadeClassifier('/home/kattynka/opencv/data/haarcascades/haarcascade_eye.xml')
    mouth_cascade = cv2.CascadeClassifier('/home/kattynka/opencv/data/haarcascades/haarcascade_mcs_mouth.xml')
    nose_cascade = cv2.CascadeClassifier('/home/kattynka/opencv/data/haarcascades/haarcascade_mcs_nose.xml')

    img = cv2.imread(img)
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

    faces = face_cascade.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)
        roi_gray = gray[y:y+h, x:x+w]
        roi_color = img[y:y+h, x:x+w]
        eyes = eye_cascade.detectMultiScale(roi_gray)
        nose =  nose_cascade.detectMultiScale(roi_gray)
        mouth = mouth_cascade.detectMultiScale(roi_gray)

        for (ex,ey,ew,eh) in eyes:
            cv2.rectangle(roi_color, (ex,ey), (ex+ew, ey+eh), (0,255,0), 2)
        for (nx, ny, nw, nh) in nose:
            cv2.rectangle(roi_color, (nx, ny), (nx + nw, ny + nh), (0, 0, 255), 2)
        for (mx, my, mw, mh) in mouth:
            cv2.rectangle(roi_color, (mx, my), (mx + mw, my + mh), (0, 0, 0), 2)

    cv2.namedWindow('image', cv2.WINDOW_NORMAL)
    cv2.imshow('image',img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


if __name__ == '__main__':
    #img = sys.argv[1]
    facedet(img)

我的问题

我做错了什么?有没有简单的解决方案,这会给我一个更好的结果?


输出:

【问题讨论】:

请注意:这些照片是公开的吗?它们属于你吗?否则,请删除。 照片是公开的,从ibug.doc.ic.ac.uk/resources/facial-point-annotations下载。 【参考方案1】:

这对我来说非常有效。

我发现如果你把脸分成两部分,让眼睛在顶部寻找眼睛,在下部寻找嘴巴,效果非常好。

face
--------
| eyes |
|------|
|mouth |
--------

这是我对下面代码所做的粗略说明。

我知道我使用的级联是smile,但嘴巴似乎不起作用。

import cv2
import sys

mouthCascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_smile.xml')
faceCascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
eyeCascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_eye.xml')
video_capture = cv2.VideoCapture(0)

while True:
    # Capture frame-by-frame
    ret, frame = video_capture.read()

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    mouth = mouthCascade.detectMultiScale(gray, 1.3, 5)
    faces = faceCascade.detectMultiScale(
                gray,
                scaleFactor=1.1,
                minNeighbors=5,
                minSize=(30, 30)
            )
            # Draw a rectangle around the faces
    for (x, y, w, h) in faces:
        cv2.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)
            # Draw a rectangle around the faces
        roi_gray_mouth = gray[y+(int(h/2)):y+h, x:x+w]
        roi_color_mouth = frame[y+(int(h/2)):y+h, x:x+w]

        roi_gray_eye = gray[y-(int(h/2)):y+h, x:x+w]
        roi_color_eye = frame[y-(int(h/2)):y+h, x:x+w]

        mouth = mouthCascade.detectMultiScale(roi_gray_mouth)
        eyes = eyeCascade.detectMultiScale(roi_gray_eye)
        for (ex,ey,ew,eh) in mouth:
            cv2.rectangle(roi_color_mouth, (ex, ey), (ex+ew, ey+eh), (0, 255, 0), 2)

        for (eex,eey,eew,eeh) in eyes:
            d = int(eew / 2)
            cv2.circle(roi_color_eye, (int(eex + eew / 4) + int(d / 2), int(eey + eeh / 4) + int(d / 2)), int(d) ,(0,0,255),2)

    # Display the resulting frame
    cv2.imshow('Video', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

# When everything is done, release the capture
video_capture.release()
cv2.destroyAllWindows()

【讨论】:

【参考方案2】:

导入 cv2 导入系统

face_cascade = cv2.CascadeClassifier('/home/kattynka/opencv/data/haarcascades/haarcascade_frontalface_default.xml')
eye_cascade = cv2.CascadeClassifier('/home/kattynka/opencv/data/haarcascades/haarcascade_eye.xml')
mouth_cascade = cv2.CascadeClassifier('/home/kattynka/opencv/data/haarcascades/haarcascade_mcs_mouth.xml')
nose_cascade = cv2.CascadeClassifier('/home/kattynka/opencv/data/haarcascades/haarcascade_mcs_nose.xml')

img = cv2.imread(img)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

faces = face_cascade.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)
    roi_gray = gray[y:y+h, x:x+w]
    roi_color = img[y:y+h, x:x+w]
    eyes = eye_cascade.detectMultiScale(gray, 1.3, 5)
    nose =  nose_cascade.detectMultiScale(gray, 1.3, 5)
    mouth = mouth_cascade.detectMultiScale(gray, 1.7, 11)

    for (ex,ey,ew,eh) in eyes:
        cv2.rectangle(img, (ex,ey), (ex+ew, ey+eh), (0,255,0), 2)
    for (nx, ny, nw, nh) in nose:
        cv2.rectangle(img, (nx, ny), (nx + nw, ny + nh), (0, 0, 255), 2)
    for (mx, my, mw, mh) in mouth:
        cv2.rectangle(img, (mx, my), (mx + mw, my + mh), (0, 0, 0), 2)

cv2.namedWindow('image', cv2.WINDOW_NORMAL)
cv2.imshow('image',img)
cv2.waitKey(0)
cv2.destroyAllWindows()

你可以试试这个代码。它对我有用。

【讨论】:

【参考方案3】:

Haar 级联对面部表现良好,但对较小的单个部分表现不佳。一个更好的解决方案是一起检测所有的面部标志。一个很好的算法是“One Millisecond Face Alignment with an Ensemble of Regression Trees by Vahid Kazemi 和 Josephine Sullivan,CVPR 2014”,在 Dlib (http://dlib.net/face_landmark_detection.py.html) 中实现。

【讨论】:

以上是关于viola jones人脸检测原理的主要内容,如果未能解决你的问题,请参考以下文章

Viola-Jones 人脸检测器如何处理多种尺寸的人脸?

matlab中的人脸检测(viola-jones)

Viola-Jones 人脸检测:像素之间的高对比度

Viola-Jones 的人脸检测声称拥有 18 万个特征

有没有办法在不使用 viola-jones 算法的情况下用 opencv 检测人脸? [关闭]

如果在 Viola-jones 人脸检测方法中,每一轮 boosting 都选择相同的 Haar 特征怎么办?