Sift和Surf算法实现两幅图像拼接的过程是一样的,主要分为4大部分:

  • 1. 特征点提取和描述
  • 2. 特征点配对,找到两幅图像中匹配点的位置
  • 3. 通过配对点,生成变换矩阵,并对图像1应用变换矩阵生成对图像2的映射图像
  • 4. 图像2拼接到映射图像上,完成拼接

过程1、2、3没啥好说的了,关键看看步骤4中的拼接部分。这里先采用比较简单一点的拼接方式来实现:

  • 1. 找到图像1和图像2中最强的匹配点所在的位置
  • 2. 通过映射矩阵变换,得到图像1的最强匹配点经过映射后投影到新图像上的位置坐标
  • 3. 在新图像上的最强匹配点的映射坐标处,衔接两幅图像,该点左侧图像完全是图像1,右侧完全是图像2

这里拼接的正确与否完全取决于特征点的选取,如果选取的是错误匹配的特征点,拼接一定失败,所以这里选了排在第一个的最强的匹配点,作为拼接点。

测试用例一原图1:

测试用例一原图2:

Sift拼接效果:

Surf拼接效果:

本例中最强匹配点的位置在图中红色小汽车附近,可以看到有一条像折痕一样的线条,这个就是两个图片的拼接线,并且如果图1和图2在拼接处的光线条件有变化的还,拼接后在衔接处左右就会显得很突兀,如Surf拼接中。拼接效果Sift貌似要比Surf好一点。

测试用例二原图1:

测试用例二原图2:

Sift拼接效果:

Surf拼接效果:

以下是Opencv实现:

  1. #include "highgui/highgui.hpp"
  2. #include "opencv2/nonfree/nonfree.hpp"
  3. #include "opencv2/legacy/legacy.hpp"
  4. using namespace cv;
  5. //计算原始图像点位在经过矩阵变换后在目标图像上对应位置
  6. Point2f getTransformPoint(const Point2f originalPoint,const Mat &transformMaxtri);
  7. int main(int argc,char *argv[])
  8. {
  9. Mat image01=imread(argv[1]);
  10. Mat image02=imread(argv[2]);
  11. imshow("拼接图像1",image01);
  12. imshow("拼接图像2",image02);
  13. //灰度图转换
  14. Mat image1,image2;
  15. cvtColor(image01,image1,CV_RGB2GRAY);
  16. cvtColor(image02,image2,CV_RGB2GRAY);
  17. //提取特征点
  18. SiftFeatureDetector siftDetector(800); // 海塞矩阵阈值
  19. vector<KeyPoint> keyPoint1,keyPoint2;
  20. siftDetector.detect(image1,keyPoint1);
  21. siftDetector.detect(image2,keyPoint2);
  22. //特征点描述,为下边的特征点匹配做准备
  23. SiftDescriptorExtractor siftDescriptor;
  24. Mat imageDesc1,imageDesc2;
  25. siftDescriptor.compute(image1,keyPoint1,imageDesc1);
  26. siftDescriptor.compute(image2,keyPoint2,imageDesc2);
  27. //获得匹配特征点,并提取最优配对
  28. FlannBasedMatcher matcher;
  29. vector<DMatch> matchePoints;
  30. matcher.match(imageDesc1,imageDesc2,matchePoints,Mat());
  31. sort(matchePoints.begin(),matchePoints.end()); //特征点排序
  32. //获取排在前N个的最优匹配特征点
  33. vector<Point2f> imagePoints1,imagePoints2;
  34. for(int i=0;i<10;i++)
  35. {
  36. imagePoints1.push_back(keyPoint1[matchePoints[i].queryIdx].pt);
  37. imagePoints2.push_back(keyPoint2[matchePoints[i].trainIdx].pt);
  38. }
  39. //获取图像1到图像2的投影映射矩阵,尺寸为3*3
  40. Mat homo=findHomography(imagePoints1,imagePoints2,CV_RANSAC);
  41. Mat adjustMat=(Mat_<double>(3,3)<<1.0,0,image01.cols,0,1.0,0,0,0,1.0);
  42. Mat adjustHomo=adjustMat*homo;
  43. //获取最强配对点在原始图像和矩阵变换后图像上的对应位置,用于图像拼接点的定位
  44. Point2f originalLinkPoint,targetLinkPoint,basedImagePoint;
  45. originalLinkPoint=keyPoint1[matchePoints[0].queryIdx].pt;
  46. targetLinkPoint=getTransformPoint(originalLinkPoint,adjustHomo);
  47. basedImagePoint=keyPoint2[matchePoints[0].trainIdx].pt;
  48. //图像配准
  49. Mat imageTransform1;
  50. warpPerspective(image01,imageTransform1,adjustMat*homo,Size(image02.cols+image01.cols+10,image02.rows));
  51. //在最强匹配点的位置处衔接,最强匹配点左侧是图1,右侧是图2,这样直接替换图像衔接不好,光线有突变
  52. Mat ROIMat=image02(Rect(Point(basedImagePoint.x,0),Point(image02.cols,image02.rows)));
  53. ROIMat.copyTo(Mat(imageTransform1,Rect(targetLinkPoint.x,0,image02.cols-basedImagePoint.x+1,image02.rows)));
  54. namedWindow("拼接结果",0);
  55. imshow("拼接结果",imageTransform1);
  56. waitKey();
  57. return 0;
  58. }
  59. //计算原始图像点位在经过矩阵变换后在目标图像上对应位置
  60. Point2f getTransformPoint(const Point2f originalPoint,const Mat &transformMaxtri)
  61. {
  62. Mat originelP,targetP;
  63. originelP=(Mat_<double>(3,1)<<originalPoint.x,originalPoint.y,1.0);
  64. targetP=transformMaxtri*originelP;
  65. float x=targetP.at<double>(0,0)/targetP.at<double>(2,0);
  66. float y=targetP.at<double>(1,0)/targetP.at<double>(2,0);
  67. return Point2f(x,y);
  68. }

