OpenCV:SURF算法浅析

引子: 课题需要SURF特征提取算法,在运动中提取摄像头图像中的特征点,并进行跟踪匹配,以此估计运动状态。开始找到了SIFT算法,SIFT特征提取具有极强的适应能力,但运算量稍大,后来就有了SURF特征提取算法,简化了计算量,保持了较高的性能,是性价比很不错的算法。开始并不知道OpenCV的存在,后来的后来发现OpenCV中已经有了SURF算法,感叹于技术发展之快(要知道SIFT是Low在2004年系统的提出的,SURF是在2006年才被Bay等提出的),感谢Low, Bay, et al. 感谢Internet、感谢Google、感谢Intel、感谢OpenCV、感谢Liu Liu(OpenCV中实现SURF算法的作者)、最重要的感谢祖国、感谢全世界最大的局域网。

在庸长的开场白中SURF登台了!!!令各位看官失望的是,引言很长,然内容不多,为什么?我是菜鸟!我怕谁!菜鸟看不懂当然写不多了。

正文开始了,在OpenCV(据说是1.1以后的版本)中包含了SURF算法,并且还有一个使用SURF的例子,这里使用的是OpenCV2.1。在OpenCV的安装目录下/samples/c 文件夹中一个叫 find_obj.cpp 的文件,这是个应用SURF算法寻找一本书的例子。同目录下还有一对于的可执行文件 find_obj.exe,可以先运行一下看看。来看find_obj.cpp

1、这个程序的框架

从入口 main() 开始慢慢道来

const char* object_filename = argc == 3 ? argv[1] : "box.png";
const char* scene_filename = argc == 3 ? argv[2] : "box_in_scene.png";
main函数前两行,判断是否有图片文件名的参数传入,没有则使用同目录下的"box.png"和"box_in_scene.png"
紧接着创建内存块“CvMemStorage* storage = cvCreateMemStorage(0);”坦白地承认:具体的不懂
cvNamedWindow("Object", 1); //建立两个窗口:物体和对比,第一个参数是窗口名称,第二个参数是Flag。
cvNamedWindow("Object Correspond", 1); //据说Flag只支持一个参数 CV_WINDOW_AUTOSIZE,可能CV_WINDOW_AUTOSIZE = 1 吧。
static CvScalar colors[] =  // 建立类似调色板的东西,colors[0],表示红色,colors[8],表示白色
{                                             // 下面的程序也只用到了这两种颜色,红色的圈儿和白色的线儿。
    {{0,0,255}},                      // 不知道为什么这里弄了九个,吓人吗?
    {{0,128,255}},
    {{0,255,255}},
    {{0,255,0}},
    {{255,128,0}},
    {{255,255,0}},
    {{255,0,0}},
    {{255,0,255}},
    {{255,255,255}}
};
IplImage* object = cvLoadImage( object_filename, CV_LOAD_IMAGE_GRAYSCALE );     
IplImage* image = cvLoadImage( scene_filename, CV_LOAD_IMAGE_GRAYSCALE );
//载入图像,如果为彩色转换为灰度图像,或者说以灰度的模式载入图像。   
//后面的if()就是判断是否正确载入了图像,过!
IplImage* object_color = cvCreateImage(cvGetSize(object), 8, 3);
cvCvtColor( object, object_color, CV_GRAY2BGR );
//上面两行依据灰度图像建立彩色图像,虽然目前实际上的数据是黑白的,但是后面可以添加红色圆圈
CvSeq *objectKeypoints = 0, *objectDescriptors = 0;// 表示指向特征点及其描述符的结构体的指针
CvSeq *imageKeypoints = 0, *imageDescriptors = 0;// CvSeq 为可动态增长元素序列,是所有OpenCV动态数据结构的基础
CvSURFParams params = cvSURFParams(500, 1);//SURF参数设置:阈值500,生成128维描述符
// cvSURFParams 函数原型如下:
CvSURFParams cvSURFParams(double threshold, int extended)
{
    CvSURFParams params;
    params.hessianThreshold = threshold; // 特征点选取的 hessian 阈值
    params.extended = extended; // 是否扩展,1 - 生成128维描述符,0 - 64维描述符
    params.nOctaves = 4; 
    params.nOctaveLayers = 2;
    return params;
}
往下
double tt = (double)cvGetTickCount(); //计时
cvExtractSURF( object, 0, &objectKeypoints, &objectDescriptors, storage, params );
cvExtractSURF( image, 0, &imageKeypoints, &imageDescriptors, storage, params );
//提取图像中的特征点,函数原型:
CVAPI(void) cvExtractSURF( const CvArr* img, const CvArr* mask,
                   CvSeq** keypoints, CvSeq** descriptors,
                   CvMemStorage* storage, CvSURFParams params, int useProvidedKeyPts CV_DEFAULT(0) );
