opencv 提供了全景图像拼接的所有实现,包括:

1)stitching 模块提供了图像拼接过程中所需要的基本元素,该模块主要依赖于 features2d 模块;

2)提供了 stitching_detailed.cpp,本示例给出了如何运用 stitching 模块的详细说明;

同时,opencv 也提供了一个更高层次的封装类 cv::Stitcher,但为了详细理解图像拼接具体实现,这里不讨论该封装类。

1 特征点提取与匹配

Ptr<FeaturesFinder> finder;
if (features_type == "surf")
{
#if defined(HAVE_OPENCV_NONFREE) && defined(HAVE_OPENCV_GPU)
if (try_gpu && gpu::getCudaEnabledDeviceCount() > 0)
finder = new SurfFeaturesFinderGpu();
else
#endif
finder = new SurfFeaturesFinder(150);
}
else if (features_type == "orb")
{
finder = new OrbFeaturesFinder();
}

FeaturesFinder 类定义在 mathers.hpp 中,mathers.hpp 封装了特征点提取,特征点描述,特征点匹配等相关功能,

FeaturesFinder 类有两个继承类,SurfFeaturesFinder 和 OrbFeaturesFinder,分别使用 SURF 特征 和 ORB 特征,当然也可以添加更多继承类,如 SIFTFeaturesFinder 等,

SURF 特征继承了 SIFT 特征良好的特性同时降低了运算时间,但 SURF 在 nonfree 模块中,所以,ORB 可能是另一种较好的选择。

由于特征计算需要耗费较多时间,所以会在小图像上进行特征提取,全局变量参数 work_megapix = 0.6 设定了特征提取的图像尺寸,表示使用 0.6M 大小图像进行特征提取,

代码 work_scale = min(1.0, sqrt(work_megapix * 1e6 / full_img.size().area())) 根据 work_megapix 计算出缩放比例。

对每幅图像,其特征点被提取到 ImageFeatures 结构体中,组成了向量 vector<ImageFeatures> features(num_images)。

struct CV_EXPORTS MatchesInfo
   {
     MatchesInfo();
     MatchesInfo(const MatchesInfo &other);
     const MatchesInfo& operator =(const MatchesInfo &other);

int src_img_idx, dst_img_idx; // Images indices (optional)
     std::vector<DMatch> matches;
     std::vector<uchar> inliers_mask; // Geometrically consistent matches mask
     int num_inliers; // Number of geometrically consistent matches
     Mat H; // Estimated homography
     double confidence; // Confidence two images are from the same panorama
   };

 vector<MatchesInfo> pairwise_matches;
BestOf2NearestMatcher matcher(try_gpu, match_conf);
matcher(features, pairwise_matches);
matcher.collectGarbage();

结构体 MatchesInfo 包含了所有图像之间进行两两匹配的相关结构,Mat H 记录了两幅图像间的仿射变换矩阵,该矩阵是全景图像拼接的基础信息,特征点间匹配信息记录在 pairwise_mathes 中。

2 认出全景图 

// Leave only images we are sure are from the same panorama
vector<int> indices = leaveBiggestComponent(features, pairwise_matches, conf_thresh);
vector<Mat> img_subset;
vector<string> img_names_subset;
vector<Size> full_img_sizes_subset;
for (size_t i = 0; i < indices.size(); ++i)
{
img_names_subset.push_back(img_names[indices[i]]);
img_subset.push_back(images[indices[i]]);
full_img_sizes_subset.push_back(full_img_sizes[indices[i]]);
} images = img_subset;
img_names = img_names_subset;
full_img_sizes = full_img_sizes_subset;

函数 leaveBiggestComponent 评估图像间特征点匹配程度,当匹配程度过低时,则认为该图像不是全景图像组成部分。

3 3D到2D映射矩阵

在特征提取与匹配中, 结构体 MatchesInfo 中 Mat H 记录了两幅图像间的仿射变换矩阵,该变换表示的二维点间的变换关系。

motion_estimators.hpp 封装了 HomographyBasedEstimator 类,用于计算3D到2D映射相关信息,这里使用旋转全景模型,通过放射变换矩阵可以首先估计出相机焦距,代码如下:

void HomographyBasedEstimator::estimate(const vector<ImageFeatures> &features, const vector<MatchesInfo> &pairwise_matches,
vector<CameraParams> &cameras)
{
LOGLN("Estimating rotations..."); const int num_images = static_cast<int>(features.size());
if (!is_focals_estimated_)
{
// Estimate focal length and set it for all cameras
vector<double> focals;
estimateFocal(features, pairwise_matches, focals);
cameras.assign(num_images, CameraParams());
for (int i = 0; i < num_images; ++i)
cameras[i].focal = focals[i];
}
else
{
for (int i = 0; i < num_images; ++i)
{
cameras[i].ppx -= 0.5 * features[i].img_size.width;
cameras[i].ppy -= 0.5 * features[i].img_size.height;
}
} // Restore global motion
Graph span_tree;
vector<int> span_tree_centers;
findMaxSpanningTree(num_images, pairwise_matches, span_tree, span_tree_centers);
span_tree.walkBreadthFirst(span_tree_centers[0], CalcRotation(num_images, pairwise_matches, cameras)); // As calculations were performed under assumption that p.p. is in image center
for (int i = 0; i < num_images; ++i)
{
cameras[i].ppx += 0.5 * features[i].img_size.width;
cameras[i].ppy += 0.5 * features[i].img_size.height;
}
}

以上代码首先估计相机焦距,在 “全景图像拼接” 一文中,给出了根据仿射变换估计相机焦距的方法,主要是利用旋转矩阵的正交特性来实现的。

然后在估计焦距基础上计算出旋转矩阵,同时假设图像中心即为光轴,假设像元长宽比为1,vector<CameraParams> cameras 保存了每张图像上的相对于 3D 空间的映射信息。

4 全局配准

到目前为止,任意 3D 点到任意图像上的映射关系已经确定。同一个 3D 点,在多幅图像上存在对应点,使用对应的映射关系将其分别映射到 3D 空间上,理论上来说应该得到同一个 3D 点。

但实际情况可能并不理想,基于 vector<CameraParams> cameras 提供的映射关系,使用光束平差法可以消除误差。主要包括两种方法:

1)最小化重映射误差法(BundleAdjusterReproj)

首先将第 i 张图像上特征点映射到三维空间,然后再将三维空间点映射到第 j 张图像中,注意,两次映射使用了不同的映射矩阵!!!

计算第 j 张图像上的映射点于特征匹配点之间的误差,最小化所有映射点误差即完成了全局配准。

2)最小化 3D 投影方向上误差法(BundleAdjusterRay)

对第 i 张与第 j 张图像上的匹配点对分别使用映射矩阵映射到三维空间中,最小化两条射线间的距离即完成了全局配准。

完成全局配准后,可以通过 waveCorrect 消除扰动,如果图像扰动较小,有时也可以忽略他。

5 图像融合

在图像融合前,首先使用 SeamFinder 找到不同图像间的缝隙,由于缝隙寻找不需要特别精确,故使用了更小的分辨率 seam_megapix = 0.1。

由于可能存在曝光不均匀详细,使用 ExposureCompensator 类实现曝光补偿相关工作,最后使用 Blender 完成图像融合。

关于图像融合还有很多需要详细理解的内容,但该部分没有太多参数需要调整,所以暂时不深入了解。下面给出拼接效果:

   

参考资料 opencv-2.4.10\modules\stitching && stitching_detailed.cpp