对于衔接处存在的缝隙问题,有一个解决办法是按一定权重叠加图1和图2的重叠部分,在重叠处图2的比重是1,向着图1的方向,越远离衔接处,图1的权重越来越大,图2的权重越来越低,实现平稳过渡按照这个思路优化过的代码如下:

  1. #include "highgui/highgui.hpp"
  2. #include "opencv2/nonfree/nonfree.hpp"
  3. #include "opencv2/legacy/legacy.hpp"
  4. using namespace cv;
  5. //计算原始图像点位在经过矩阵变换后在目标图像上对应位置
  6. Point2f getTransformPoint(const Point2f originalPoint,const Mat &transformMaxtri);
  7. int main(int argc,char *argv[])
  8. {
  9. Mat image01=imread(argv[1]);
  10. Mat image02=imread(argv[2]);
  11. imshow("拼接图像1",image01);
  12. imshow("拼接图像2",image02);
  13. //灰度图转换
  14. Mat image1,image2;
  15. cvtColor(image01,image1,CV_RGB2GRAY);
  16. cvtColor(image02,image2,CV_RGB2GRAY);
  17. //提取特征点
  18. SiftFeatureDetector siftDetector(800); // 海塞矩阵阈值
  19. vector<KeyPoint> keyPoint1,keyPoint2;
  20. siftDetector.detect(image1,keyPoint1);
  21. siftDetector.detect(image2,keyPoint2);
  22. //特征点描述,为下边的特征点匹配做准备
  23. SiftDescriptorExtractor siftDescriptor;
  24. Mat imageDesc1,imageDesc2;
  25. siftDescriptor.compute(image1,keyPoint1,imageDesc1);
  26. siftDescriptor.compute(image2,keyPoint2,imageDesc2);
  27. //获得匹配特征点,并提取最优配对
  28. FlannBasedMatcher matcher;
  29. vector<DMatch> matchePoints;
  30. matcher.match(imageDesc1,imageDesc2,matchePoints,Mat());
  31. sort(matchePoints.begin(),matchePoints.end()); //特征点排序
  32. //获取排在前N个的最优匹配特征点
  33. vector<Point2f> imagePoints1,imagePoints2;
  34. for(int i=0;i<10;i++)
  35. {
  36. imagePoints1.push_back(keyPoint1[matchePoints[i].queryIdx].pt);
  37. imagePoints2.push_back(keyPoint2[matchePoints[i].trainIdx].pt);
  38. }
  39. //获取图像1到图像2的投影映射矩阵,尺寸为3*3
  40. Mat homo=findHomography(imagePoints1,imagePoints2,CV_RANSAC);
  41. Mat adjustMat=(Mat_<double>(3,3)<<1.0,0,image01.cols,0,1.0,0,0,0,1.0);
  42. Mat adjustHomo=adjustMat*homo;
  43. //获取最强配对点在原始图像和矩阵变换后图像上的对应位置,用于图像拼接点的定位
  44. Point2f originalLinkPoint,targetLinkPoint,basedImagePoint;
  45. originalLinkPoint=keyPoint1[matchePoints[0].queryIdx].pt;
  46. targetLinkPoint=getTransformPoint(originalLinkPoint,adjustHomo);
  47. basedImagePoint=keyPoint2[matchePoints[0].trainIdx].pt;
  48. //图像配准
  49. Mat imageTransform1;
  50. warpPerspective(image01,imageTransform1,adjustMat*homo,Size(image02.cols+image01.cols+110,image02.rows));
  51. //在最强匹配点左侧的重叠区域进行累加,是衔接稳定过渡,消除突变
  52. Mat image1Overlap,image2Overlap; //图1和图2的重叠部分
  53. image1Overlap=imageTransform1(Rect(Point(targetLinkPoint.x-basedImagePoint.x,0),Point(targetLinkPoint.x,image02.rows)));
  54. image2Overlap=image02(Rect(0,0,image1Overlap.cols,image1Overlap.rows));
  55. Mat image1ROICopy=image1Overlap.clone(); //复制一份图1的重叠部分
  56. for(int i=0;i<image1Overlap.rows;i++)
  57. {
  58. for(int j=0;j<image1Overlap.cols;j++)
  59. {
  60. double weight;
  61. weight=(double)j/image1Overlap.cols; //随距离改变而改变的叠加系数
  62. image1Overlap.at<Vec3b>(i,j)[0]=(1-weight)*image1ROICopy.at<Vec3b>(i,j)[0]+weight*image2Overlap.at<Vec3b>(i,j)[0];
  63. image1Overlap.at<Vec3b>(i,j)[1]=(1-weight)*image1ROICopy.at<Vec3b>(i,j)[1]+weight*image2Overlap.at<Vec3b>(i,j)[1];
  64. image1Overlap.at<Vec3b>(i,j)[2]=(1-weight)*image1ROICopy.at<Vec3b>(i,j)[2]+weight*image2Overlap.at<Vec3b>(i,j)[2];
  65. }
  66. }
  67. Mat ROIMat=image02(Rect(Point(image1Overlap.cols,0),Point(image02.cols,image02.rows))); //图2中不重合的部分
  68. ROIMat.copyTo(Mat(imageTransform1,Rect(targetLinkPoint.x,0, ROIMat.cols,image02.rows))); //不重合的部分直接衔接上去
  69. namedWindow("拼接结果",0);
  70. imshow("拼接结果",imageTransform1);
  71. imwrite("D:\\拼接结果.jpg",imageTransform1);
  72. waitKey();
  73. return 0;
  74. }
  75. //计算原始图像点位在经过矩阵变换后在目标图像上对应位置
  76. Point2f getTransformPoint(const Point2f originalPoint,const Mat &transformMaxtri)
  77. {
  78. Mat originelP,targetP;
  79. originelP=(Mat_<double>(3,1)<<originalPoint.x,originalPoint.y,1.0);
  80. targetP=transformMaxtri*originelP;
  81. float x=targetP.at<double>(0,0)/targetP.at<double>(2,0);
  82. float y=targetP.at<double>(1,0)/targetP.at<double>(2,0);
  83. return Point2f(x,y);
  84. }

