Sift和Surf算法实现两幅图像拼接的过程是一样的,主要分为4大部分:
1. 特征点提取和描述
2. 特征点配对,找到两幅图像中匹配点的位置
3. 通过配对点,生成变换矩阵,并对图像1应用变换矩阵生成对图像2的映射图像
4. 图像2拼接到映射图像上,完成拼接

具体请转到http://m.blog.csdn.net/article/details?id=52629856

代码如下:

#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,image02;if (argc < 2){image01 = imread("left.jpg");image02 = imread("right.jpg");}else{image01 = imread(argv[1]);image02 = imread(argv[2]);}if (image01.empty() || image02.empty()){return 0;//图像没有全部读取成功}imshow("拼接图像1", image01);imshow("拼接图像2", image02);double time = getTickCount();//灰度图转换  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());if (matchePoints.size() < 10){return 0;}sort(matchePoints.begin(), matchePoints.end()); //特征点排序,opencv按照匹配点准确度排序    //获取排在前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);//向后偏移image01.cols矩阵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 imageTransform;//将图片1进行映射到图像2,本来映射后x值为负值,但是把映射矩阵向后偏移image01.cols矩阵//我们很难判断出拼接后图像的大小尺寸,为了尽可能保留原来的像素,我们尽可能的大一些,对于拼接后的图片可以进一步剪切无效或者不规则的边缘warpPerspective(image01, imageTransform, 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)));//在最强匹配点左侧的重叠区域进行累加,是衔接稳定过渡,消除突变  Mat image1Overlap, image2Overlap; //图1和图2的重叠部分     image1Overlap = imageTransform(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(imageTransform, Rect(targetLinkPoint.x, 0, ROIMat.cols, image02.rows))); //不重合的部分直接衔接上去  time = getTickCount() - time;time /= getTickFrequency();printf("match time=%f\n",time);namedWindow("拼接结果", 0);imshow("拼接结果", imageTransform);imwrite("matchResult.jpg",imageTransform);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);
}

测试结果:


                                               left左边图片

right右边图片

result拼接结果

从测试结果能发现,合并后的图片两边会有黑色区域,如果相机位置不是同高的化,上下两边也会有黑色区域,需要对拼接后的图片进行二次剪切。算法思路是:分别扫描四个边缘,分别找到最大黑色区域长度,然后删掉就行了。

