SIFT(尺度不变特征变换,Scale-Invariant Feature Transform)是在计算机视觉领域中检测和描述图像中局部特征的算法,该算法于1999年被David Lowe提出,并于2004年进行了补充和完善。该算法应用很广,如目标识别,自动导航,图像拼接,三维建模,手势识别,视频跟踪等。不幸的是,该算法已经在美国申请了专利,专利拥有者为Lowe所在的加拿大不列颠哥伦比亚大学,因此我们不能随意使用它。
由于SIFT算法在计算机视觉的特征检测和特征描述中表现十分优异,因此该算法一经提出,就引起了广泛的关注。国内外对其研究的人很多,相关的资料也很多。在csdn中,有几位作者的文章对SIFT算法介绍得很详细,如网名为:zddhub、Rachel Zhang和xiaowei_cqu。由王永明和王贵锦所编著的,由国防工业出版社出版的《图像局部不变性特征与描述》也对该算法进行了详细的介绍。上述文章对我帮助很大。
经过一段时间的研究,并结合opencv中的源代码,自认为对SIFT算法有了一定的认识和体会,因此也写了一篇关于SIFT的文章。该文章共分为三部分,首先是SIFT的算法分析,然后是opencv的源码分析,最后是应用实例。在算法分析中,注意了每个细节的描述;在源码分析中,基本做到了每条代码都进行了注释;在应用实例中,列举了特征提取和图像匹配两个实例。
本想把这篇文章发表在这里,但文章比较长(有30多页),关键是公式太多,复制粘贴太麻烦,排版也不好。因此我把这篇文章分别上传到了csdn和百度文库,地址是:
http://wenku.baidu.com/view/d7edd2464b73f242336c5ffa.html
http://download.csdn.net/detail/zhaocj/8294793
可以在线阅读,也可以免费下载(在这里,鄙视那些设置下载权限和积分的人!!!)。如果上述方法都不方便,可以留下email,向我索要。文章中错误的地方欢迎指正!
为了不使这篇博文过于空洞,我把这篇文章的第三部分粘贴在这里。
首先给出的是特征点的检测:
#include "opencv2/core/core.hpp" #include "highgui.h" #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/features2d/features2d.hpp" #include "opencv2/nonfree/nonfree.hpp" using namespace cv; //using namespace std; int main(int argc, char** argv) { Mat img = imread("box_in_scene.png"); SIFT sift; //实例化SIFT类 vector<KeyPoint> key_points; //特征点 // descriptors为描述符,mascara为掩码矩阵 Mat descriptors, mascara; Mat output_img; //输出图像矩阵 sift(img,mascara,key_points,descriptors); //执行SIFT运算 //在输出图像中绘制特征点 drawKeypoints(img, //输入图像 key_points, //特征点矢量 output_img, //输出图像 Scalar::all(-1), //绘制特征点的颜色,为随机 //以特征点为中心画圆,圆的半径表示特征点的大小,直线表示特征点的方向 DrawMatchesFlags::DRAW_RICH_KEYPOINTS); namedWindow("SIFT"); imshow("SIFT", output_img); waitKey(0); return 0; }
结果如下图所示:
上面的程序需要说明一点的是,如果需要改变SIFT算法的默认参数,可以通过实例化SIFT类的时候更改,例如我们只想检测20个特征点,则实例化SIFT的语句为:
SIFT sift(20);
下面给出利用描述符进行图像匹配的实例:
#include "opencv2/core/core.hpp" #include "highgui.h" #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/features2d/features2d.hpp" #include "opencv2/nonfree/nonfree.hpp" #include "opencv2/legacy/legacy.hpp" using namespace cv; using namespace std; int main(int argc, char** argv) { //待匹配的两幅图像,其中img1包括img2,也就是要从img1中识别出img2 Mat img1 = imread("box_in_scene.png"); Mat img2 = imread("box.png"); SIFT sift1, sift2; vector<KeyPoint> key_points1, key_points2; Mat descriptors1, descriptors2, mascara; sift1(img1,mascara,key_points1,descriptors1); sift2(img2,mascara,key_points2,descriptors2); //实例化暴力匹配器——BruteForceMatcher BruteForceMatcher<L2<float>> matcher; //定义匹配器算子 vector<DMatch>matches; //实现描述符之间的匹配,得到算子matches matcher.match(descriptors1,descriptors2,matches); //提取出前30个最佳匹配结果 std::nth_element(matches.begin(), //匹配器算子的初始位置 matches.begin()+29, // 排序的数量 matches.end()); // 结束位置 //剔除掉其余的匹配结果 matches.erase(matches.begin()+30, matches.end()); namedWindow("SIFT_matches"); Mat img_matches; //在输出图像中绘制匹配结果 drawMatches(img1,key_points1, //第一幅图像和它的特征点 img2,key_points2, //第二幅图像和它的特征点 matches, //匹配器算子 img_matches, //匹配输出图像 Scalar(255,255,255)); //用白色直线连接两幅图像中的特征点 imshow("SIFT_matches",img_matches); waitKey(0); return 0; }
结果如下图所示:
程序是通过距离测度实现两幅图像描述符之间的比较的,距离越小,匹配性越好,越说明这两个描述符表示的是同一事物。描述符的匹配结果保存在匹配器算子matches中。如果直接使用matches,匹配效果并不好,因为它是尽可能的匹配所有的描述符。因此我们要进行筛选,只保留那些好的结果。在这里,我们利用排序,选择距离最小的前30个匹配结果,并进行输出。另外,matcher.match函数中,两个描述符的顺序一定不能写反,否则运行会出错。