Sift拼接效果:

Surf拼接效果:

拼接处的线条消失了,也没有见突兀的光线变化,基本实现了无缝拼接。

测试用例三原图1:

测试用例三原图2:

拼接效果:

Opencv Sift和Surf特征实现图像无缝拼接生成全景图像的更多相关文章

  1. Opencv中使用Surf特征实现图像配准及对透视变换矩阵H的平移修正

    图像配准需要将一张测试图片按照第二张基准图片的尺寸.角度等形态信息进行透视(仿射)变换匹配,本例通过Surf特征的定位和匹配实现图像配准. 配准流程: 1. 提取两幅图像的Surf特征 2. 对Sur ...

  2. 【OpenCV新手教程之十八】OpenCV仿射变换 &amp; SURF特征点描写叙述合辑

    本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/33320997 作者:毛星云(浅墨)  ...

  3. Opencv 使用Stitcher类图像拼接生成全景图像

    Opencv中自带的Stitcher类可以实现全景图像,效果不错.下边的例子是Opencv Samples中的stitching.cpp的简化,源文件可以在这个路径里找到: \opencv\sourc ...

  4. python opencv SIFT,获取特征点的坐标位置

    备注:SIFT算法的实质是在不同的尺度空间上查找关键点(特征点),并计算出关键点的方向.SIFT所查找到的关键点是一些十分突出,不会因光照,仿射变换和噪音等因素而变化的点,如角点.边缘点.暗区的亮点及 ...

  5. SIFT和SURF特征(草稿)

    (草稿) https://www.cnblogs.com/gavanwanggw/p/7073905.html

  6. 【OpenCV新手教程之十七】OpenCV重映射 &amp; SURF特征点检測合辑

    本系列文章由@浅墨_毛星云 出品.转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/30974513 作者:毛星云(浅墨)  ...

  7. OpenCV教程(47) sift特征和surf特征

         在前面三篇教程中的几种角检测方法,比如harris角检测,都是旋转无关的,即使我们转动图像,依然能检测出角的位置,但是图像缩放后,harris角检测可能会失效,比如下面的图像,图像放大之前可 ...

  8. opencv surf特征点匹配拼接源码

    http://blog.csdn.net/huixingshao/article/details/42672073 /** * @file SURF_Homography * @brief SURF ...

  9. sift、surf、orb 特征提取及最优特征点匹配

    目录 sift sift特征简介 sift特征提取步骤 surf surf特征简介 surf特征提取步骤 orb orb特征简介 orb特征提取算法 代码实现 特征提取 特征匹配 附录 sift si ...

