OpenCV C++案例实战十二《图像全景拼接》

  • 前言
  • 一、OpenCV Stitcher
    • 1.功能源码
    • 2.效果
  • 二、图像全景拼接
    • 1.特征检测
    • 2.计算单应性矩阵
    • 3.透视变换
    • 4.图像拼接
    • 5.功能源码
    • 6.效果
  • 三、图像融合
    • 1.效果
  • 四、源码
  • 总结

前言

本文将使用OpenCV C++ 进行图像全景拼接。目前使用OpenCV对两幅图像进行拼接大致可以分为两类。
一、使用OpenCV内置API Stitcher 进行拼接。
二、使用特征检测算法匹配两幅图中相似的点、计算变换矩阵、最后对其进行透视变换就可以了。

一、OpenCV Stitcher

image_left

image_right

原图如图所示。本案例的需求是将上述两幅图片拼接成一幅图像。首先使用OpenCV提供的Stitcher进行拼接。关于Stitcher的具体原理请大家自行查找相关资料。

1.功能源码

bool OpenCV_Stitching(Mat image_left, Mat image_right)
{//将待拼接图片放进容器里面vector<Mat>images;images.push_back(image_left);images.push_back(image_right);//创建Stitcher模型Ptr<Stitcher>stitcher = Stitcher::create();Mat result;Stitcher::Status status = stitcher->stitch(images, result);// 使用stitch函数进行拼接if (status != Stitcher::OK) return false;imshow("OpenCV图像全景拼接", result);return true;
}

2.效果


这就是使用OpenCV 内置Stitcher拼接出来的效果。

二、图像全景拼接

1.特征检测

使用方法二进行图像全景拼接。由于我们需要用到SIFT或SURF特征检测算子,故我们需要提前配置nonfree模块,关于如何配置nonfree模块请大家自行查阅资料,网上教程也有很多。

目前网上教程大致流程归为:

1、使用特征检测算子提取两幅图像的关键点,然后进行特征描述子匹配。我这里使用的是SURF算子。当然SIFT等其他特征检测算子也可以。

 //创建SURF特征检测器int Hessian = 800;Ptr<SURF>detector = SURF::create(Hessian);//进行图像特征检测、特征描述vector<KeyPoint>keypoint_left, keypoint_right;Mat descriptor_left, descriptor_right;detector->detectAndCompute(image_left, Mat(), keypoint_left, descriptor_left);detector->detectAndCompute(image_right, Mat(), keypoint_right, descriptor_right);//使用FLANN算法进行特征描述子的匹配FlannBasedMatcher matcher;vector<DMatch>matches;matcher.match(descriptor_left, descriptor_right, matches);


如图为使用FLANN算法进行特征描述子匹配的结果。我们需要把那些匹配程度高的关键点筛选出来用以下面计算两幅图像的单应性矩阵。