opencv笔记--stitching模块的更多相关文章

  1. Stitching模块中对特征提取的封装解析(以ORB特性为例)

    titching模块中对特征提取的封装解析(以ORB特性为例)     OpenCV中Stitching模块(图像拼接模块)的拼接过程可以用PipeLine来进行描述,是一个比较复杂的过程.在这个过程 ...

  2. opencv笔记1:opencv的基本模块,以及环境搭建

    opencv笔记1:opencv的基本模块,以及环境搭建 安装系统 使用fedora22-workstation-x86_64 安装opencv sudo dnf install opencv-dev ...

  3. 利用OpenCV实现图像拼接Stitching模块讲解

    https://zhuanlan.zhihu.com/p/71777362 1.1 图像拼接基本步骤 图像拼接的完整流程如上所示,首先对输入图像提取鲁棒的特征点,并根据特征描述子完成特征点的匹配,然后 ...

  4. OpenCV整体的模块架构

    之前啃了不少OpenCV的官方文档,发现如果了解了一些OpenCV整体的模块架构后,再重点学习自己感兴趣的部分的话,就会有一览众山小的感觉,于是,就决定写出这篇文章,作为启程OpenCV系列博文的第二 ...

  5. Stitching模块中leaveBiggestComponent初步研究

    在Stitching模块中以及原始论文<Automatic Panoramic Image Stitching using Invariant Features>3.2中,都有" ...

  6. OpenCV笔记大集锦(转载)

    整理了我所了解的有关OpenCV的学习笔记.原理分析.使用例程等相关的博文.排序不分先后,随机整理的.如果有好的资源,也欢迎介绍和分享. 1:OpenCV学习笔记 作者:CSDN数量:55篇博文网址: ...

  7. 【OpenCV】OpenCV中GPU模块使用

    CUDA基本使用方法 在介绍OpenCV中GPU模块使用之前,先回顾下CUDA的一般使用方法,其基本步骤如下: 1.主机代码执行:2.传输数据到GPU:3.确定grid,block大小: 4.调用内核 ...

  8. opencv笔记6:角点检测

    time:2015年10月09日 星期五 23时11分58秒 # opencv笔记6:角点检测 update:从角点检测,学习图像的特征,这是后续图像跟踪.图像匹配的基础. 角点检测是什么鬼?前面一篇 ...

  9. opencv笔记5:频域和空域的一点理解

    time:2015年10月06日 星期二 12时14分51秒 # opencv笔记5:频域和空域的一点理解 空间域和频率域 傅立叶变换是f(t)乘以正弦项的展开,正弦项的频率由u(其实是miu)的值决 ...

随机推荐

  1. win 10 遇到某文件一直在占用导致无法关闭,或者去任务管理器找不到服务怎么办?具体解决

    1. 打开 cmd 指令框 ,输入 perfmon 回车 就会出来这个 点击  打开资源监视器, 在句柄搜索框搜索 那个占用资源的文件或软件关键词 ,如下 搜索酷狗 将有关的选项,右键选中后 打开菜单 ...

  2. Ribbon原理与应用

    一.定义 Ribbon是请求的负载均衡器,它为我们提供了几种负载均衡算法:轮询.随机等. 二.配置 spring: cloud: loadbalancer: retry: enabled: true ...

  3. http://dl-ssl.google.com/android上不去解决方案

    转:https://blog.csdn.net/j04110414/article/details/44149653/ 一. 更新sdk,遇到了更新下载失败问题: Fetching https://d ...

  4. 【刷题-PAT】A1112 Stucked Keyboard (20 分)

    1112 Stucked Keyboard (20 分) On a broken keyboard, some of the keys are always stucked. So when you ...

  5. 【问题排查过程】vm-backup的snapshots导致磁盘满

    使用中发现,vm-storage节点仅仅过了6天,就占用了800GB的硬盘空间.很不正常.下面是排查过程: 1.查看磁盘占用情况: 先登录容器,执行: df -h /dev/vdb 1012.8G 8 ...

  6. 一种Django多租户解决方案

    什么是多租户? 多租户技术或称多重租赁技术,简称SaaS,是一种软件架构技术,是实现如何在多用户环境下(此处的多用户一般是面向企业用户)共用相同的系统或程序组件,并且可确保各用户间数据的隔离性. 多租 ...

  7. Ioc容器-Bean管理(工厂bean)

    IoC操作Bean管理(FactoryBean) 1,Spring有两种类型bean,一种像自己创建的普通bean,另一种工厂bean(FactoryBean) 2,普通bean:在spring配置文 ...

  8. Redis Hyperloglog的原理及数学理论的通俗理解

    redis中有一种数据格式,hyperloglog,本文就此数据结构的作用.redis的实现及其背后的数学原理作一个整理.当然本文不包含任何数学公式,而是希望用直观的例子帮大家理解. 主要内容如下: ...

  9. 数据库查询语句遇到:Unknown column 'XXXX' in 'where clause'解决方法

    数据库查询语句遇到:Unknown colunm 'XXX' in 'where clause'解决方法 根本原因:可能是sql语句所用到的数据类型错误(int与String)弄错- 我的情况: 在网 ...

  10. Nginx搭建游戏

    目录 一:Nginx搭建<小游戏> 1.上传<象棋游戏>代码 2.编辑配置文件(尾部必须要加 .conf<文件>) 3.测试配置文件是否正常 4.重启Nginx 5 ...