【自己动手,丰衣食足】系列
Haar特征是一种很早就被提出的图像特征提取算法,后面还经过了几次改进。Haar特征能够很好地运用于人脸识别技术,当然很多目标检测技术中对目标图像的特征提取也可以使用Haar特征。当我们使用opencv自带的cascade分类器时可以选择Haar特征作为训练样本数据的特征描述子,然后将特征描述子作为样本数据送入cascade分类器中,就可以通过Adaboost级联分类算法来训练用于图像识别和目标检测的分类器。我是在使用opencv自带的cascade分类器时候接触到了Haar特征提取,当时使用的时候,我是调用的是opencv库中Haar特征提取的接口,Haar特征提取的算法原理我并没有深究,因为计算机视觉课程布置一项手动实现一种图像特征提取算法的作业。。。。。我顺势就把Haar特征提取的算法原理学习了一下,并将其实现。
网上有太多的Haar特征提取算法的原理介绍,这里推荐一篇:
https://blog.csdn.net/lanxuecc/article/details/52222369原理过程已经介绍得非常详细。
- 理解Haar特征提取的关键点就在于对积分图的理解以及如何利用积分图去计算给定矩形模板的特征值。Haar特征提取算法在提取某一张图片的特征的时候,首先会计算该图片的积分图,一次性计算完积分图并保存下来为后面的像素值加和计算提供了直接的计算结果,这里运用到了动态规划的算法思想,是一种典型的用空间换时间的做法。计算完积分图之后,每次对图片中任意位置的矩形内像素之和的计算都由原本的O(n^2)计算复杂度变成了O(1),不得不说对于Haar特征提取这种特征提取机制,积分图的运用是一种非常聪明的做法。
- 关于Haar特征的特征维数,其实这个问题很多介绍Haar特征的博客里面都没有提到。如果对任意一种尺寸的模板(还没有算模板的种类)出现在图片上任意位置的特征值都进行计算,在一张仅仅25*25的图片上都能提取出上万维的特征向量。可想而知Haar特征的大小控制得当非常重要,如果要计算某一个尺寸模板的特征值,那么为了保证多个尺度的模板都能够充分地反应该图片在该尺度模板下的特征,就应该让多个尺度的模板下在图像创厚重进行充分地滑动。既要控制特征向量的维数,又要保证特征模板的充分滑动,因此控制滑动步长和模板的尺度伸缩速度非常重要。
代码如下:
定义的MyHaar类的头文件myhaar.h
#ifndef MY_HAAR #define MY_HAAR #include <opencv2/opencv.hpp> #include <iostream> //定义多种模板 enum MODEL_TYPE{ VERTICAL=1,HORIZONTAL=2,CENTER=3 }; struct model{ std::vector<cv::Rect> rects;//保存一个模板里面的多个矩形 std::vector<char> flag;//记录每个矩形颜色,1代表白色,-1代表黑色 MODEL_TYPE type; }; //定义HAAR类 class MyHaar{ public: MyHaar(); //计算Haar特征的接口,参数:原图、特征模板、步长、缩放速度 void compute(cv::Mat &src,std::vector<double> &descriptor,model m,int step_x=4,int step_y=4,float mutil=1.5); private: cv::Mat integral_img;//积分图 void generate_integral_image(cv::Mat &image);//生成积分图 double compute_sum_of_rect(cv::Rect r);//计算矩形内像素值之和 void mutil_transform(model &m,float mutil);//对模板进行伸缩变换 void model_move(model &m,int bios_x,int bios_y);//模板平移 void x_reset(model &m);//重置模板的x坐标为0 void y_reset(model &m);//重置模板的y坐标为0 }; #endif // MY_HAAR
定义的MyHaar类的源文件myhaar.cpp
#include "my_haar.h" using namespace std; using namespace cv; MyHaar::MyHaar(){} void MyHaar::compute(cv::Mat &src,vector<double> &descriptor,model m,int step_x,int step_y,float mutil){ //生成积分图 generate_integral_image(src); vector<Rect>::iterator iter=m.rects.begin(); Rect total_model=*iter; for(iter=m.rects.begin()+1;iter!=m.rects.end();iter++) total_model=total_model|*iter; // cout<<total_model.width<<" "<<total_model.height<<endl; while((total_model.width<=src.cols&&total_model.width>=1)&&(total_model.height<=src.rows&&total_model.height>=1)){ //当前模板在目标窗口中进行滑动 for(y_reset(m);m.rects[0].y+total_model.height<=src.rows;model_move(m,0,step_y)){ for(x_reset(m);m.rects[0].x+total_model.width<=src.cols;model_move(m,step_x,0)){ //计算当前模板特征值 double sum=0; for(int i=0;i<m.rects.size();i++) sum+=m.flag[i]*compute_sum_of_rect(m.rects[i]); descriptor.push_back(sum);//将特征值保存至descriptor if(m.rects[0].x+total_model.width+step_x>src.cols) break; } if(m.rects[0].y+total_model.height+step_y>src.rows) break; } //伸缩变换 mutil_transform(m,mutil); //重新计算total_rect vector<Rect>::iterator iter=m.rects.begin(); total_model=*iter; for(iter=m.rects.begin()+1;iter!=m.rects.end();iter++) total_model=total_model|*iter; } } //生成积分图 void MyHaar::generate_integral_image(Mat &img){ cv::integral(img,integral_img,CV_64F); } //计算矩形像素值之和 double MyHaar::compute_sum_of_rect(Rect r){ int x=r.x; int y=r.y; int width=r.width; int height=r.height; double sum; sum=integral_img.at<double>(x,y)+integral_img.at<double>(x+width,y+height) -integral_img.at<double>(x,y+height)-integral_img.at<double>(x+width,y); return sum; } //模板进行伸缩变换 void MyHaar::mutil_transform(model &m, float mutil){ //坐标归零 m.rects[0].x=0; m.rects[0].y=0; //宽高伸缩 for(vector<Rect>::iterator iter=m.rects.begin();iter!=m.rects.end();iter++) iter->width=iter->width*mutil,iter->height=iter->height*mutil; //矩形位置重定位 switch(m.type){ case 1: m.rects[1].x=m.rects[0].x+m.rects[0].width; break; case 2: m.rects[1].y=m.rects[0].y+m.rects[0].height; break; case 3: m.rects[1].x=m.rects[0].x+m.rects[0].width; m.rects[2].x=m.rects[1].x+m.rects[1].width; break; default:break; } } //模板平移 void MyHaar::model_move(model &m, int bios_x, int bios_y){ for(vector<Rect>::iterator iter=m.rects.begin();iter!=m.rects.end();iter++){ iter->x+=bios_x; iter->y+=bios_y; } } //重置模板的x坐标为0 void MyHaar::x_reset(model &m){ m.rects[0].x=0; switch(m.type){ case 1: m.rects[1].x=m.rects[0].x+m.rects[0].width; break; case 2: m.rects[1].y=m.rects[0].y+m.rects[0].height; break; case 3: m.rects[1].x=m.rects[0].x+m.rects[0].width; m.rects[2].x=m.rects[1].x+m.rects[1].width; break; default:break; } } //重置模板的y坐标为0 void MyHaar::y_reset(model &m){ m.rects[0].y=0; switch(m.type){ case 1: m.rects[1].x=m.rects[0].x+m.rects[0].width; break; case 2: m.rects[1].y=m.rects[0].y+m.rects[0].height; break; case 3: m.rects[1].x=m.rects[0].x+m.rects[0].width; m.rects[2].x=m.rects[1].x+m.rects[1].width; break; default:break; } }
main函数main.cpp:
#include <iostream> #include <opencv2/opencv.hpp> #include "my_haar.h" using namespace std; using namespace cv; //Mat image2=(Mat_<unsigned char> << ); model m_vertical; model m_horizontal; model m_center; void init_model(){ //模板vertical Rect r=Rect(0,0,2,4); m_vertical.rects.push_back(r); m_vertical.flag.push_back(1); r=Rect(2,0,2,4); m_vertical.rects.push_back(r); m_vertical.flag.push_back(-1); m_vertical.type=VERTICAL; //模板horizontal r=Rect(0,0,4,2); m_horizontal.rects.push_back(r); m_horizontal.flag.push_back(-1); r=Rect(0,2,4,2); m_horizontal.rects.push_back(r); m_horizontal.flag.push_back(1); m_horizontal.type=HORIZONTAL; //模板center r=Rect(0,0,2,4); m_center.rects.push_back(r); m_center.flag.push_back(1); r=Rect(2,0,4,4); m_center.rects.push_back(r); m_center.flag.push_back(-1); r=Rect(6,0,2,4); m_center.rects.push_back(r); m_center.flag.push_back(1); } int main(){ init_model(); MyHaar mh; Mat image=imread("lena.jpg"); //imshow("1",image); cvtColor(image,image,CV_RGB2GRAY); Mat src; cout<<"OK"<<endl; vector<double> descriptor; mh.compute(image,descriptor,m_vertical); // cout<<descriptor.size(); for(vector<double>::iterator iter=descriptor.begin();iter!=descriptor.end();iter++) cout<<*iter<<' '; waitKey(0); return 0; }
main.cpp中只定义了三种模板,如果要添加其他类型的模板,可以在main.cpp中添加并在init()函数中初始化。代码中步长默认值为4,伸缩速度为1.5,运行代码得到三种模板中某一个模板的特征值的维度都有几千维。
其中的每一个数值都代表了一个模板在某一个尺度在图片中的某一个位置计算得到的特征值,该代码中的模板种类很少也没有实现后来Haar特征提取算法所提出的斜的模板,因此提取来的特征运用于分类器的训练和检测效果应该没有保证,改代码只是基于Haar的原理进行了一次流程的重现。如果我们定义了多种矩形模板并能自动选择一个合适的维度,应该就能将由此提取出来的超高维特征向量送入分类其中进行训练。