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


相关推荐

  • Redis创建高可用集群教程【Windows环境】

    模仿的过程中,加入自己的思考和理解,也会有进步和收获。在这个互联网时代,在高并发和高流量可能随时爆发的情况下,单机版的系统或者单机版的应用已经无法生存,越来越多的应用开始支持集群,支持分布式部署了。而Redis作为缓存服务器的比较出色的一员,它在出生的时候就被设置支持集群,本篇就是介绍Redis集群的介绍和搭建过程!使用的平台是Windows,搭建的思路和Linux上基本一致! 墙…

    2022年2月27日
    41
  • TestDisk使用教程

    TestDisk使用教程修复一个读取不出盘符,数据的硬盘

    2025年8月2日
    5
  • mysql命令窗口_HLOOKUP函数

    mysql命令窗口_HLOOKUP函数窗口:记录集合窗口函数:在满足某些条件的记录集合上执行的特殊函数,对于每条记录都要在此窗口内执行函数。有的函数随着记录的不同,窗口大小都是固定的,称为静态窗口;有的函数则相反,不同的记录对应着不同的窗口,称为滑动窗口。1.窗口函数和普通聚合函数的区别:①聚合函数是将多条记录聚合为一条;窗口函数是每条记录都会执行,有几条记录执行完还是几条。②聚合函数也可以用于窗口函数。2.窗口函数的基…

    2022年10月4日
    4
  • HashTable的数组和连接两种实现方法(Java版本号)

    HashTable的数组和连接两种实现方法(Java版本号)

    2022年1月22日
    48
  • 大数据开发工程师需要具备哪些技能?[通俗易懂]

    目录:1.典型需求2.40K以上专家必备技能3.项目中的迷宫场景部件制作4.Hadoop生态核心原理一、典型需求(互联网公司)二、40K以上专家必备技能三、大数从业者角色分类四、Hadoop生态核心原理1.大数据整体画像 数据流程 数据技术 2.大数据平台整体画像 大数据平台逻辑划…

    2022年4月16日
    48
  • 前端进大厂难吗_java程序员怎么才能进大厂

    前端进大厂难吗_java程序员怎么才能进大厂冰河肝了一个月整理的并发编程知识体系,这应该是全网最全的并发编程知识体系了,学完进大厂不再难,强烈建议收藏!!

    2022年8月22日
    8

发表回复

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

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