随机推荐

  1. 应该知道的30个jQuery代码开发技巧

    1. 创建一个嵌套的过滤器 .filter(":not(:has(.selected))") //去掉所有不包含class为.selected的元素 2. 重用你的元素查询 var ...

  2. Docker---(6)问题:bash: vi: command not found

    原文:Docker---(6)问题:bash: vi: command not found 版权声明:欢迎转载,请标明出处,如有问题,欢迎指正!谢谢!微信:w1186355422 https://bl ...

  3. 【Codeforces Round #450 (Div. 2) A】Find Extra One

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 模拟. 看看Y左边或右边的点个数是否<=1 [代码] #include <bits/stdc++.h> using ...

  4. [D3] Reuse Transitions in D3 v4

    D3 transitions start executing as soon as they’re created, and they’re destroyed once they end. This ...

  5. Android 迭代器 Iteraor迭代器以及foreach的使用

    Iterator是一个迭代器接口,专门用来迭代各种Collection集合,包括Set集合和List集合. Java要求各种集合都提供一个iteratot()方法,该方法返回一个Iterator用于遍 ...

  6. Java 服务端入门和进阶指南

    作者:谢龙 链接:https://www.zhihu.com/question/29581524/answer/44872235 来源:知乎 著作权归作者所有,转载请联系作者获得授权. 现在互联网上资 ...

  7. 【Codeforces Round #431 (Div. 1) B】

    [链接]h在这里写链接 [题意] 场上有 n 个点,它们分别向上与向右在不同时刻开始运动,相遇则改变移动方向,求最终这些点到达的坐标. [题解] 先把每个点的坐标都往它本该移动的方向相反的方向退ti个 ...

  8. vue配置路由

    1,首先用vue-cli搭建vue项目.这个我就不细说了,详见以前的博客 2,npm安装vue-router 3.打开router文件加下的index.js 4.导入你想跳转的组件. import z ...

  9. 使用doxygen为C/C++程序生成中文文档

    文章来自:http://www.fmddlmyy.cn/text21.html 依照约定的格式凝视源码,用工具处理凝视过的源码产生文档.通过这样的方式产生文档至少有下面优点: 便于代码和文档保持同步. ...

  10. VS2012调试C++工程DLL

    1.C++工程属性对话框 2.配置属性: (1)常规:输出目录:..\Bin\WFCrawler(调用DLL的工程)            中间目录:..\Bin\WFCrawler(调用DLL的工程 ...