第3、4个参数返回结果:特征点和特征点描述符,数据类型是指针的指针,真麻烦,但是后面的例程中有如何将这些数据从指针的指针中提取出来,所以这里就不管指针的指针是什么东西了。关于对特征点的描述见第二小节,关于特征点描述符见第三小节(三,中国传统文化对三情有独钟,怎么如隔三秋、三日不绝、三月不知菜味<本人菜鸟吗,喜是食白菜的鸟人>,新时代新气象也给“三”赋予新的含义,为发扬传统文化,故本菜鸟喜欢什么都分割为1、2、3)
//后面的因为不太关心没仔细看,大概就是生成匹配对比的图像即:"correspond"
//然后,根据是否定义了 "USE_FLANN" 采用不同的匹配方法,flann 和 非flann
//最后,在object图像中使用红色圆圈画出特征点位置及所处尺度的大小
//在correspond 图像中显示匹配结果,用白色线段相连。

2、特征点的描述,例如 objectKeypoints
定义:CvSeq *objectKeypoints = 0;
对于描述特征点和特征点的描述是基于一种 结构体CvSeq,结构CvSeq是所有OpenCV动态数据结构的基础,内部结构:
int flags; /* micsellaneous flags */ \ //不懂
int header_size; /* size of sequence header */ \ // 头大小,具体不懂
struct CvSeq* h_prev; /* previous sequence */ \//类似链表中的东西
struct CvSeq* h_next; /* next sequence */ \                //同上
struct CvSeq* v_prev; /* 2nd previous sequence */ \//同上
struct CvSeq* v_next; /* 2nd next sequence */ \         //同上
int total; /* total number of elements */ \ // 总共有多少个元素,objectKeypoints->total 表有多少个特征点
int elem_size;/* size of sequence element in bytes */ \ // 每个元素的大小,字节表示
char* block_max;/* maximal bound of the last block */ \ //不懂
char* ptr; /* current write pointer */ \ // 当时写指针的位置,可能就是指向实际数据的指针吧
int delta_elems; /* how many elements allocated when the sequence grows (sequence granularity) */ \
//不懂
CvMemStorage* storage; /* where the seq is stored */ \//同上
CvSeqBlock* free_blocks; /* free blocks list */ \ //同上
CvSeqBlock* first; /* pointer to the first sequence block */ //同上

其实这些弄不弄明白对于应用来说没影响,因为一组特征点被提取出来了,通常会关心:
整体的
特征点的个数: objectKeypoints->total
第i个特征点
CvSURFPoint *r = (CvSURFPoint*)cvGetSeqElem(objectKeypoints,i);
第i个特征点的:
X坐标位置(单位像素): r->pt.x
Y坐标位置(单位像素): r->pt.y
特征点所处的尺度大小 : r->size
特征点主方位角 : r->dir (0-360 表示,如何定义未知)
特征点的Hessian值 : r->hessian
还有一个参数: r->laplacian 取值为1 或 -1 不知道是什么意思,什么拉普拉斯,有木有高人给指点一下,望指导

