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实现:

#include "highgui/highgui.hpp"
#include "opencv2/nonfree/nonfree.hpp"
#include "opencv2/legacy/legacy.hpp" using namespace cv; //计算原始图像点位在经过矩阵变换后在目标图像上对应位置
Point2f getTransformPoint(const Point2f originalPoint,const Mat &transformMaxtri); int main(int argc,char *argv[])
{
Mat image01=imread(argv[1]);
Mat image02=imread(argv[2]);
imshow("拼接图像1",image01);
imshow("拼接图像2",image02); //灰度图转换
Mat image1,image2;
cvtColor(image01,image1,CV_RGB2GRAY);
cvtColor(image02,image2,CV_RGB2GRAY); //提取特征点
SiftFeatureDetector siftDetector(800); // 海塞矩阵阈值
vector<KeyPoint> keyPoint1,keyPoint2;
siftDetector.detect(image1,keyPoint1);
siftDetector.detect(image2,keyPoint2); //特征点描述,为下边的特征点匹配做准备
SiftDescriptorExtractor siftDescriptor;
Mat imageDesc1,imageDesc2;
siftDescriptor.compute(image1,keyPoint1,imageDesc1);
siftDescriptor.compute(image2,keyPoint2,imageDesc2); //获得匹配特征点,并提取最优配对
FlannBasedMatcher matcher;
vector<DMatch> matchePoints;
matcher.match(imageDesc1,imageDesc2,matchePoints,Mat());
sort(matchePoints.begin(),matchePoints.end()); //特征点排序
//获取排在前N个的最优匹配特征点
vector<Point2f> imagePoints1,imagePoints2;
for(int i=0;i<10;i++)
{
imagePoints1.push_back(keyPoint1[matchePoints[i].queryIdx].pt);
imagePoints2.push_back(keyPoint2[matchePoints[i].trainIdx].pt);
} //获取图像1到图像2的投影映射矩阵,尺寸为3*3
Mat homo=findHomography(imagePoints1,imagePoints2,CV_RANSAC);
Mat adjustMat=(Mat_<double>(3,3)<<1.0,0,image01.cols,0,1.0,0,0,0,1.0);
Mat adjustHomo=adjustMat*homo; //获取最强配对点在原始图像和矩阵变换后图像上的对应位置,用于图像拼接点的定位
Point2f originalLinkPoint,targetLinkPoint,basedImagePoint;
originalLinkPoint=keyPoint1[matchePoints[0].queryIdx].pt;
targetLinkPoint=getTransformPoint(originalLinkPoint,adjustHomo);
basedImagePoint=keyPoint2[matchePoints[0].trainIdx].pt; //图像配准
Mat imageTransform1;
warpPerspective(image01,imageTransform1,adjustMat*homo,Size(image02.cols+image01.cols+10,image02.rows)); //在最强匹配点的位置处衔接,最强匹配点左侧是图1,右侧是图2,这样直接替换图像衔接不好,光线有突变
Mat ROIMat=image02(Rect(Point(basedImagePoint.x,0),Point(image02.cols,image02.rows)));
ROIMat.copyTo(Mat(imageTransform1,Rect(targetLinkPoint.x,0,image02.cols-basedImagePoint.x+1,image02.rows))); namedWindow("拼接结果",0);
imshow("拼接结果",imageTransform1);
waitKey();
return 0;
} //计算原始图像点位在经过矩阵变换后在目标图像上对应位置
Point2f getTransformPoint(const Point2f originalPoint,const Mat &transformMaxtri)
{
Mat originelP,targetP;
originelP=(Mat_<double>(3,1)<<originalPoint.x,originalPoint.y,1.0);
targetP=transformMaxtri*originelP;
float x=targetP.at<double>(0,0)/targetP.at<double>(2,0);
float y=targetP.at<double>(1,0)/targetP.at<double>(2,0);
return Point2f(x,y);
}

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

