目标检测的图像特征提取—Haar特征
1、Haar-like特征
?????? Haar-like特征最早是由Papageorgiou等應(yīng)用于人臉表示,Viola和Jones在此基礎(chǔ)上,使用3種類型4種形式的特征。
Haar特征分為三類:邊緣特征、線性特征、中心特征和對(duì)角線特征,組合成特征模板。特征模板內(nèi)有白色和黑色兩種矩形,并定義該模板的特征值為白色矩形像素和減去黑色矩形像素和。Haar特征值反映了圖像的灰度變化情況。例如:臉部的一些特征能由矩形特征簡(jiǎn)單的描述,如:眼睛要比臉頰顏色要深,鼻梁兩側(cè)比鼻梁顏色要深,嘴巴比周圍顏色要深等。但矩形特征只對(duì)一些簡(jiǎn)單的圖形結(jié)構(gòu),如邊緣、線段較敏感,所以只能描述特定走向(水平、垂直、對(duì)角)的結(jié)構(gòu)。
?????對(duì)于圖中的A, B和D這類特征,特征數(shù)值計(jì)算公式為:v=Sum白-Sum黑,而對(duì)于C來(lái)說(shuō),計(jì)算公式如下:v=Sum白-2*Sum黑;之所以將黑色區(qū)域像素和乘以2,是為了使兩種矩形區(qū)域中像素?cái)?shù)目一致。
???? 通過改變特征模板的大小和位置,可在圖像子窗口中窮舉出大量的特征。上圖的特征模板稱為“特征原型”;特征原型在圖像子窗口中擴(kuò)展(平移伸縮)得到的特征稱為“矩形特征”;矩形特征的值稱為“特征值”。
????? 矩形特征可位于圖像任意位置,大小也可以任意改變,所以矩形特征值是矩形模版類別、矩形位置和矩形大小這三個(gè)因素的函數(shù)。故類別、大小和位置的變化,使得很小的檢測(cè)窗口含有非常多的矩形特征,如:在24*24像素大小的檢測(cè)窗口內(nèi)矩形特征數(shù)量可以達(dá)到16萬(wàn)個(gè)。這樣就有兩個(gè)問題需要解決了:(1)如何快速計(jì)算那么多的特征?---積分圖大顯神通;(2)哪些矩形特征才是對(duì)分類器分類最有效的?---如通過AdaBoost算法來(lái)訓(xùn)練(這一塊這里不討論,具體見http://blog.csdn.net/zouxy09/article/details/7922923)
?
2、Haar-like特征的計(jì)算—積分圖
?????? 積分圖就是只遍歷一次圖像就可以求出圖像中所有區(qū)域像素和的快速算法,大大的提高了圖像特征值計(jì)算的效率。
?????? 積分圖主要的思想是將圖像從起點(diǎn)開始到各個(gè)點(diǎn)所形成的矩形區(qū)域像素之和作為一個(gè)數(shù)組的元素保存在內(nèi)存中,當(dāng)要計(jì)算某個(gè)區(qū)域的像素和時(shí)可以直接索引數(shù)組的元素,不用重新計(jì)算這個(gè)區(qū)域的像素和,從而加快了計(jì)算(這有個(gè)相應(yīng)的稱呼,叫做動(dòng)態(tài)規(guī)劃算法)。積分圖能夠在多種尺度下,使用相同的時(shí)間(常數(shù)時(shí)間)來(lái)計(jì)算不同的特征,因此大大提高了檢測(cè)速度。
???????我們來(lái)看看它是怎么做到的。
?????? 積分圖是一種能夠描述全局信息的矩陣表示方法。積分圖的構(gòu)造方式是位置(i,j)處的值ii(i,j)是原圖像(i,j)左上角方向所有像素的和:
??
????????
積分圖構(gòu)建算法:
1)用s(i,j)表示行方向的累加和,初始化s(i,-1)=0;
2)用ii(i,j)表示一個(gè)積分圖像,初始化ii(-1,i)=0;
3)逐行掃描圖像,遞歸計(jì)算每個(gè)像素(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)掃描圖像一遍,當(dāng)?shù)竭_(dá)圖像右下角像素時(shí),積分圖像ii就構(gòu)造好了。? ? ?
? ? 任意矩形區(qū)域內(nèi)像素積分。由圖像的積分圖可方便快速地計(jì)算圖像中任意矩形內(nèi)所有像素灰度積分。如下圖2.3所示,點(diǎn)1的積分圖像ii1的值為(其中Sum為求和) :?ii1=Sum(A)
????
? ? 同理,點(diǎn)2、點(diǎn)3、點(diǎn)4的積分圖像分別為:
????ii2=Sum(A)+Sum(B);??????ii3=Sum(A)+Sum(C);????ii4=Sum(A)+Sum(B)+Sum(C)+Sum(D);
????矩形區(qū)域D內(nèi)的所有像素灰度積分可由矩形端點(diǎn)的積分圖像值得到:
?????????????????????Sum(D)=ii1+ii4-(ii2+ii3)????????????(1)
????(2) 特征值計(jì)算
????矩形特征的特征值是兩個(gè)不同的矩形區(qū)域像素和之差,由(1)式可以計(jì)算任意矩形特征的特征值,下面以圖2.1中特征原型A為例說(shuō)明特征值的計(jì)算。
????
? ? 如圖2.4 所示,該特征原型的特征值定義為:
????Sum(A)-Sum(B)
????根據(jù)(1)式則有:Sum(A)=ii4+ii1-(ii2+ii3);????Sum(B)=ii6+ii3-(ii4+ii5);
????所以此類特征原型的特征值為:
?????????????????(ii4-ii3)-(ii2-ii1)+(ii4-ii3)-(ii6-ii5)
????另示:運(yùn)用積分圖可以快速計(jì)算給定的矩形之所有象素值之和Sum(r)。假設(shè)r=(x,y,w,h),那么此矩形內(nèi)部所有元素之和等價(jià)于下面積分圖中下面這個(gè)式子:
?????????????????Sum(r) = ii(x+w,y+h)+ii(x-1,y-1)-ii(x+w,y-1)-ii(x-1,y+h)
? ??而Haar-like特征值無(wú)非就是兩個(gè)矩陣像素和的差,同樣可以在常數(shù)時(shí)間內(nèi)完成。所以矩形特征的特征值計(jì)算,只與此特征矩形的端點(diǎn)的積分圖有關(guān),所以不管此特征矩形的尺度變換如何,特征值的計(jì)算所消耗的時(shí)間都是常量。這樣只要遍歷圖像一次,就可以求得所有子窗口的特征值。
3、Haar-like矩形特征拓展
???????? Lienhart R.等對(duì)Haar-like矩形特征庫(kù)作了進(jìn)一步擴(kuò)展,加入了旋轉(zhuǎn)45。角的矩形特征。擴(kuò)展后的特征大致分為4種類型:邊緣特征、線特征環(huán)、中心環(huán)繞特征和對(duì)角線特征:
?????? 在特征值的計(jì)算過程中,黑色區(qū)域的權(quán)值為負(fù)值,白色區(qū)域的權(quán)值為正值。而且權(quán)值與矩形面積成反比(使兩種矩形區(qū)域中像素?cái)?shù)目一致);
豎直矩陣特征值計(jì)算:
???? 對(duì)于豎直矩陣,與上面2處說(shuō)的一樣。
45°旋角的矩形特征計(jì)算:
????? 對(duì)于45°旋角的矩形,我們定義RSAT(x,y)為點(diǎn)(x,y)左上角45°區(qū)域和左下角45°區(qū)域的像素和。
???????
用公式可以表示為:
為了節(jié)約時(shí)間,減少重復(fù)計(jì)算,可按如下遞推公式計(jì)算:
而計(jì)算矩陣特征的特征值,是位于十字行矩形RSAT(x,y)之差。可參考下圖:
4、代碼實(shí)現(xiàn)
? ?這里我是在一副灰度圖像內(nèi)隨便選取一個(gè)框,計(jì)算其haar特征,當(dāng)然具體應(yīng)用時(shí)由于haar特征對(duì)于人臉檢測(cè)效果尤其好,感興趣的朋友可以把程序進(jìn)行更改,加入手動(dòng)畫框模塊,讀取視頻,計(jì)算得到每幀目標(biāo)框內(nèi)的特征值。
main函數(shù): // 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) //這里主要實(shí)現(xiàn)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; }
HaarFeature類: 頭文件: #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); //計(jì)算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); //分別實(shí)現(xiàn)六種haar特征,可以參照文章開頭時(shí)引用的圖片進(jìn)行對(duì)應(yīng) 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函數(shù)返回的是積分圖像對(duì)應(yīng)的數(shù)值 } return value / (m_factor*(_rect.Area())*(m_box.Area())); }
? ? 這里大家應(yīng)該都看到了HaarFeature類頭文件中還加入了一個(gè)RECT頭文件,這個(gè)頭文件的作用定義兩種矩形框:IntRect和FloatRect。之所以要定義這兩種矩形框,是因?yàn)槲覀兯x取的用于生成特征模板的矩形框r的值過小(r在main.cpp中),而opencv里自帶的rect原型為typedef Rect_ <int> Rect,如果使用它會(huì)導(dǎo)致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;
[1]?http://blog.csdn.net/smf0504/article/details/51322255 [2]?http://blog.csdn.net/zouxy09/article/details/7929570
總結(jié)
以上是生活随笔為你收集整理的目标检测的图像特征提取—Haar特征的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: pp助手正版激活失败怎么办
- 下一篇: Pedestrian Identific