3、特征点描述符,例如:objectDescriptors
定义:CvSeq *objectDescriptors = 0;
特征点的个数: objectDescriptors->total
对于某个特征点描述符,就没有特征点那么简单了,至少这个例程中没有涉及仅使用一个函数和对应的结构体就可以提取出所需数据的简单的方法。
通过观查特征点匹配的代码,菜鸟以为得到特征点描述符的方法为:
CvSetReader reader;
cvStartReadSeq(objectDescriptors, &reader, 0);
for(int i = 0; i < objectDescriptors->total; i++)
{
        const float* descriptor = (const float*)reader.ptr; // descriptor 指向第i 特征描述符(float数组)的指针
        // 数组长度为:reader.seq->elem_size/sizeof(float) or objectDescriptors->elem_size/sizeof(float) 
       CV_NEXT_SEQ_ELEM(reader.seq->elem_size, reader); //读取下一个特征点
}

// 同样对于 特征点 objectKeypoints 也有
CvSetReader kreader;
cvStartReadSeq(objectKeypoints , &kreader, 0);
for(int i = 0; i < objectKeypoints ->total; i++)
{
        const CvSURFPoint* kp = (const CvSURFPoint*)kreader.ptr; 
        CV_NEXT_SEQ_ELEM(reader.seq->elem_size, reader); //读取下一个特征点
        // 上面等价于 CvSURFPoint *kp = (CvSURFPoint*)cvGetSeqElem(objectKeypoints,i);(第二小节)
}

顺便提一下特征点的匹配,前面第一小节涉及到了两种特征匹配:flann 和 非flann。
flann没看明白,非flann就是计算描述符向量的距离,即每个对应元素差的平方和,对于每个需要匹配的特征点同对应的图片中的各个特征点进行距离的计算并保存最短的两个距离,当最短距离小于次短距离的0.6时认为匹配成功。函数定义如下:
int naiveNearestNeighbor( const float* vec, int laplacian,
                                                 const CvSeq* model_keypoints,
                                                 const CvSeq* model_descriptors )
vec ---- 待匹配的描述符
laplacian ---- 第二小节中的神秘变量在这里再一次出现了,当两个特征点的 laplacian 值不同时直接pass 不准配对(只有同性才能配对,OMG 太变态了太变态了EMTF怎么存在这么恶心的变量)不匹配也不计算距离
model_keypoints --------- 指向匹配候选特征点们的结构体指针
model_descriptors ------ 指向匹配候选特征点描述符们的结构体指针
如果匹配成功返回 特征点在 model_keypoints 中的索引值

