本文主要是介绍目标检测的图像特征提取—Haar特征,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1、Haar-like特征
Haar-like特征最早是由Papageorgiou等应用于人脸表示,Viola和Jones在此基础上,使用3种类型4种形式的特征。
Haar特征分为三类:边缘特征、线性特征、中心特征和对角线特征,组合成特征模板。特征模板内有白色和黑色两种矩形,并定义该模板的特征值为白色矩形像素和减去黑色矩形像素和。Haar特征值反映了图像的灰度变化情况。例如:脸部的一些特征能由矩形特征简单的描述,如:眼睛要比脸颊颜色要深,鼻梁两侧比鼻梁颜色要深,嘴巴比周围颜色要深等。但矩形特征只对一些简单的图形结构,如边缘、线段较敏感,所以只能描述特定走向(水平、垂直、对角)的结构。
对于图中的A, B和D这类特征,特征数值计算公式为:v=Sum白-Sum黑,而对于C来说,计算公式如下:v=Sum白-2*Sum黑;之所以将黑色区域像素和乘以2,是为了使两种矩形区域中像素数目一致。
通过改变特征模板的大小和位置,可在图像子窗口中穷举出大量的特征。上图的特征模板称为“特征原型”;特征原型在图像子窗口中扩展(平移伸缩)得到的特征称为“矩形特征”;矩形特征的值称为“特征值”。
矩形特征可位于图像任意位置,大小也可以任意改变,所以矩形特征值是矩形模版类别、矩形位置和矩形大小这三个因素的函数。故类别、大小和位置的变化,使得很小的检测窗口含有非常多的矩形特征,如:在24*24像素大小的检测窗口内矩形特征数量可以达到16万个。这样就有两个问题需要解决了:(1)如何快速计算那么多的特征?---积分图大显神通;(2)哪些矩形特征才是对分类器分类最有效的?---如通过AdaBoost算法来训练(这一块这里不讨论,具体见http://blog.csdn.net/zouxy09/article/details/7922923)
2、Haar-like特征的计算—积分图
积分图就是只遍历一次图像就可以求出图像中所有区域像素和的快速算法,大大的提高了图像特征值计算的效率。
积分图主要的思想是将图像从起点开始到各个点所形成的矩形区域像素之和作为一个数组的元素保存在内存中,当要计算某个区域的像素和时可以直接索引数组的元素,不用重新计算这个区域的像素和,从而加快了计算(这有个相应的称呼,叫做动态规划算法)。积分图能够在多种尺度下,使用相同的时间(常数时间)来计算不同的特征,因此大大提高了检测速度。
我们来看看它是怎么做到的。
积分图是一种能够描述全局信息的矩阵表示方法。积分图的构造方式是位置(i,j)处的值ii(i,j)是原图像(i,j)左上角方向所有像素的和:
积分图构建算法:
1)用s(i,j)表示行方向的累加和,初始化s(i,-1)=0;
2)用ii(i,j)表示一个积分图像,初始化ii(-1,i)=0;
3)逐行扫描图像,递归计算每个像素(i,j)行方向的累加和s(i,j)和积分图像ii(i,j)的值
s(i,j)=s(i,j-1)+f(i,j)
ii(i,j)=ii(i-1,j)+s(i,j)
4)扫描图像一遍,当到达图像右下角像素时,积分图像ii就构造好了。
任意矩形区域内像素积分。由图像的积分图可方便快速地计算图像中任意矩形内所有像素灰度积分。如下图2.3所示,点1的积分图像ii1的值为(其中Sum为求和) : ii1=Sum(A)
同理,点2、点3、点4的积分图像分别为:
如图2.4 所示,该特征原型的特征值定义为:
而Haar-like特征值无非就是两个矩阵像素和的差,同样可以在常数时间内完成。所以矩形特征的特征值计算,只与此特征矩形的端点的积分图有关,所以不管此特征矩形的尺度变换如何,特征值的计算所消耗的时间都是常量。这样只要遍历图像一次,就可以求得所有子窗口的特征值。
3、Haar-like矩形特征拓展
Lienhart R.等对Haar-like矩形特征库作了进一步扩展,加入了旋转45。角的矩形特征。扩展后的特征大致分为4种类型:边缘特征、线特征环、中心环绕特征和对角线特征:
在特征值的计算过程中,黑色区域的权值为负值,白色区域的权值为正值。而且权值与矩形面积成反比(使两种矩形区域中像素数目一致);
竖直矩阵特征值计算:
对于竖直矩阵,与上面2处说的一样。
45°旋角的矩形特征计算:
对于45°旋角的矩形,我们定义RSAT(x,y)为点(x,y)左上角45°区域和左下角45°区域的像素和。
用公式可以表示为:
为了节约时间,减少重复计算,可按如下递推公式计算:
而计算矩阵特征的特征值,是位于十字行矩形RSAT(x,y)之差。可参考下图:
4、代码实现
这里我是在一副灰度图像内随便选取一个框,计算其haar特征,当然具体应用时由于haar特征对于人脸检测效果尤其好,感兴趣的朋友可以把程序进行更改,加入手动画框模块,读取视频,计算得到每帧目标框内的特征值。
// main.cpp
// HaarFeature #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)。
#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;
这篇关于目标检测的图像特征提取—Haar特征的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!