2、筛选出匹配程度高的关键点

 double Max = 0.0;for (int i = 0; i < matches.size(); i++){//float distance –>代表这一对匹配的特征点描述符(本质是向量)的欧氏距离,数值越小也就说明两个特征点越相像。double dis = matches[i].distance;if (dis > Max){Max = dis;}}//筛选出匹配程度高的关键点vector<DMatch>goodmatches;vector<Point2f>goodkeypoint_left, goodkeypoint_right;for (int i = 0; i < matches.size(); i++){double dis = matches[i].distance;if (dis < 0.15*Max){/*以右图做透视变换左图->queryIdx:查询点索引(查询图像)右图->trainIdx:被查询点索引(目标图像)*///注:对image_right图像做透视变换,故goodkeypoint_left对应queryIdx,goodkeypoint_right对应trainIdx//int queryIdx –>是测试图像的特征点描述符(descriptor)的下标,同时也是描述符对应特征点(keypoint)的下标。goodkeypoint_left.push_back(keypoint_left[matches[i].queryIdx].pt);//int trainIdx –> 是样本图像的特征点描述符的下标,同样也是相应的特征点的下标。goodkeypoint_right.push_back(keypoint_right[matches[i].trainIdx].pt);goodmatches.push_back(matches[i]);}}

如图为image_left筛选出来的关键点。

如图为image_right筛选出来的关键点。

从上图可以看出,我们已经筛选出image_left,image_right共有的关键点部分。接下来,我们需要使用这两个点集计算两幅图的单应性矩阵。

2.计算单应性矩阵

3、计算单应性变换矩阵

 //获取图像right到图像left的投影映射矩阵,尺寸为3*3//注意顺序,srcPoints对应goodkeypoint_right,dstPoints对应goodkeypoint_leftH = findHomography(goodkeypoint_right, goodkeypoint_left, RANSAC);

3.透视变换

4、根据计算出来的单应性矩阵对image_right进行透视变换

 //对image_right进行透视变换warpPerspective(image_right, WarpImg, H, Size(image_right.cols + image_left.cols, image_right.rows));imshow("透视变换", WarpImg);


如图所示为image_right进行透视变换得到的结果。

4.图像拼接

5、根据上述操作,我们已经得到了经透视变换的WarpImg,接下来只需将image_left与WarpImg拼接起来就可以了。

 //将image_left拷贝到透视变换后的图片上,完成图像拼接image_left.copyTo(DstImg(Rect(0, 0, image_left.cols, image_left.rows)));imshow("图像全景拼接", DstImg);

5.功能源码

bool Image_Stitching(Mat image_left, Mat image_right, Mat& H, Mat & WarpImg, Mat &DstImg, bool draw)
{//创建SURF特征检测器int Hessian = 800;Ptr<SURF>detector = SURF::create(Hessian);//进行图像特征检测、特征描述vector<KeyPoint>keypoint_left, keypoint_right;Mat descriptor_left, descriptor_right;detector->detectAndCompute(image_left, Mat(), keypoint_left, descriptor_left);detector->detectAndCompute(image_right, Mat(), keypoint_right, descriptor_right);//使用FLANN算法进行特征描述子的匹配FlannBasedMatcher matcher;vector<DMatch>matches;matcher.match(descriptor_left, descriptor_right, matches);double Max = 0.0;for (int i = 0; i < matches.size(); i++){//float distance –>代表这一对匹配的特征点描述符(本质是向量)的欧氏距离,数值越小也就说明两个特征点越相像。double dis = matches[i].distance;if (dis > Max){Max = dis;}}//筛选出匹配程度高的关键点vector<DMatch>goodmatches;vector<Point2f>goodkeypoint_left, goodkeypoint_right;for (int i = 0; i < matches.size(); i++){double dis = matches[i].distance;if (dis < 0.15*Max){/*以右图做透视变换左图->queryIdx:查询点索引(查询图像)右图->trainIdx:被查询点索引(目标图像)*///注:对image_right图像做透视变换,故goodkeypoint_left对应queryIdx,goodkeypoint_right对应trainIdx//int queryIdx –>是测试图像的特征点描述符(descriptor)的下标,同时也是描述符对应特征点(keypoint)的下标。goodkeypoint_left.push_back(keypoint_left[matches[i].queryIdx].pt);//int trainIdx –> 是样本图像的特征点描述符的下标,同样也是相应的特征点的下标。goodkeypoint_right.push_back(keypoint_right[matches[i].trainIdx].pt);goodmatches.push_back(matches[i]);}}//绘制特征点if (draw){Mat result;drawMatches(image_left, keypoint_left, image_right, keypoint_right, goodmatches, result);imshow("特征匹配", result);Mat temp_left = image_left.clone();for (int i = 0; i < goodkeypoint_left.size(); i++){circle(temp_left, goodkeypoint_left[i], 3, Scalar(0, 255, 0), -1);}imshow("goodkeypoint_left", temp_left);Mat temp_right = image_right.clone();for (int i = 0; i < goodkeypoint_right.size(); i++){circle(temp_right, goodkeypoint_right[i], 3, Scalar(0, 255, 0), -1);}imshow("goodkeypoint_right", temp_right);}//findHomography计算单应性矩阵至少需要4个点/*计算多个二维点对之间的最优单映射变换矩阵H(3x3),使用MSE或RANSAC方法,找到两平面之间的变换矩阵*/if (goodkeypoint_left.size() < 4 || goodkeypoint_right.size() < 4) return false;//获取图像right到图像left的投影映射矩阵,尺寸为3*3//注意顺序,srcPoints对应goodkeypoint_right,dstPoints对应goodkeypoint_leftH = findHomography(goodkeypoint_right, goodkeypoint_left, RANSAC);//对image_right进行透视变换warpPerspective(image_right, WarpImg, H, Size(image_right.cols + image_left.cols, image_right.rows));imshow("透视变换", WarpImg);DstImg = WarpImg.clone();//将image_left拷贝到透视变换后的图片上,完成图像拼接image_left.copyTo(DstImg(Rect(0, 0, image_left.cols, image_left.rows)));imshow("图像全景拼接", DstImg);return true;
}

6.效果


如上图所示,我们已经完成了图像拼接。但是从上图可以看出,拼接效果有明显的拼接缝,故我们还需要对拼接结果进行图像融合,使拼接效果看起来更自然。

三、图像融合


如上图所示,我们需要对红线区域进行融合。即将DstImg与WarpImg重叠区域进行像素融合,使拼接效果过渡更加自然。

//图像融合,消除拼接缝
bool OptimizeSeam(int start_x, int end_x, Mat& WarpImg, Mat& DstImg)
{double Width = (end_x - start_x);//重叠区域的宽度  //图像加权融合,通过改变alpha修改DstImg与WarpImg像素权重,达到融合效果double alpha = 1.0;for (int i = 0; i < DstImg.rows; i++){for (int j = start_x; j < end_x; j++){for (int c = 0; c < 3; c++){//如果图像WarpImg像素为0,则完全拷贝DstImgif (WarpImg.at<Vec3b>(i, j)[c] == 0){alpha = 1.0;}else{double l = Width - (j - start_x); //重叠区域中某一像素点到拼接缝的距离alpha = l / Width;}DstImg.at<Vec3b>(i, j)[c] = DstImg.at<Vec3b>(i, j)[c] * alpha + WarpImg.at<Vec3b>(i, j)[c] * (1.0 - alpha);}}}return true;
}

1.效果


如图为最终融合效果,基本看不出拼接缝啦。

四、源码

#include<iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/xfeatures2d.hpp>
#include<opencv2/stitching.hpp>
using namespace std;
using namespace cv;
using namespace cv::xfeatures2d;//1、使用特征检测算法找到两张图像中相似的点,计算变换矩阵
//2、将图像right透视变换后得到的图片与图像left拼接//图像融合,消除拼接缝
bool OptimizeSeam(int start_x, int end_x, Mat& WarpImg, Mat& DstImg)
{double Width = (end_x - start_x);//重叠区域的宽度  //图像加权融合,通过改变alpha修改DstImg与WarpImg像素权重,达到融合效果double alpha = 1.0;for (int i = 0; i < DstImg.rows; i++){for (int j = start_x; j < end_x; j++){for (int c = 0; c < 3; c++){//如果图像WarpImg像素为0,则完全拷贝DstImgif (WarpImg.at<Vec3b>(i, j)[c] == 0){alpha = 1.0;}else{double l = Width - (j - start_x); //重叠区域中某一像素点到拼接缝的距离alpha = l / Width;}DstImg.at<Vec3b>(i, j)[c] = DstImg.at<Vec3b>(i, j)[c] * alpha + WarpImg.at<Vec3b>(i, j)[c] * (1.0 - alpha);}}}return true;
}bool Image_Stitching(Mat image_left, Mat image_right, Mat & WarpImg, Mat &DstImg, bool draw)
{//创建SURF特征检测器int Hessian = 800;Ptr<SURF>detector = SURF::create(Hessian);//进行图像特征检测、特征描述vector<KeyPoint>keypoint_left, keypoint_right;Mat descriptor_left, descriptor_right;detector->detectAndCompute(image_left, Mat(), keypoint_left, descriptor_left);detector->detectAndCompute(image_right, Mat(), keypoint_right, descriptor_right);//使用FLANN算法进行特征描述子的匹配FlannBasedMatcher matcher;vector<DMatch>matches;matcher.match(descriptor_left, descriptor_right, matches);double Max = 0.0;for (int i = 0; i < matches.size(); i++){//float distance –>代表这一对匹配的特征点描述符(本质是向量)的欧氏距离,数值越小也就说明两个特征点越相像。double dis = matches[i].distance;if (dis > Max){Max = dis;}}//筛选出匹配程度高的关键点vector<DMatch>goodmatches;vector<Point2f>goodkeypoint_left, goodkeypoint_right;for (int i = 0; i < matches.size(); i++){double dis = matches[i].distance;if (dis < 0.15*Max){/*以右图做透视变换左图->queryIdx:查询点索引(查询图像)右图->trainIdx:被查询点索引(目标图像)*///注:对image_right图像做透视变换,故goodkeypoint_left对应queryIdx,goodkeypoint_right对应trainIdx//int queryIdx –>是测试图像的特征点描述符(descriptor)的下标,同时也是描述符对应特征点(keypoint)的下标。goodkeypoint_left.push_back(keypoint_left[matches[i].queryIdx].pt);//int trainIdx –> 是样本图像的特征点描述符的下标,同样也是相应的特征点的下标。goodkeypoint_right.push_back(keypoint_right[matches[i].trainIdx].pt);goodmatches.push_back(matches[i]);}}//绘制特征点if (draw){Mat result;drawMatches(image_left, keypoint_left, image_right, keypoint_right, goodmatches, result);imshow("特征匹配", result);Mat temp_left = image_left.clone();for (int i = 0; i < goodkeypoint_left.size(); i++){circle(temp_left, goodkeypoint_left[i], 3, Scalar(0, 255, 0), -1);}imshow("goodkeypoint_left", temp_left);Mat temp_right = image_right.clone();for (int i = 0; i < goodkeypoint_right.size(); i++){circle(temp_right, goodkeypoint_right[i], 3, Scalar(0, 255, 0), -1);}imshow("goodkeypoint_right", temp_right);}//findHomography计算单应性矩阵至少需要4个点/*计算多个二维点对之间的最优单映射变换矩阵H(3x3),使用MSE或RANSAC方法,找到两平面之间的变换矩阵*/if (goodkeypoint_left.size() < 4 || goodkeypoint_right.size() < 4) return false;//获取图像right到图像left的投影映射矩阵,尺寸为3*3//注意顺序,srcPoints对应goodkeypoint_right,dstPoints对应goodkeypoint_leftMat H = findHomography(goodkeypoint_right, goodkeypoint_left, RANSAC);//对image_right进行透视变换warpPerspective(image_right, WarpImg, H, Size(image_right.cols + image_left.cols, image_right.rows));namedWindow("透视变换", WINDOW_NORMAL);imshow("透视变换", WarpImg);DstImg = WarpImg.clone();//将image_left拷贝到透视变换后的图片上,完成图像拼接image_left.copyTo(DstImg(Rect(0, 0, image_left.cols, image_left.rows)));namedWindow("图像全景拼接", WINDOW_NORMAL);imshow("图像全景拼接", DstImg);//透视变换左上角(0,0,1)Mat V2 = (Mat_<double>(3, 1) << 0.0, 0.0, 1.0);Mat V1 = H * V2;Point left_top;left_top.x = V1.at<double>(0, 0) / V1.at<double>(2, 0);left_top.y = V1.at<double>(0, 1) / V1.at<double>(2, 0);if (left_top.x < 0)left_top.x = 0;//透视变换左下角(0,src.rows,1)V2 = (Mat_<double>(3, 1) << 0.0, image_left.rows, 1.0);V1 = H * V2;Point left_bottom;left_bottom.x = V1.at<double>(0, 0) / V1.at<double>(2, 0);left_bottom.y = V1.at<double>(0, 1) / V1.at<double>(2, 0);if (left_bottom.x < 0)left_bottom.x = 0;int start_x = min(left_top.x, left_bottom.x);//重合区域起点int end_x = image_left.cols;//重合区域终点OptimizeSeam(start_x, end_x, WarpImg, DstImg); //图像融合namedWindow("图像融合", WINDOW_NORMAL);imshow("图像融合", DstImg);return true;
}bool OpenCV_Stitching(Mat image_left, Mat image_right)
{//将待拼接图片放进容器里面vector<Mat>images;images.push_back(image_left);images.push_back(image_right);//创建Stitcher模型Ptr<Stitcher>stitcher = Stitcher::create();Mat result;Stitcher::Status status = stitcher->stitch(images, result);// 使用stitch函数进行拼接if (status != Stitcher::OK) return false;imshow("OpenCV图像全景拼接", result);return true;
}int main()
{Mat image_left = imread("left.jpg");Mat image_right = imread("right.jpg");if (image_left.empty() || image_right.empty()){cout << "No Image!" << endl;system("pause");return -1;}Mat WarpImg, DstImg;if (!Image_Stitching(image_left, image_right, WarpImg, DstImg, false)){cout << "can not stitching the image!" << endl;system("pause");return false;}if (!OpenCV_Stitching(image_left, image_right)){cout << "can not stitching the image!" << endl;system("pause");return false;}waitKey(0);destroyAllWindows();system("pause");return 0;
}

总结

本文使用OpenCV C++进行图像全景拼接,关键步骤有以下几点。
1、使用特征检测算子提取两幅图像的关键点,然后进行特征描述子匹配。
2、筛选出匹配程度高的关键点计算两幅图的单应性矩阵。
3、利用计算出来的单应性矩阵对其中一张图片进行透视变换。
4、将透视变换的图片与另一张图片进行拼接。
5、将拼接得到的结果进行融合。

OpenCV C++案例实战十二《图像全景拼接》相关推荐

  1. OpenCV C++案例实战十六《制作哈哈镜图像》

    OpenCV C++案例实战十六<制作哈哈镜图像> 前言 一.凸透镜 1.功能源码 2.效果显示 二.凹透镜 1.功能源码 2.效果显示 三.源码 总结 前言 本文将使用OpenCV C+ ...

  2. OpenCV C++案例实战十八《抖音特效——“蓝线挑战”》

    OpenCV C++案例实战十八<抖音特效--"蓝线挑战"> 前言 一.图像扫描 二.生成定格图像 三.图像混合 四.效果显示 五.源码 总结 前言 本文将使用Open ...

  3. OpenCV C++案例实战十九《制作电子相册查看器》

    OpenCV C++案例实战十九<制作电子相册查看器> 前言 一.图片读取 二.图片展示 三.键盘控制 四.效果显示 五.源码 总结 前言 本文将使用OpenCV C++ 制作电子相册查看 ...

  4. OpenCV与图像处理学习十二——图像形状特征之HOG特征

    OpenCV与图像处理学习十二--图像形状特征之HOG特征 一.图像特征理解 1.1 颜色特征 1.2 纹理特征 1.3 形状特征 1.4 空间关系特征 二.形状特征描述 2.1 HOG特征 2.1. ...

  5. OpenCV C++案例实战五《答题卡识别》

    OpenCV C++案例实战五<答题卡识别> 前言 一.图像矫正 1.源码 二.获取选项区域 1.扣出每题选项 2.源码 三.获取答案 1.思路 2.辅助函数 3.源码 4.效果 总结 前 ...

  6. OpenCV C++案例实战二十九《遥感图像分割》

    OpenCV C++案例实战二十九<遥感图像分割> 前言 一.准备数据 二.K-Means分类 三.效果显示 四.源码 总结 前言 本案例基于k-means机器学习算法进行遥感图像分割.主 ...

  7. OpenCV C++案例实战二十二《手势识别》

    OpenCV C++案例实战二十二<手势识别> 前言 一.手部关键点检测 1.1 功能源码 1.2 功能效果 二.手势识别 2.1算法原理 2.2功能源码 三.结果显示 3.1功能源码 3 ...

  8. OpenCV C++案例实战三十二《字符识别》

    OpenCV C++案例实战三十二<字符识别> 前言 一.结果演示 二.制作数据集 三.字符识别 四.源码 总结 前言 本案例将使用OpenCV C++ 进行字符识别.主要包括制作数据集. ...

  9. OpenCV C++案例实战二《生成蒙太奇图像》

    OpenCV C++案例实战二<生成蒙太奇图像> 前言 一.输入模板图像 二.读取素材图像 三.生成蒙太奇模板 四.生成蒙太奇图像 五.源码 总结 前言 本文将使用OpenCV C++ 生 ...

最新文章

  1. c语言代码大全500行,C语言职工档案管理系统 500多行代码1
  2. 微信小程序onLoad与onShow的区别
  3. Laravel学习笔记(四)数据库 数据库迁移案例
  4. c语言中通过指针引用数组,C语言基础(二)
  5. Kerberos打开debug日志
  6. url没有参数名怎么直接带参数_用30行Python爬虫带你看PLMM(划掉,喵星人)
  7. esp8266 防掉线方法_ESP8266-12F 中断
  8. Linux系统管理(7)——Linux单用户模式详解 及应用场景
  9. php sql注入审计,php审计基础一:sql注入
  10. 医学相关excel表格计算机作业,求计算机作业电子表格演示文稿。
  11. 剑指offer 09、30:栈与队列
  12. Ubuntu 解压 zip、z01、z02等文件方法
  13. 解决POI导出Excel单元格内容换行问题
  14. 兆骑科创创业大赛,线上直播路演,高层次人才引进服务平台
  15. 网站反爬指南:政府网站篇
  16. Photoshop---Wacom手绘板绘画画变成了拖动,根本不能画画
  17. wireshark 学习更进一步 之wireshark异常数据解读
  18. 证券公司信息化2-投资银行业务是做什么的,怎样赚钱,IT在里边起到什么作用?
  19. 头铁!我就硬钢算法岗!
  20. MySQL运维进阶必备

热门文章

  1. 质量管理数字化(QMS系统)该如何开展
  2. Jmeter(总篇): 针对性能测试工具:Jmeter的专题学习
  3. 在下列HTML中那也可以产生超链接,html+css+js完整版面试题(选择,简答,程序题)
  4. linux安装 pycocotools_Windows下安装 pycocotools
  5. 我的Debian GNU/Linux——安装篇
  6. networkx使用
  7. 基于深度学习的目标检测研究综述
  8. 中国三角警示牌行业市场供需与战略研究报告
  9. php 自带sql防注入函数,php 防Sql注入函数的简单示例
  10. [OpenGL(C++)] - (开源)3D 游戏界的大牛人 John Carmack 终于放出quakeIII(雷神之锤3)的源代码...