计算两个描述符距离,函数定义
double compareSURFDescriptors( const float* d1, const float* d2, double best, int length )
d1 --------- 描述符1数组
d2 --------- 描述符2数组
best ------ 相当阈值,当计算的过程中距离大于 beat  的值时即刻返回当前计算的距离值,不用再计算了,木有意义,通常将目前的次近距离传递给 best,以便节省匹配时间(其实提取时间远远大于匹配时间
length  --- 描述符的维数,即描述符数组长度
返回计算的距离。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/406789.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Codeforces Round #277(Div 2) A、B、C、D、E题解

转载请注明出处&#xff1a; http://www.cnblogs.com/fraud/ ——by fraud A. Calculating Function 水题&#xff0c;判个奇偶即可 1 #include <iostream>2 #include <sstream>3 #include <ios>4 #include <iomanip>5 #include <functio…

Vue 中使用watch监听$route 无效问题

Vue 中使用watch监听$route失效问题&#xff01; 今天在项目操作中发现一个问题&#xff0c;在watch里面监听$route变化&#xff0c;发现并没有监听到&#xff0c;查阅了一些资料最终解决&#xff0c;现写出与大家共同分享&#xff0c;也忘出现此问题的同学也能顺利解决 路由词…

oa 系统后期安装服务

http://www.didibabawu.com/oa.htm

.idl与.odl的区别

From: http://hi.baidu.com/%CD%E6%CA%AF%CD%B7%B5%C4%D0%A1%BA%A2/blog/item/1b9a942fe8cfcf594ec22629.html 1. .odl和.idl在com中的功能相同. 前者是ActiveX中的 后者是ATL中. 可在前者的文件中 用 #import "XXXXX.idl" 的方式包含后者. 反过来没有试过. 2. …

Mac OS X 安装和配置ngrok

一、下载ngrok 到官网下载ngrok: https://ngrok.com/download 二、配置ngrok 进入终端&#xff0c;执行以下命令&#xff1a; open -e .bash_profile 修改.bash_profile&#xff08;以实际安装目录为准&#xff09;&#xff1a; export PATH${PATH}:/Users/xuxiansheng/D…

Windows Phone理解和运用ItemTemplate、ContentTemplate和DataTemplate

2.2.5 ItemTemplate、ContentTemplate和DataTemplate 在理解ItemTemplate、ContentTemplate和DataTemplate的关系的之前&#xff0c;我们先来看看ContentControl类和ItemsControl类。ContentControl类是内容控件的基类&#xff0c;如Button, CheckBox&#xff0c;最明显的特征就…

杂七杂八的杂记

引子&#xff1a;这里是零零星星的没有系统总结的东西&#xff0c;或者是有待继续了解之前一些零散的备忘。 高手达人慎入&#xff0c;以免贻笑大方&#xff0c;如果误入进来&#xff0c;敬请指导一二。 1、今天发现一个Vc 中一个很好的东西&#xff1a;vector。需要#include…

C#和C++结构体Socket通信

From: http://hi.baidu.com/yangliangwang/blog/item/1a0116138ff098d6f6039ea9.html 最近在用C#做一个项目的时候&#xff0c;Socket发送消息的时候遇到了服务端需要接收C结构体的二进制数据流&#xff0c;这个时候就需要用C#仿照C的结构体做出一个结构来&#xff0c;然后将其…

用 vue-route 的 beforeEach 实现导航守卫(路由跳转前验证登录)

路由跳转前做一些验证&#xff0c;比如登录验证&#xff08;未登录去登录页&#xff09;&#xff0c;是网站中的普遍需求。对此&#xff0c;vue-route 提供的 beforeRouteUpdate 可以方便地实现导航守卫&#xff08;navigation-guards&#xff09;。 导航守卫&#xff08;navig…

统计代码行数

使用终端&#xff0c;cd到项目下 find . "(" -name "*.m" -or -name "*.mm" -or -name "*.cpp" -or -name "*.h" -or -name "*.rss" ")" -print | xargs wc -l 转载于:https://www.cnblogs.com/mo-shou…

基本原理

零序电流保护的基本原理是基于基尔霍夫电流定律&#xff1a;流入电路中任一节点的复电流的代数和等于零&#xff0c;即ΣI0&#xff0c;它是用零序C.T作为取样元件。在线路与电气设备正常的情况下&#xff0c;各相电流的矢量和等于零&#xff08;对零序电流保护假定不考虑不平衡…

GPS数据格式

本菜鸟人&#xff1a;专业导航&#xff0c;善于寻北&#xff0c;精通惯导&#xff0c;兼职GPS&#xff0c;然近日因课题之需&#xff0c;方究机器之视觉也&#xff0c;乃叹人生之无常兮&#xff01;惯导今天不讲&#xff0c;讲的话可以说上6天7夜&#xff08;貌似被拍成电影了&…

C# 的内存拷贝

From: http://www.cnblogs.com/Yjianyong/archive/2010/08/05/1792976.html 近段时间在C#是直接调用动态库比较多&#xff0c;由于有时又需要使用ActiveX控件&#xff0c;往往出现很多的同名的不同命名空间的类&#xff0c;结构等&#xff0c;对不同实体之类的转换是很烦的一件…

也谈如何构建高性能服务端程序

引子&#xff1a;我接触过很多编程语言&#xff0c;接触过各种各样的服务器端开发&#xff0c;Java&#xff0c;Go&#xff0c;Ruby&#xff0c;Javascript等语言&#xff0c;Spring&#xff0c;Node.js&#xff0c;Rails等等常见服务器端框架和编程模型都有接触。这里谈一下我…

Mint-Ui的mt-search点击选中

<template><div class"page-search"><mt-search autofocus v-model"value" placeholder"搜索"> </mt-search><mt-cellv-for"item of filterResult":key"item":title"item"click.nativ…

毕业了!

忆往昔 论文犹如一座大山 日夜码字不知疲倦 写了半天改个没完 良辰美景于我何干 俱往矣 神马检索与期刊 能有几篇是真言 而今迈步从头越 翻过大山尽开颜