Haar特征描述算子与人脸检测

目录

  • Haar-like 特征描述算子
    • 特征类别(模板)
    • 特征计算—积分图
    • 计算Haar特征值
    • Haar特征值归一化(方法不唯一)
  • Adaboost级联分类器
    • 级联分类器
      • 级联分类模型
      • 级联分类器的训练
    • XML文件
  • 程序

Haar-like 特征描述算子

特征类别(模板)

Haar(哈尔)特征模板分为三类:边缘特征、线性特征(含对角线特征)、中心特征。其结构如下图所示。
在这里插入图片描述
定义该模板的特征值为白色矩形像素和减去黑色矩形像素和。

Haar特征值反映了图像的灰度变化情况。但矩形特征只对一些简单的图形结构,如边缘、线段较敏感,所以只能描述特定走向(水平、垂直、对角)的结构。

特征计算时要保证两种颜色矩形区域中的像素数目一致。对于黑白矩形数目相等的特征,特征数值计算公式为:v=Σ白-Σ黑;
而对于黑白矩形数目相等的特征,如两个白色一个黑色,计算公式如下:v=Σ白-2*Σ黑。

通过改变特征模板的大小和位置,可在图像子窗口中穷举出大量的特征。故模板类别、大小和位置的变化,会使得一个很小的检测窗口含有非常多的矩形特征,如:在24*24像素大小的检测窗口内矩形特征数量可以达到16万个。
关于特征数量的计算可以参考:Haar特征数量计算

这样就有两个问题需要解决了:
(1)如何快速计算那么多的特征?—积分图;
(2)如何判断哪些矩形特征才是对分类器分类最有效的?—AdaBoost算法

特征计算—积分图

积分图只遍历一次图像就可以求出图像中所有区域像素和,大大的提高了图像特征值计算的效率。

我的理解是积分图相当于一个列表。开始遍历图像一次后,把每个点的积分像素值存在里面,需要计算某个区域的像素值时,只要调用这个列表里的值就可以了,不用再重复计算。

积分图的构造方式是:位置(𝑖,𝑗)处的值𝑖𝑖(𝑖,𝑗)是原图像(𝑖,𝑗)左上角方向所有像素𝑓(𝑘,𝑙)的和:
ii(i,j)=ki,ljf(k,l)ii(i,j)=\sum_{k≤i,l≤j} f(k,l)

积分图构建算法:
1、用𝑠(𝑖,𝑗)表示行方向的累加和,初始化𝑠(𝑖,−1)=0;
2、使用𝑖𝑖(𝑖,𝑗)表示一个积分图像,初始化𝑖𝑖(−1,𝑖)=0;
3、逐行扫描图像,递归计算每个像素(𝑖,𝑗)行方向的累加和𝑠(𝑖,𝑗)和积分图𝑖𝑖(𝑖,𝑗)的值: s(i,j)=s(i,j1)+f(i,j)s(i,j)=s(i,j−1)+f(i,j)

ii(i,j)=ii(i1,j)+s(i,j)ii(i,j)=ii(i−1,j)+s(i,j)

4、扫描图像一遍,当到达图像右下角像素时,积分图像𝑖𝑖就构建好了。

积分图构造好之后,图像中任何矩阵区域像素累加和都可以通过简单运算得到。

计算Haar特征值

矩形特征的特征值可以由特征端点的积分图计算出来。以矩形特征为例,如下图,使用积分图计算其特征值:

img

该矩形特征的特征值为区域A的像素值减去区域B的像素值。

用积分图计算区域A的像素值: ii(5)+ii(1)ii(2)ii(4) ii(5)+ii(1)−ii(2)−ii(4)

区域B的像素值: ii(6)+ii(2)ii(5)ii(3) ii(6)+ii(2)−ii(5)−ii(3)

所以:该矩形特征的特征值 ii(5)+ii(1)ii(2)ii(4)[ii(6)+ii(2)ii(5)ii(3)]=[ii(5)ii(4)]+[ii(3)ii(2)][ii(2)ii(1)][ii(6)ii(5)]ii(5)+ii(1)−ii(2)−ii(4)−[ii(6)+ii(2)−ii(5)−ii(3)]\\ =[ii(5)−ii(4)]+[ii(3)−ii(2)]−[ii(2)−ii(1)]−[ii(6)−ii(5)]
矩形特征的特征值,只与特征矩形的端点的积分图有关,而与图像的坐标无关。

Haar特征值归一化(方法不唯一)

