Haar特征提取算法的实现

Haar特征提取算法的实现自己动手 丰衣食足 系列 nbsp nbsp nbsp nbsp nbsp nbsp nbsp nbsp Haar 特征是一种很早就被提出的图像特征提取算法 后面还经过了几次改进 Haar 特征能够很好地运用于人脸识别技术 当然很多目标检测技术中对目标图像的特征提取也可以使用 Haar 特征 当我们使用 opencv 自带的 cascade 分类器时可以选择 Haar 特征作为训练样本数据的特征描述子 然后将特征描述子作为样本数据送入 cascade 分类器中 就可以通过 Adab

【自己动手,丰衣食足】系列

        Haar特征是一种很早就被提出的图像特征提取算法,后面还经过了几次改进。Haar特征能够很好地运用于人脸识别技术,当然很多目标检测技术中对目标图像的特征提取也可以使用Haar特征。当我们使用opencv自带的cascade分类器时可以选择Haar特征作为训练样本数据的特征描述子,然后将特征描述子作为样本数据送入cascade分类器中,就可以通过Adaboost级联分类算法来训练用于图像识别和目标检测的分类器。我是在使用opencv自带的cascade分类器时候接触到了Haar特征提取,当时使用的时候,我是调用的是opencv库中Haar特征提取的接口,Haar特征提取的算法原理我并没有深究,因为计算机视觉课程布置一项手动实现一种图像特征提取算法的作业。。。。。我顺势就把Haar特征提取的算法原理学习了一下,并将其实现。

网上有太多的Haar特征提取算法的原理介绍,这里推荐一篇:https://blog.csdn.net/lanxuecc/article/details/原理过程已经介绍得非常详细。

个人总结:

  • 理解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; //这里使用Mat::at函数需要注意第一参数为行数对应的y和高度height,第二个参数对应才是列数对应的x和宽度width sum=integral_img.at<double>(y,x)+integral_img.at<double>(y+height,x+width) -integral_img.at<double>(y+height,x)-integral_img.at<double>(y,x+width); 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特征提取算法所提出的斜的模板,因此提取来的特征运用于分类器的训练和检测效果应该没有保证,改代码只是基于Haar的原理进行了一次流程的重现。如果我们定义了多种矩形模板并能自动选择一个合适的维度,应该就能将由此提取出来的超高维特征向量送入分类其中进行训练。

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/233541.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 操作系统虚拟存储技术_虚拟存储

    操作系统虚拟存储技术_虚拟存储虚拟存储管理   在前面总结了集中存储管理的刚上,要求作业的逻辑地址空间连续的存放主存储器的某个区域中。当主存储器中没有足够大的区域是,则作业是无法装入的,或必须移动某些作业后才能装入。是否有可能吧作业的连续逻辑地址空间分散到几个不连续的主存区域,且仍能使作业正确执行呢?若可行的话,则可充分利用主存空间有可减少移动所花费的开销。不仅如此,还可采用虚拟存储管理技

    2022年9月25日
    3
  • 移位寄存器-Verilog

    移位寄存器-Verilog//五位循环右移moduleregister_right( inputclk, input [4:0]data_in, outputreg[4:0]data_out); always@(posedgeclk)begin data_out<=({data_in[0],data_in[4:1]});endendmodule

    2022年7月16日
    11
  • map:根据 value 找 key ?

    map:根据 value 找 key ?在之前的学习中,我们在使用map的时候,都是利用key找value。之前我们使用的函数是find,若存在,返回查找到的指向第一个key的迭代器,若不存在,返回尾后迭代器。反过头来想一想,我们可不可以根据value找key呢?答案是肯定的。我们使用find_if+lambda可以实现。返回值和find一致。实例1:std::strings="c";autofin…

    2022年7月23日
    12
  • java snmp walk,SNMPWALK 用法详解

    java snmp walk,SNMPWALK 用法详解(1)Net-SNMPSNMPWALK(1)NAMEsnmpwalk-communicateswithanetworkentityusingSNMPGETNEXTrequests.SYNOPSISsnmpwalk[APPL…

    2022年6月29日
    29
  • 第四章:activiti RuntimeService设置获和取流程变量,及与taskService的区别,开始和完成任务时设置流程变量[通俗易懂]

    第四章:activiti RuntimeService设置获和取流程变量,及与taskService的区别,开始和完成任务时设置流程变量[通俗易懂]第四章:activiti RuntimeService设置获和取流程变量,及与taskService的区别,开始和完成任务时设置流程变量

    2022年4月23日
    148
  • GDI 总结三: CImage类使用「建议收藏」

    GDI 总结三: CImage类使用「建议收藏」若对您有所启发欢迎打赏古典小说网致力于打造极致阅读体验首创卡拉OK读书方式首创,桌面大屏幕TXT阅读方式前言CImage类是基于GDI+的,但是这里为什么要讲归于GDI?主要是基于这样的考虑:在GDI+环境中,我们可以直接使用GDI+,没多少必要再使用CImage类…

    2022年6月29日
    24

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号