引用:http://blog.csdn.net/qianxin_dh/article/details/39268113
在2001年,
Viola和Jones两位大牛发表了经典的<<Rapid Object Detection using a Boosted Cascade of Simple Features>>和<<Robust Real-Time Face Detection>>,文中提出使用Haar特征和积分图方法进行人脸检测,并对AdaBoost训练出的强分类器进行级联,实现了很好的人脸检测效果。那么究竟什么是Haar特征?怎么计算Haar特征?又该如何利用Haar特征呢?
一.什么是Haar特征?
Harr特征,是计算机视觉领域中常用的一种特征描述子,用来表示我们感兴趣目标的特征,帮助我们找到目标。举例来说,
假设在人脸检测时,我们可以利用一个子窗口在待检测的图片帧中进行滑动检测(也就是将图像的指定位置转化为特征,通常情况下是采用滑动窗口技术),计算出每一位置的特征,然后用预先训练好的级连分类器对该特征进行筛选,一旦该特征通过了所有强分类器的筛选,则判定该区域为人脸。
目前常用的Haar特征有:
其中,由Viola等牛人提出的Haar特征主要是:
那
么
这这些黑白方格表示什么意义呢?这就是所谓的Haar特征计算了,将白色区域的像素和减去黑色区域的像素和,既为所求的特征值。也就是说,这些黑白方格能够将检测的目标进行量化,设想将其中一个特征模板覆盖在图像帧中,则目标区域计算出的像素特征值和非目标的像素特征值是不一样的,差值越大,分类的效果越明显,目标找的越准确。
二.积分图
上面提到了Haar特征如何进行计算,我们可以想到不同大小,不同位置的特征模板在不同图像帧中都会面临着计算特征值的问题,一般来说,
在一个24×24像素的窗口中任意排列至少可以产生数以10万计的特征,计算量是十分巨大的,因此,如何提高计算效率是将Haar特征从理论应用到实践的关键步骤。
积分图是一个数据结构,可实现子区域的快速求和。这样的求和在很多应用中是很有用的,最显著的就是在人脸识别及相关算法中应用的Haar小波。
Opencv函数:
void integral
(
InputArray
image
, OutputArray
sum
, int
sdepth
)
参 数:
image:输入图像大小为W*H
sum: 输出图像大小为(W+1)*(H+1);
sdepth:根据需要可以提供,是指积分图像的深度和类型,如
CV_32S
,
CV_32F
, or
CV_64F
.
利用这些积分图,可以计算图像的任意直立或“倾斜”的矩形区域之和。例如一个简单的例子,计算一个简单矩形区域的和,这个区域是通过角点(x1,y1),(x2,y2)定位的,这里x2>x1,y2>y1,计算如下:
在这种方式下,就可以为各种窗口大小执行快速的可变窗块相关计算。
三.代码的实现
这里我是在一副灰度图像内随便选取一个框,计算其haar特征,当然具体应用时由于haar特征对于人脸检测效果尤其好,感兴趣的朋友可以把程序进行更改,加入手动画框模块,读取视频,计算得到每帧目标框内的特征值。测试环境:vs2008+opencv2.3.1
就从main函数开始吧:
- // main.cpp
- // HaarFeature
- // Created by qianxin_dh on 14-9-13.
- // Copyright (c) 2014年 qianxin_dh. All rights reserved.
- #include "stdafx.h"
- #include <opencv2/core/core.hpp>
- #include <opencv2/highgui/highgui.hpp>
- #include <opencv2/imgproc/imgproc.hpp>
- #include "HaarFeature.h"
- using namespace cv;
- using namespace std;
- const int featureNUM=192;
- int main()
- {
- Mat image=imread("lena.bmp");
- //cvtColor(image,image,CV_RGB2GRAY);
- if (image.empty())
- {
- cout<<"Load the image error!"<<endl;
- return -1;
- }
- vector<HaarFeature> m_features;
- //用于生成特征模板
- float x[] = {0.2f, 0.4f, 0.6f, 0.8f};
- float y[] = {0.2f, 0.4f, 0.6f, 0.8f};
- float s[] = {0.2f, 0.4f};
- for (int iy = 0; iy < 4; ++iy)
- {
- for (int ix = 0; ix < 4; ++ix)
- {
- for (int is = 0; is < 2; ++is)
- {
- FloatRect r(x[ix]-s[is]/2, y[iy]-s[is]/2, s[is], s[is]); //32种尺寸
- for (int it = 0; it < 6; ++it) //这里主要实现6种hair特征,32*6=192种特征模板
- {
- m_features.push_back(HaarFeature(r, it));
- }
- }
- }
- }
- float m_feat;
- FloatRect rect(10,10,100,50);
- for(int i=0;i<featureNUM;i++){
- m_feat= m_features[i].caluHf(image,rect);
- cout<<m_feat<<" "<<endl;
- }
- return 0;
- }
头文件:
- #include <opencv2/opencv.hpp>
- #include <vector>
- #include "RECT.h"
- using namespace cv;
- class HaarFeature
- {
- public:
- HaarFeature(FloatRect& bb, int type);
- ~HaarFeature();
- float caluHf(Mat& _image,FloatRect& _rect); //计算haar特征值
- private:
- float sum(Mat& _image,IntRect& _rect);
- private:
- FloatRect m_box;
- std::vector<FloatRect> m_rects;
- std::vector<float> m_weights;
- float m_factor;
- Mat _imageIntegral;
- };
- #include "HaarFeature.h"
- #include <iostream>
- using namespace std;
- HaarFeature::HaarFeature(FloatRect& bb, int type) :
- m_box(bb)
- {
- assert(type < 6); //分别实现六种haar特征,可以参照文章开头时引用的图片进行对应
- switch (type)
- {
- case 0:
- {
- m_rects.push_back(FloatRect(bb.XMin(), bb.YMin(), bb.Width(), bb.Height()/2));
- m_rects.push_back(FloatRect(bb.XMin(), bb.YMin()+bb.Height()/2, bb.Width(), bb.Height()/2));
- m_weights.push_back(1.f);
- m_weights.push_back(-1.f);
- m_factor = 255*1.f/2;
- break;
- }
- case 1:
- {
- m_rects.push_back(FloatRect(bb.XMin(), bb.YMin(), bb.Width()/2, bb.Height()));
- m_rects.push_back(FloatRect(bb.XMin()+bb.Width()/2, bb.YMin(), bb.Width()/2, bb.Height()));
- m_weights.push_back(1.f);
- m_weights.push_back(-1.f);
- m_factor = 255*1.f/2;
- break;
- }
- case 2:
- {
- m_rects.push_back(FloatRect(bb.XMin(), bb.YMin(), bb.Width()/3, bb.Height()));
- m_rects.push_back(FloatRect(bb.XMin()+bb.Width()/3, bb.YMin(), bb.Width()/3, bb.Height()));
- m_rects.push_back(FloatRect(bb.XMin()+2*bb.Width()/3, bb.YMin(), bb.Width()/3, bb.Height()));
- m_weights.push_back(1.f);
- m_weights.push_back(-2.f);
- m_weights.push_back(1.f);
- m_factor = 255*2.f/3;
- break;
- }
- case 3:
- {
- m_rects.push_back(FloatRect(bb.XMin(), bb.YMin(), bb.Width(), bb.Height()/3));
- m_rects.push_back(FloatRect(bb.XMin(), bb.YMin()+bb.Height()/3, bb.Width(), bb.Height()/3));
- m_rects.push_back(FloatRect(bb.XMin(), bb.YMin()+2*bb.Height()/3, bb.Width(), bb.Height()/3));
- m_weights.push_back(1.f);
- m_weights.push_back(-2.f);
- m_weights.push_back(1.f);
- m_factor = 255*2.f/3;
- break;
- }
- case 4:
- {
- m_rects.push_back(FloatRect(bb.XMin(), bb.YMin(), bb.Width()/2, bb.Height()/2));
- m_rects.push_back(FloatRect(bb.XMin()+bb.Width()/2, bb.YMin()+bb.Height()/2, bb.Width()/2, bb.Height()/2));
- m_rects.push_back(FloatRect(bb.XMin(), bb.YMin()+bb.Height()/2, bb.Width()/2, bb.Height()/2));
- m_rects.push_back(FloatRect(bb.XMin()+bb.Width()/2, bb.YMin(), bb.Width()/2, bb.Height()/2));
- m_weights.push_back(1.f);
- m_weights.push_back(1.f);
- m_weights.push_back(-1.f);
- m_weights.push_back(-1.f);
- m_factor = 255*1.f/2;
- break;
- }
- case 5:
- {
- m_rects.push_back(FloatRect(bb.XMin(), bb.YMin(), bb.Width(), bb.Height()));
- m_rects.push_back(FloatRect(bb.XMin()+bb.Width()/4, bb.YMin()+bb.Height()/4, bb.Width()/2, bb.Height()/2));
- m_weights.push_back(1.f);
- m_weights.push_back(-4.f);
- m_factor = 255*3.f/4;
- break;
- }
- }
- }
- HaarFeature::~HaarFeature()
- {
- }
- float HaarFeature::sum(Mat& _image,IntRect& _rect)
- {
- int xMin=_rect.XMin();
- int yMin=_rect.YMin();
- int xMax=_rect.XMin()+_rect.Width();
- int yMax=_rect.YMin()+_rect.Height();
- int tempValue=0;
- tempValue +=
- _imageIntegral.at<int>(yMin, xMin) +
- _imageIntegral.at<int>(yMax, xMax) -
- _imageIntegral.at<int>(yMin, xMax) -
- _imageIntegral.at<int>(yMax, xMin);
- //cout<<weight<<endl;
- //cout<<tempValue<<endl;
- return tempValue;
- }
- float HaarFeature::caluHf(Mat& _image,FloatRect& _rect)
- {
- int value = 0;
- integral(_image, _imageIntegral, CV_32F);
- //cout<<_imageIntegral<<" "<<endl;
- for (int i = 0; i < (int)m_rects.size(); ++i) //m_rects.size()=2;
- {
- FloatRect& r = m_rects[i];
- IntRect sampleRect((int)(_rect.XMin()+r.XMin()*_rect.Width()+0.5f), (int)(_rect.YMin()+r.YMin()*_rect.Height()+0.5f),
- (int)(r.Width()*_rect.Width()), (int)(r.Height()*_rect.Height()));
- value +=m_weights[i]*sum(_image,sampleRect); //sum函数返回的是积分图像对应的数值
- }
- return value / (m_factor*(_rect.Area())*(m_box.Area()));
- }
这里大家应该都看到了
HaarFeature类头文件中还加入了一个RECT头文件,这个头文件的作用定义两种矩形框:IntRect和FloatRect。之所以要定义这两种矩形框,是因为我们所选取的用于生成特征模板的矩形框r的值过小(r在main.cpp中),而opencv里自带的rect原型为typedef Rect_ <int> Rect,如果使用它会导致r的值为(0,0,0,0)。
RECT类的实现:
- #pragma once
- #include <iostream>
- #include <algorithm>
- template <typename T>
- class Rect
- {
- public:
- Rect() :
- m_xMin(0),
- m_yMin(0),
- m_width(0),
- m_height(0)
- {
- }
- Rect(T xMin, T yMin, T width, T height) :
- m_xMin(xMin),
- m_yMin(yMin),
- m_width(width),
- m_height(height)
- {
- }
- template <typename T2>
- Rect(const Rect<T2>& rOther) :
- m_xMin((T)rOther.XMin()),
- m_yMin((T)rOther.YMin()),
- m_width((T)rOther.Width()),
- m_height((T)rOther.Height())
- {
- }
- inline T XMin() const { return m_xMin; }
- inline T YMin() const { return m_yMin; }
- inline T Width() const { return m_width; }
- inline T Height() const { return m_height; }
- inline T Area() const { return m_width * m_height; }
- private:
- T m_xMin;
- T m_yMin;
- T m_width;
- T m_height;
- };
- typedef Rect<int> IntRect;
- typedef Rect<float> FloatRect;