从上图我们可以发现,Haar特征计算出的特征值变化范围从-2000~+6000,跨度非常大,需要进行“归一化”,压缩特征值范围。
假设当前检测窗口中的图像像素为i(x,y)i(x,y),当前检测窗口为whw∗h大小(例如上图中为20*20大小),OpenCV采用如下方式“归一化”:
1、计算检测窗口中图像的灰度值和灰度值平方和: sum=i(x,y) sum=\sum i(x,y)

sqsum=i2(x,y) sq_{sum}=\sum i^2(x,y)

2、计算平均值: mean=sumwh mean = \frac{sum}{w*h}

sqmean=sqsumwh sq_{mean}=\frac{sq_{sum}}{w*h}

3、计算归一化因子: varNormFactor=sqmeanmean2 varNormFactor=\sqrt{sq_{mean}-mean^2} 4、归一化特征值: normValue=featureValuevarNormFactor normValue=\frac{featureValue}{varNormFactor} 之后使用归一化的特征值𝑛𝑜𝑟𝑚𝑉𝑎𝑙𝑢𝑒与阈值对比。

Adaboost级联分类器

前面几块内容我们分析了Haar特征,积分图、特征值计算。这里则主要分析一下2个内容:

(1)OpenCV中的Adaboost级联分类器的结构,包括强分类器和弱分类器的形式;

(2)OpenCV自带的XML分类器中各项参数,如internalNodes和leafValues标签里面的一大堆数字的意义。

级联分类器

级联分类模型

模型是树状结构,可以用下图表示:

img

其中每一个stage都代表一级强分类器。当检测窗口通过所有的强分类器时才被认为是正样本,否则拒绝。

实际上,不仅强分类器是树状结构,强分类器中的每一个弱分类器也是树状结构。由于每一个强分类器对负样本的判别准确度非常高,所以一旦发现检测到的目标位负样本,就不继续调用下面的强分类器,减少了很多的检测时间。

级联分类器的训练

1.训练原理
首先需要训练出每一个弱分类器,然后把每个弱分类器按照一定的组合策略,得到一个强分类器,训练出多个强分类器后按照级联的方式把它们组合在一块,就会得到我们最终想要的Haar分类器。

img
弱分类器
一个弱分类器就是一个基本和上图类似的决策树,最基本的弱分类器只包含一个Haar-like特征,也就是它的决策树只有一层,被称为树桩(stump)。

以20*20图像为例,78,460个特征,如果直接利用AdaBoost训练,那么工作量是极其极其巨大的。所以必须筛选,筛选出T个优秀的特征值(即最优弱分类器),然后把这个T个最优弱分类器传给AdaBoost进行训练。

如何筛选最优弱分类器
现在有人脸样本2000张,非人脸样本4000张,这些样本都经过了归一化,大小都是20x20的图像。
对于78,460中的任一特征fif_i,计算该特征在这2000人脸样本、4000非人脸样本上的值,这样就得到6000个特征值。将这些特征值排序,然后选取一个最佳的特征值,在该特征值下,对于特征fif_i来说,样本的加权错误率最低。

在确定了训练子窗口中(20x20的图像)的矩形特征数量(78460)和特征值后,需要对每一个特征ff,训练一个弱分类器h(x,f,ρ,Θ)h(x,f,ρ,Θ)