Opencv实现图像无缝拼接,Sift查找特征点,Flann进行匹配相关推荐

  1. Php 360度跟随图,一种360度全景图像无缝拼接的方法与流程

    本发明涉及一种图像拼接方法,特别是一种360度全景图像无缝拼接的方法,本发明属于多传感器图像拼接技术领域. 背景技术: 图像拼接是将在同一场景下拍摄的多个图像拼接成具有宽视场的高分辨率图像的技术.图像 ...

  2. Opencv Sift和Surf特征实现图像无缝拼接生成全景图像

    Sift和Surf算法实现两幅图像拼接的过程是一样的,主要分为4大部分: 1. 特征点提取和描述 2. 特征点配对,找到两幅图像中匹配点的位置 3. 通过配对点,生成变换矩阵,并对图像1应用变换矩阵生 ...

  3. Opencv Surf特征实现图像无缝拼接生成全景图像(三)

    转自:https://guo-pu.blog.csdn.net/article/details/90657830 图像拼接在实际的应用场景很广,比如无人机航拍,遥感图像等等,图像拼接是进一步做图像理解 ...

  4. python 图像无缝拼接,OpenCV Python 系列教程3 - Core 组件

    基本知识 灰度图像的存储方式: image 多通道图像存储方式 image OpenCV 中的通道存储为 BGR 像素值的存储方式 RGB 模式,显示设备采用这种模式 HSV.HLS 将颜色分解成色调 ...

  5. python 图像无缝拼接_Python+OpenCV实现图像的全景拼接的代码

    环境:python3.5.2 + openCV3.4 1.算法目的 将两张相同场景的场景图片进行全景拼接. 2.算法步骤 本算法基本步骤有以下几步: 步骤1:将图形先进行桶形矫正 没有进行桶形变换的图 ...

  6. Android OpenCV(五十七):ORB特征点FLANN匹配

    前言 Android OpenCV 系列的上一篇文章中,我们学习了 ORB 特征点的暴力匹配方式.复习一下,暴力匹配法会针对查询描述子中的每个描述符在训练描述子中寻找匹配描述子,算法复杂度是 O( n ...

  7. opencv学习笔记(三)—— 利用图像金字塔进行图像无缝拼接,cv2.pyrDown() ,cv2.pyrUp()

    原理 一般情况下,我们要处理是一副具有固定分辨率的图像.但是有些情况下,我们需要对同一图像的不同分辨率的子图像进行处理.比如,我们要在一幅图像中查找某个目标,比如脸,我们不知道目标在图像中的尺寸大小. ...

  8. opencv实现图像的拼接功能

    opencv 图像拼接. 代码来自版本2.4.9,stitching.cpp /*M/// // // IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INS ...

  9. OpenCV中图像水平拼接函数hconcat的使用

    OPenCV版本:4.4 IDE:VS2019 功能描述 对给定的矩阵应用进行水平连接. 函数垂直连接两个或更多的cv::Mat矩阵 (具有相同的行数). 函数原型1 CV_EXPORTS void ...

  10. OpenCV中图像垂直拼接函数vconcat的使用

    OPenCV版本:4.4 IDE:VS2019 功能描述 对给定的矩阵应用进行垂直连接 函数垂直连接两个或更多的cv::Mat矩阵 (具有相同的列数). 函数原型1 CV_EXPORTS void v ...

最新文章

  1. ThreeJS获取快照
  2. [转]NS2 Data Collections by mitkook
  3. shiro+php,一套基于SpringBoot+Vue+Shiro 前后端分离 开发的代码生成器
  4. php u6536编码转,详谈PHP编码转换问题
  5. java 妖魔道-倩女幽魂_《倩女幽魂I-妖魔道》地图新手功略
  6. Python中字符串操作函数string.split('str1')和string.join(ls)
  7. angular五大服务顺序,angularJS $事件处理程序的触发顺序
  8. tomcat9-jenkins:insufficient free space available after evicting expired cache entries-consider
  9. Bootstrap HTML 编码规范之JavaScript生成的标签
  10. ios 与web服务器传值_中高级iOS大厂面试宝典,拿到offer率80%,金三银四将是你的新起点...
  11. excel html modify,Modify excel cell
  12. 中心极限与大数定理律的关系_多元函数的极限、连续性分析
  13. C语言——指针函数和函数指针(回调函数)
  14. python好东西啊
  15. luogu P3802 小魔女帕琪
  16. 基于主从博弈的电热综合能源系统动态定价与能量管理 主要做的是电热综合能源系统的动态定价问题,采用是主从博弈方法
  17. 新版qq虚拟摄像头颜色不正常_云答辩 | QQ群“视频通话”来了
  18. 呼叫压力测试软件,MyComm呼叫中心压力测试解决方案
  19. 一款PDF解密工具的Keygen
  20. 突如其来的第一个1024要笑着过

热门文章

  1. 以后所有经济时事的点评都不在这里
  2. LINUX上安装gstreamer,解决video.h找不到的错误
  3. Data Member的布局
  4. c++数组排序_为什么?为什么?Java处理排序后的数组比没有排序的快?想过没有?
  5. 集美大学计算机工程学院 曾勇进,电子政务评估方法AHP 的研究及实现.pdf
  6. C# pictureBox桌面大小自适应 大小自适应 窗体居中
  7. 实现列表CListCtrl可点击编辑
  8. 桌面环境选择_如何在 Ubuntu 20.04 LTS 上安装深度(Deepin)桌面环境 | Linux 中国
  9. linux内核make 时间久,Linux内核makefile问题
  10. 计算机应用基础辅导资料,《计算机应用基础》辅导资料三