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)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 算法学习–分酒问题(BFS)[通俗易懂]

    算法学习–分酒问题(BFS)[通俗易懂]有4个红酒瓶子,它们的容量分别是:9升,7升,4升,2升开始的状态是[9,0,0,0],也就是说:第一个瓶子满着,其它的都空着。允许把酒从一个瓶子倒入另一个瓶子,但只能把一个瓶子倒满或把一个瓶子倒空,不能有中间状态。这样的一次倒酒动作称为1次操作。假设瓶子的容量和初始状态不变,对于给定的目标状态,至少需要多少次操作才能实现?本题就是要求你编程实现最小操作次数的计算。输入:最终状…

    2022年10月19日
    1
  • 操作系统维护相关总结「建议收藏」

    操作系统维护相关总结「建议收藏」操作系统维护相关总结声明:引用请注明出处http://blog.csdn.net/lg1259156776/说明:由于前段时间遭受了电脑固态硬盘故障系统崩溃,数据未及时备份无法读取而导致的痛苦,这里

    2022年8月3日
    5
  • java.lang_java.lang.AbstractMethodError[通俗易懂]

    java.lang_java.lang.AbstractMethodError[通俗易懂]/***用户管理*/publicvoiduserManage(){render(“userManage.html”);//renderHtml(“userManage.html”);}进入方法后出现的问题。。。。哎,,各位大神帮我分析分析可好??????用的3.33.4jfinal整合shrio插件五月14,20189:21:19下午org.apache.catalina.c…

    2022年5月22日
    38
  • php开发工程师面试题知识点总结(三)–中级篇

    php开发工程师面试题知识点总结(三)–中级篇

    2022年2月12日
    38
  • request.getParameter();的意思[通俗易懂]

    request.getParameter();的意思[通俗易懂]对于httprequrest的request.getParameter()的作用,之前我只是在用它而不知道它到底有什么作用,今天看了一遍文章突然明白了其中的意思。  大致的内容如下:这个form提交请求后,在你的action中Stringname=request.getparameter(“name”);那么name的值就是“哈哈”  它是一种取参数的方法。

    2025年7月5日
    2
  • pytest 执行用例_python分布式爬虫

    pytest 执行用例_python分布式爬虫前言平常我们功能测试用例非常多时,比如有1千条用例,假设每个用例执行需要1分钟,如果单个测试人员执行需要1000分钟才能跑完当项目非常紧急时,会需要协调多个测试资源来把任务分成两部分,于是执行时间

    2022年7月31日
    6

发表回复

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

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