h(x,f,ρ,Θ)={1,ρf(x)<ρΘ0,otherh(x,f,ρ,Θ)=\begin {cases}1, {ρf(x)<ρΘ}\\ 0, {other}\end{cases}

其中𝑓为特征,Θ为阈值,ρ指示不等号的方向,𝑥代表一个检测子窗口。对每个特征𝑓,训练一个弱分类器h(x,f,ρ,Θ)h(x,f,ρ,Θ),就是确定𝑓的最优阈值,使得这个弱分类器对所有的训练样本分类误差最低。

2.训练步骤
在弱分类器训练的过程中,训练采用的照片一般都是20*20左右的小图片,弱分类器训练的具体步骤:

1、对于每个特征 𝑓,计算所有训练样本的特征值,并将其排序:

2、扫描一遍排好序的特征值,对排好序的表中的每个元素,计算下面四个值:
全部正例的权重和𝑇+;
全部负例的权重和𝑇−;
该元素前之前的正例的权重和𝑆+;
该元素前之前的负例的权重和𝑆−;

3、选取当前元素的特征值Fk,jF_{k,j}和它前面的一个特征值Fk,j1F_{k,j-1}之间的数作为阈值,这个阈值对应的弱分类器将当前元素前的所有元素分为人脸(或非人脸),而把当前元素后(含)的所有元素分为非人脸(或人脸)。
该阈值的分类误差为: e=min(S++(TS),S+(T+S+)) e=min(S^++(T^--S^-),S^-+(T^+-S^+))

于是,从头到尾扫描一遍就可以为弱分类器选择使分类误差最小的阈值,即选取了一个最佳弱分类器。

由于一共有78,460个特征、因此会得到78,460个最优弱分类器,在78,460个特征中,我们选取错误率最低的特征,用来判断人脸,同时用此分类器对样本进行分类,并更新样本的权重。

强分类器的训练步骤:

1.、给定训练样本集(xi,yi),i=1,2,3,N\left(x_{i}, y_{i}\right), i=1,2,3, \ldots N,共N个样本,yiy_i取值为0(负样本)或者1(正样本);设人脸正样本的数量为n1n_1,负样本数量为n2n_2; T为训练的最大循环次数;

2.、初始化样本权重为1n1+n2\frac{1}{n_1+n_2},即为训练样本的初始概率分布;

3、for t=1,...Tfor\ t=1,...T:
①权重归一化 ωt,i=ωt,ij1nωt,j ω_{t,i}=\frac{ω_{t,i}}{\sum\limits_{j-1}^{n}ω_{t,j}}

②对每个(种)特征fjf_j,训练一个弱分类器hjh_j,每个分类器只使用一种Haar特征进行训练。分类误差为: εj=iωihj(xi)yi ε_j=\sum\limits_{i}ω_i|h_j(x_i)-y_i|

③从②确定的弱分类器中,找出一个具有最小分类误差的弱分类器hth_t;

④更新每个样本对应的权重: ωt+1,i=ωt,iβt1ei \omega_{t+1, i}=\omega_{t, i} \beta_{t}^{1-e_{i}}

这里,如果样本xix_i被正确分类,则ei=0e_i=0,否则ei=1e_i=1,而 βt=εt1εt \beta_t=\frac{ε_t}{1-ε_t}

最终形成的强分类器组成为:
其中: αt=log1βt \alpha_t=log\frac{1}{\beta_t}

在使用Adaboost算法训练分类器之前,需要准备好正、负样本,根据样本特点选择和构造特征集。由算法的训练过程可知,当弱分类器对样本分类正确,样本的权重会减小;而分类错误时,样本的权重会增加。这样,后面的分类器会加强对错分样本的训练。最后,组合所有的弱分类器形成强分类器,通过比较这些弱分类器投票的加权和与平均投票结果来检测图像。

3、级联分类器的检测
训练级联分类器的目的就是为了检测的时候,更加准确,这涉及到Haar分类器的另一个体系,检测体系。
检测体系是以一幅大图片作为输入,然后对图片中进行多区域、多尺度的检测。所谓多区域,是要对图片划分多块,对每个块进行检测。
由于训练的时候用的照片一般都是20*20左右的小图片,所以对于大的人脸,还需要进行多尺度的检测,多尺度检测机制一般有两种策略:
一种是不改变搜索窗口的大小,而不断缩放图片,这种方法显然需要对每个缩放后的图片进行区域特征值的运算,效率不高;
另一种方法,不断扩大搜索窗口,进行搜索,解决了第一种方法的弱势。

无论哪一种搜索方法,都会为输入图片输出大量的子窗口图像,这些子窗口图像经过筛选式级联分类器会不断地被每一个节点筛选,抛弃或通过。

总结
从上面所述内容我们可以总结Haar分类器训练的五大步骤:

1、准备人脸、非人脸样本集;
2、计算特征值和积分图;
3、筛选出T个优秀的特征值(即最优弱分类器);
4、把这个T个最优弱分类器传给AdaBoost进行训练。
5、级联。

XML文件

OpenCV 自带了训练器和检测器。这里我们介绍的XML文件,就是OpenCV自带的检测器。

(1)使用python pip安装最新的opencv-python包:pip install opencv-python
(2)查看包的位置:pip show opencv-python image-20200614115759788
(3)进入红框的文件夹:cd d f:\cjc\imagefusion\venv\lib\site-packages(根据上一步结果选择)

(4)进入这个文件夹下的cv2目录:cd cv2
XML文件就在data/文件夹下

xml文件主要保存相关的特征矩阵,以及各个弱分类器相关的信息。

程序

import cv2
import numpy as np

haar_front_face_xml='./data/haarcascade_frontalface_default.xml'
haar_eye_xml='./data/haarcascade_eye.xml'


# 1.静态图像中的人脸检测
def StaticDetect(filename):
    # 创建一个级联分类器 加载一个 .xml 分类器文件. 它既可以是Haar特征也可以是LBP特征的分类器.
    face_cascade = cv2.CascadeClassifier(haar_front_face_xml)

    # 加载图像
    img = cv2.imread(filename)
    # 转换为灰度图
    gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # 进行人脸检测,传入scaleFactor,minNegihbors,分别表示人脸检测过程中每次迭代时图像的压缩率以及
    # 每个人脸矩形保留近似数目的最小值
    # 返回人脸矩形数组
    faces = face_cascade.detectMultiScale(gray_img, 1.3, 5)
    for (x, y, w, h) in faces:
        # 在原图像上绘制矩形
        img = cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
    cv2.namedWindow('Face Detected!')
    cv2.imshow('Face Detected!', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


# 2、视频中的人脸检测
def DynamicDetect():
    '''
    打开摄像头,读取帧,检测帧中的人脸,扫描检测到的人脸中的眼睛,对人脸绘制蓝色的矩形框,对人眼绘制绿色的矩形框
    '''
    # 创建一个级联分类器 加载一个 .xml 分类器文件. 它既可以是Haar特征也可以是LBP特征的分类器.
    face_cascade = cv2.CascadeClassifier(haar_front_face_xml)
    eye_cascade = cv2.CascadeClassifier(haar_eye_xml)

    # 打开摄像头
    camera = cv2.VideoCapture(0)
    cv2.namedWindow('Dynamic')

    while True:
        # 读取一帧图像
        ret, frame = camera.read()
        # 判断图片读取成功?
        if ret:
            gray_img = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
            # 人脸检测
            faces = face_cascade.detectMultiScale(gray_img, 1.3, 5)
            for (x, y, w, h) in faces:
                # 在原图像上绘制矩形
                cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)
                roi_gray = gray_img[y:y + h, x:x + w]
                # 眼睛检测
                eyes = eye_cascade.detectMultiScale(roi_gray, 1.03, 5, 0, (40, 40))
                for (ex, ey, ew, eh) in eyes:
                    cv2.rectangle(frame, (ex + x, ey + y), (x + ex + ew, y + ey + eh), (0, 255, 0), 2)

            cv2.imshow('Dynamic', frame)
            # 如果按下q键则退出
            if cv2.waitKey(100) & 0xff == ord('q'):
                break

    camera.release()
    cv2.destroyAllWindows()


if __name__ == '__main__':
    filename = 'face.jpg'
    StaticDetect(filename)
    #DynamicDetect()

热门文章

暂无图片
编程学习 ·

PAT 1161 Merging Linked Lists

原题链接:暂无 关键词:链表 Given two singly linked lists L 1 =a 1 →a 2 →…→a n−1 →a n L1=a1→a2→…→an−1→an and L 2 =b 1 →b 2 →…→b m−1 →b m L2=b1→b2→…→bm−1→bm . If n≥2m n≥2m , you are supposed to reverse and merge the shorter one i…
暂无图片
编程学习 ·

报表热切换是什么意思?如何做到?

热切换(Hot Swap)是指在系统不停机的情况下更换系统部件,在报表业务中则是指在不重启报表及相关应用的情况下完成对报表的维护(新增、修改、删除),冷切换则恰好相反。报表业务很不稳定,业务开展的过程中会刺激出更多查询统计需求,如果每次需求实现后都要等系统空闲(往…
暂无图片
编程学习 ·

【考试记录】Apsara Clouder基础技能认证:实现调用API接口

从今天开始,准备把阿里的认证尽可能多的考出来。原因有这么几个:研究生要毕业了,除了把论文写好,还有找工作的压力,所以想尽可能多的考出几个证来证明自己的学习能力;研究生毕业后想找个教师的工作,所以得以身作则,多学习知识,这样教学生才能有底气。知道自己现在能力…
暂无图片
编程学习 ·

leetcode 718.最长重复子数组

原题 718.最长重复子数组 2020年7月1日 每日一题题解 方法一 暴力法。 /*暴力法 @v7fgg 执行用时:2265 ms, 在所有 Java 提交中击败了5.00%的用户 内存消耗:39.3 MB, 在所有 Java 提交中击败了100.00%的用户 2020年7月1日 8:03 */ class Solution {public int findLength(int…
暂无图片
编程学习 ·

win10系统Idea运行maven项目的编码设置

编码。。。。好吧,我了解的比较少,此处仅针对本人遇到的问题整理个处理过程。今天运行个maven项目,idea控制台显示中文乱码,不管是System.out还是log4j的输出,但是log4j输出到日志文件又是正常的。查看了网上的处理办法:1、设置Idea的File Encoding2、设置Idea vmoption3…
暂无图片
编程学习 ·

mongoDB采坑

mongoDB采坑 安装问题没有权限参考 https://blog.csdn.net/qq_20084101/article/details/82261195
暂无图片
编程学习 ·

Autosar4.4:通用架构模板 - 元建模模式与模型转换(2/3)

元模型化模式是参数化的结构,当将其应用于实际参数时,会产生规则的,非参数化的结构。 结构只是由关联和聚合关联的元类的集合。 模式的好处在于,它们允许重复使用重复结构,而无需重复其定义。 本章介绍元建模模式的概念,以及它们在AUTOSAR元模型中的使用和表示法。 另一个…
暂无图片
编程学习 ·

参考nacos写的Registry注册中心

Registry注册中心 github链接:https://github.com/lzj-github/registry 麻烦大家顺便点个star,谢谢啦! 在学习了SpringCloud的各个组件的使用以及研究过相关源码后,为了更好地理解其内部实现原理,模仿了nacos的部分设计思想,自己实现了该注册中心,代码量4000+,代码中有详…
暂无图片
编程学习 ·

jvm垃圾收集算法以垃圾收集器简介

jvm垃圾收集算法以垃圾收集器简介 每天多学一点点~ 话不多说,这就开始吧… 文章目录jvm垃圾收集算法以垃圾收集器简介1.前言2.内存模型判断机制3.垃圾收集算法3.1 分代收集理论3.2 标记-复制算法3.3 标记-清除算法3.4 标记-整理算法4.常用的垃圾收集器4.1 Serial收集器(-XX:+U…
暂无图片
编程学习 ·

LeetCode 226. 翻转二叉树

目录结构1.题目2.题解1.题目翻转一棵二叉树。示例:输入:4/ \2 7/ \ / \ 1 3 6 9输出:4/ \7 2/ \ / \ 9 6 3 1备注:这个问题是受到 Max Howell 的 原问题 启发的 :谷歌:我们90%的工程师使用您编写的软件(Homebrew),但是您却无法在面试时在白板上…
暂无图片
编程学习 ·

JS Array

一、鉴别数组 typeof Array :Object (不可取) array instanof(Array) :true (可取) 二、转换方法 array.toString()返回字符串 array.valueOf() 返回数组本身 三、栈方法 pop()从尾部删除最后一个数据,并返回该值 push()在尾部加入新值,并返回加入后的…
暂无图片
编程学习 ·

共享内存实现多进程低延迟队列 10us

起因之前的博客写过通过inotify 加文件的形式来实现多进程队列的文章。这种方式在通常情况下表现不错,但是这里存在一个问题就是当消费者过慢,会产生大量的击穿内核高速缓冲区io,导致消费者卡在读取数据的瓶颈上,无法使用负载均衡等手段来提高处理能力。为了解决上述问题,…
暂无图片
编程学习 ·

「bitset」库下的便捷二进制转换

基本含义 int n;// 将要转换为二进制的数 string temp;// 承载二进制数据的字符串 bitset<8> bit;// 将要转换的数据给bit赋值,数字n将会被自动转换为二进制存储在bitset集合中 temp=bit.to_string(); // 将bitset里的数据转换为字符串形式传给temp 相关代码 #include&l…
暂无图片
编程学习 ·

web安全专题(1)注入攻击

注入攻击 对于所有的网络安全问题,从公司的发文来看,结合目前业界发现的所有对网站的攻击方式,基本都可以归类于这三类: 1、防注入; 2、防破解; 3、防范洪; 第一类、注入攻击是最常见的一种攻击,涉及的方式也有很多,例如比较常见的有,脚本注入,sql注入,日志注入,x…
暂无图片
编程学习 ·

初级Java工程师也能轻松进行JVM调优了

本文来自: PerfMa技术社区 PerfMa(笨马网络)官网Java 性能调优对于每一个Java程序员来说,是实现技术进阶必不可缺的知识了。比如:一台8G的服务器怎么配置JVM参数?我的JVM参数是否合理?如何分析内存Dump文件?如何分析线程Dump文件?等等…,你肯定或多或少都遇到过,熟知Ja…
暂无图片
编程学习 ·

JS奇淫巧技

0. 向下取整最快方式 向下取整有很多方法, Math.floor, parseInt都可以, 不过两个非(~)运算符来取整是最方便的, 而且逻辑运算很快.还可以用~~再加1来向上取整.~~3.14 > 3 ~~Math.PI > 3 ~~Math.E > 2 ~~12.98 > 12var b= 2.33 | 0 ---b=2var c= 2.33 >> …