#include "highgui/highgui.hpp"
#include "opencv2/nonfree/nonfree.hpp"
#include "opencv2/legacy/legacy.hpp" using namespace cv; //计算原始图像点位在经过矩阵变换后在目标图像上对应位置
Point2f getTransformPoint(const Point2f originalPoint,const Mat &transformMaxtri); int main(int argc,char *argv[])
{
Mat image01=imread(argv[1]);
Mat image02=imread(argv[2]);
imshow("拼接图像1",image01);
imshow("拼接图像2",image02); //灰度图转换
Mat image1,image2;
cvtColor(image01,image1,CV_RGB2GRAY);
cvtColor(image02,image2,CV_RGB2GRAY); //提取特征点
SiftFeatureDetector siftDetector(800); // 海塞矩阵阈值
vector<KeyPoint> keyPoint1,keyPoint2;
siftDetector.detect(image1,keyPoint1);
siftDetector.detect(image2,keyPoint2); //特征点描述,为下边的特征点匹配做准备
SiftDescriptorExtractor siftDescriptor;
Mat imageDesc1,imageDesc2;
siftDescriptor.compute(image1,keyPoint1,imageDesc1);
siftDescriptor.compute(image2,keyPoint2,imageDesc2); //获得匹配特征点,并提取最优配对
FlannBasedMatcher matcher;
vector<DMatch> matchePoints;
matcher.match(imageDesc1,imageDesc2,matchePoints,Mat());
sort(matchePoints.begin(),matchePoints.end()); //特征点排序
//获取排在前N个的最优匹配特征点
vector<Point2f> imagePoints1,imagePoints2;
for(int i=0;i<10;i++)
{
imagePoints1.push_back(keyPoint1[matchePoints[i].queryIdx].pt);
imagePoints2.push_back(keyPoint2[matchePoints[i].trainIdx].pt);
} //获取图像1到图像2的投影映射矩阵,尺寸为3*3
Mat homo=findHomography(imagePoints1,imagePoints2,CV_RANSAC);
Mat adjustMat=(Mat_<double>(3,3)<<1.0,0,image01.cols,0,1.0,0,0,0,1.0);
Mat adjustHomo=adjustMat*homo; //获取最强配对点在原始图像和矩阵变换后图像上的对应位置,用于图像拼接点的定位
Point2f originalLinkPoint,targetLinkPoint,basedImagePoint;
originalLinkPoint=keyPoint1[matchePoints[0].queryIdx].pt;
targetLinkPoint=getTransformPoint(originalLinkPoint,adjustHomo);
basedImagePoint=keyPoint2[matchePoints[0].trainIdx].pt; //图像配准
Mat imageTransform1;
warpPerspective(image01,imageTransform1,adjustMat*homo,Size(image02.cols+image01.cols+110,image02.rows)); //在最强匹配点左侧的重叠区域进行累加,是衔接稳定过渡,消除突变
Mat image1Overlap,image2Overlap; //图1和图2的重叠部分
image1Overlap=imageTransform1(Rect(Point(targetLinkPoint.x-basedImagePoint.x,0),Point(targetLinkPoint.x,image02.rows)));
image2Overlap=image02(Rect(0,0,image1Overlap.cols,image1Overlap.rows));
Mat image1ROICopy=image1Overlap.clone(); //复制一份图1的重叠部分
for(int i=0;i<image1Overlap.rows;i++)
{
for(int j=0;j<image1Overlap.cols;j++)
{
double weight;
weight=(double)j/image1Overlap.cols; //随距离改变而改变的叠加系数
image1Overlap.at<Vec3b>(i,j)[0]=(1-weight)*image1ROICopy.at<Vec3b>(i,j)[0]+weight*image2Overlap.at<Vec3b>(i,j)[0];
image1Overlap.at<Vec3b>(i,j)[1]=(1-weight)*image1ROICopy.at<Vec3b>(i,j)[1]+weight*image2Overlap.at<Vec3b>(i,j)[1];
image1Overlap.at<Vec3b>(i,j)[2]=(1-weight)*image1ROICopy.at<Vec3b>(i,j)[2]+weight*image2Overlap.at<Vec3b>(i,j)[2];
}
}
Mat ROIMat=image02(Rect(Point(image1Overlap.cols,0),Point(image02.cols,image02.rows))); //图2中不重合的部分
ROIMat.copyTo(Mat(imageTransform1,Rect(targetLinkPoint.x,0, ROIMat.cols,image02.rows))); //不重合的部分直接衔接上去
namedWindow("拼接结果",0);
imshow("拼接结果",imageTransform1);
imwrite("D:\\拼接结果.jpg",imageTransform1);
waitKey();
return 0;
} //计算原始图像点位在经过矩阵变换后在目标图像上对应位置
Point2f getTransformPoint(const Point2f originalPoint,const Mat &transformMaxtri)
{
Mat originelP,targetP;
originelP=(Mat_<double>(3,1)<<originalPoint.x,originalPoint.y,1.0);
targetP=transformMaxtri*originelP;
float x=targetP.at<double>(0,0)/targetP.at<double>(2,0);
float y=targetP.at<double>(1,0)/targetP.at<double>(2,0);
return Point2f(x,y);
}

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. 深入理解HTTP协议及原理分析之缓存(3种缓存机制)

    3.2 缓存的实现原理 3.2.1什么是Web缓存 WEB缓存(cache)位于Web服务器和客户端之间. 缓存会根据请求保存输出内容的副本,例如html页面,图片,文件,当下一个请求来到的时候:如果 ...

  2. 《社交红利》读书总结--如何从微信微博QQ空间等社交网络带走海量用户、流量与收入

    <社交红利--如何从微信微博QQ空间等社交网络带走海量用户.流量与收入>--徐志斌 著 <社交红利>这本书2013年9月才上市,卖的非常火. 我最初是在公司内部的期刊上,看到有 ...

  3. sql server备份与还原 sql语句

    USE master DECLARE tb CURSOR LOCAL FOR SELECT 'Kill '+ CAST(Spid AS VARCHAR) FROM master.dbo.sysproc ...

  4. 【河南省多校脸萌第六场 A】巴什博弈?

    [链接]http://acm.nyist.me/JudgeOnline/problem.php?cid=1013&pid=5 [题意] 在这里写题意 [题解] 0..a-1 YES a..a+ ...

  5. 关于python中矩阵的实现和矩阵的转置

    python中矩阵的实现是靠序列,,, 序列有很多形式, 其实矩阵是现实生活中的东西,把现实生活中的结构转换到程序中. 就需要有个实现的方法,而这种路径是多种多样的. 下面给出一个把矩阵转换成pyth ...

  6. Javascript和jquery事件--鼠标右键事件,contextmenu

    右键点击触发是浏览器的默认菜单事件contextmenu,你可以选择阻止它,使用event.preventDefault();或者return false;. 想要定义右键点击事件,关注的是mouse ...

  7. dataTable() 与 DataTable() 的差别与处理方式

    jQuery dataTable的初始化有两种方式: var dataTable = $('#example').dataTable(); 与 var DataTable = $('#example' ...

  8. 基于bootstrap的主流框架有哪些

    基于bootstrap的主流框架有哪些 一.总结 一句话总结:其实可以直接百度bootstrap后台模板,出来一大堆,想用哪个用哪个. 二.[前端框架系列]浅谈当前基于bootstrap框架的几种主流 ...

  9. vue2.0 踩坑记录之组件

    - did you register the component correctly? For recursive components, make sure to provide the " ...

  10. python中如何在一张图上画两条折线

    摘自:https://segmentfault.com/q/1010000002760775