[计算机视觉] 图像拼接 Image Stitching

2017年04月28日 14:05:19

阅读数:1027

作业要求:

1、将多张图片合并拼接成一张全景图(看下面效果图)

2、尽量用C/C++(老师说用matlab会给很低的分_(:зゝ∠)_,所以下面的代码全部都用C++来写)

效果图:

实现大致步骤:

1、SIFT算法进行图像特征提取(SIFT算法是http://blog.csdn.net/v_JULY_v/article/details/6245939找的,不过是用C写,不太好获得中间结果。为了方便我们这次作业使用,我改写成C++代码)

2、利用RANSAC算法进行图像特征匹配

3、利用匹配关键点进行图像拼接(Blending)

实现步骤详解:

1、SIFT算法进行图像特征提取:

SIFT算法在这里就不详细说了,上面的链接已经讲的很详细了(使用上面的代码要配置opencv环境,挺简单的,网上很多教程)。我是将上面链接的代码改写成C++,封装了一些方法,使得能够提取中间结果。

SIFT算法的输入是图片,我们需要的输出是各个关键点的位置、128维描述子(用于关键点匹配)。而代码把一个关键点的这些信息都封装在一个结构体Keypoint里面。同时,代码将所有的关键点Keypoint保存为一个链表List形式,即可以根据第一个节点访问到所有的Keypoint节点。

因此我在改写后的MySift.h文件里,添加了几个方法,一个是SIFT的方法入口SiftMainProcess(),一个是获取处理后得到的关键点的头结点方法getFirstKeyDescriptors()。

MySift.h:

MySift.cpp:

由于.cpp代码有1000+行,由于篇幅问题,在这里就不放出来了_(:зゝ∠)_。有需要的可以私聊下我哈_(:зゝ∠)_或者直接对着上面链接给的代码找一下就好了,函数名都一样的。

阶段结果:

黄色圈圈的就是识别出来的关键点。

     

2、利用RANSAC算法进行图像特征匹配:

由于从上面步骤1得到的结果只是每张图片自身的特征点,即两张图片的特征点之间还没对应关系。因此我们需要先通过上面得到的128维描述子先进行大致的特征点匹配(结果可能包括outliers)。匹配方法不难理解,只需计算两个128维特征描述子的距离差,小于某阈值即可视为相同的特征点。

处理后得到下面的结果,黄色点为匹配的特征点,另外再给每对特征点连线:

可以看到连线特别杂乱,说明其中夹杂着很多outliers。因此需要用下面的RANSAC算法去排除outliers。

其实我用的可以说是伪RANSAC算法_(:зゝ∠)_,简单的说就是:

(1)对每一对关键点P,得到位置间的转移向量v(位置相减)

(2)对其他的每一对关键点P' ,计算位置间的转移向量v'。若v与v' 距离(计算欧拉距离即可)小于一定阈值,则认为P' 与P有相同的特征点位置转移,即为inlier(看下图应该好理解一点)。

(3)计算拥有最多inliers的转移向量v,即可视为两张图特征点位置转移向量V。

(4)再重新扫描所有的关键点对,属于此特征点位置转移向量V的关键点对则视为两张图真正的特征匹配点。

MyMatching.h:

MyMatching.cpp

阶段结果:

(可以看到转移向量V基本一致了)

3、利用匹配关键点进行图像拼接(Blending)

我使用的图像拼接方法其实只是最简单的平移+像素RGB值插值的方法(好在这次的数据集图像不存在太大的放缩,不然就不能用这种方法了_(:зゝ∠)_ 涉及到放缩的图片暂时还想不到怎么做_(:зゝ∠)_)。

可以直观的从下面的图(用ppt拼凑的哈哈)看到,由于输入图像始终保持左图在右图的左侧,即两图并排的时候,右图需要向左移动:

变成:

从上面可以看到,右图不仅需要向左平移,还需要向下/上平移。回想我们第2步得到的转移向量V(dx, dy),就不难理解转移向量V的作用了:dy<0,右图向下平移;dy>=0,右图向上平移。

如果右图是向下平移时,可以得到如下的模型图,而区域的划分我们可以通过简单的数学关系计算出来。明显,A和B单独的区域可以直接取原图像素RGB值;由于两张图长宽可能不一致,以及平移的原因,可能产生黑边(黑色部分)。

最后剩下两图混合部分A/B。如果只是简单的,对混合区域,两张图上对应点像素RGB值各取50%,则容易造成上面那张图那样,在分界处有明显的边缘,以及边缘两边匹配不上。因此我使用了插值的方法,即:根据混合区域内点P的与两边边缘的水平距离,按不同比例取两张图上对应点像素RGB值组合成点P的RGB值(即越靠近左边边缘的点,取左图对应点RGB值的占比越大)。这样就可以实现较好的过渡。

MyBlending.h:

#ifndef MYBLENDING_H
#define MYBLENDING_H

#include "CImg.h"
#include <iostream>
using namespace cimg_library;
using namespace std;

struct TransVector {
int dx;
int dy;
TransVector() : dx(-1), dy(-1) {}
TransVector(int _dx, int _dy) : dx(_dx), dy(_dy) {}
};

class MyBlending
{
public:
MyBlending();
~MyBlending();
MyBlending(int sx, int sy);

void blendingMainProcess(char* _filenameA, char* _filenameB);
void saveBlendedImg(char* blendedImgAddr);

private:
TransVector matchVec; //x为合并图上的水平距离,y
CImg<int> srcImgA, srcImgB;
CImg<int> blendedImg;
};

#endif

MyBlending.cpp:

  1. #include "MyBlending.h"
  2.  
     
  3.  
    MyBlending::MyBlending() {
  4.  
    }
  5.  
     
  6.  
    MyBlending::~MyBlending() {
  7.  
    }
  8.  
     
  9.  
    MyBlending::MyBlending(int sx, int sy) {
  10.  
    matchVec.dx = sx;
  11.  
    matchVec.dy = sy;
  12.  
    }
  13.  
     
  14.  
    void MyBlending::blendingMainProcess(char* _filenameA, char* _filenameB) {
  15.  
    srcImgA.load_bmp(_filenameA);
  16.  
    srcImgB.load_bmp(_filenameB);
  17.  
     
  18.  
    blendedImg = CImg<int>(srcImgA._width + srcImgB._width - matchVec.dx,
  19.  
    srcImgA._height + abs(matchVec.dy), 1, 3, 0);
  20.  
     
  21.  
    cimg_forXY(blendedImg, x, y) {
  22.  
    if (matchVec.dy <= 0) { //右侧图片需要往下左移动
  23.  
    if (x < srcImgA._width && y < srcImgA._height) {
  24.  
    if (x >= (srcImgA._width - matchVec.dx) && y >= (0 - matchVec.dy)) { //混合
  25.  
    blendedImg(x, y, 0, 0) = (float)srcImgA(x, y, 0, 0)
  26.  
    * (float)(srcImgA._width - x) / (float)abs(matchVec.dx)
  27.  
    + (float)srcImgB(x - (srcImgA._width - matchVec.dx), y - (0 - matchVec.dy), 0, 0)
  28.  
    * (float)(x - (srcImgA._width - matchVec.dx)) / (float)abs(matchVec.dx);
  29.  
    blendedImg(x, y, 0, 1) = (float)srcImgA(x, y, 0, 1)
  30.  
    * (float)(srcImgA._width - x) / (float)abs(matchVec.dx)
  31.  
    + (float)srcImgB(x - (srcImgA._width - matchVec.dx), y - (0 - matchVec.dy), 0, 1)
  32.  
    * (float)(x - (srcImgA._width - matchVec.dx)) / (float)abs(matchVec.dx);
  33.  
    blendedImg(x, y, 0, 2) = (float)srcImgA(x, y, 0, 2)
  34.  
    * (float)(srcImgA._width - x) / (float)abs(matchVec.dx)
  35.  
    + (float)srcImgB(x - (srcImgA._width - matchVec.dx), y - (0 - matchVec.dy), 0, 2)
  36.  
    * (float)(x - (srcImgA._width - matchVec.dx)) / (float)abs(matchVec.dx);
  37.  
    }
  38.  
    else { //A独在部分
  39.  
    blendedImg(x, y, 0, 0) = srcImgA(x, y, 0, 0);
  40.  
    blendedImg(x, y, 0, 1) = srcImgA(x, y, 0, 1);
  41.  
    blendedImg(x, y, 0, 2) = srcImgA(x, y, 0, 2);
  42.  
    }
  43.  
    }
  44.  
    else if (x >= (srcImgA._width - matchVec.dx)
  45.  
    && y >= (0 - matchVec.dy) && y < (0 - matchVec.dy) + srcImgB._height) { //B独在部分
  46.  
    blendedImg(x, y, 0, 0) = srcImgB(x - (srcImgA._width - matchVec.dx), y - (0 - matchVec.dy), 0, 0);
  47.  
    blendedImg(x, y, 0, 1) = srcImgB(x - (srcImgA._width - matchVec.dx), y - (0 - matchVec.dy), 0, 1);
  48.  
    blendedImg(x, y, 0, 2) = srcImgB(x - (srcImgA._width - matchVec.dx), y - (0 - matchVec.dy), 0, 2);
  49.  
    }
  50.  
    else { //黑色部分
  51.  
    blendedImg(x, y, 0, 0) = 0;
  52.  
    blendedImg(x, y, 0, 1) = 0;
  53.  
    blendedImg(x, y, 0, 2) = 0;
  54.  
    }
  55.  
    }
  56.  
    else { //matchVec.dy > 0; 右侧图片需要往上左移动
  57.  
    if (x < srcImgA._width && y >= matchVec.dy) {
  58.  
    if (x >= (srcImgA._width - matchVec.dx) && y < srcImgB._height) { //混合
  59.  
    blendedImg(x, y, 0, 0) = (float)srcImgA(x, y - matchVec.dy, 0, 0)
  60.  
    * (float)(srcImgA._width - x) / (float)abs(matchVec.dx)
  61.  
    + (float)srcImgB(x - (srcImgA._width - matchVec.dx), y, 0, 0)
  62.  
    * (float)(x - (srcImgA._width - matchVec.dx)) / (float)abs(matchVec.dx);
  63.  
    blendedImg(x, y, 0, 1) = (float)srcImgA(x, y - matchVec.dy, 0, 1)
  64.  
    * (float)(srcImgA._width - x) / (float)abs(matchVec.dx)
  65.  
    + (float)srcImgB(x - (srcImgA._width - matchVec.dx), y, 0, 1)
  66.  
    * (float)(x - (srcImgA._width - matchVec.dx)) / (float)abs(matchVec.dx);
  67.  
    blendedImg(x, y, 0, 2) = (float)srcImgA(x, y - matchVec.dy, 0, 2)
  68.  
    * (float)(srcImgA._width - x) / (float)abs(matchVec.dx)
  69.  
    + (float)srcImgB(x - (srcImgA._width - matchVec.dx), y, 0, 2)
  70.  
    * (float)(x - (srcImgA._width - matchVec.dx)) / (float)abs(matchVec.dx);
  71.  
    }
  72.  
    else { //A独在部分
  73.  
    blendedImg(x, y, 0, 0) = srcImgA(x, y - matchVec.dy, 0, 0);
  74.  
    blendedImg(x, y, 0, 1) = srcImgA(x, y - matchVec.dy, 0, 1);
  75.  
    blendedImg(x, y, 0, 2) = srcImgA(x, y - matchVec.dy, 0, 2);
  76.  
    }
  77.  
    }
  78.  
    else if (x >= (srcImgA._width - matchVec.dx) && y < srcImgB._height) { //B独在部分
  79.  
    blendedImg(x, y, 0, 0) = srcImgB(x - (srcImgA._width - matchVec.dx), y, 0, 0);
  80.  
    blendedImg(x, y, 0, 1) = srcImgB(x - (srcImgA._width - matchVec.dx), y, 0, 1);
  81.  
    blendedImg(x, y, 0, 2) = srcImgB(x - (srcImgA._width - matchVec.dx), y, 0, 2);
  82.  
    }
  83.  
    else { //黑色部分
  84.  
    blendedImg(x, y, 0, 0) = 0;
  85.  
    blendedImg(x, y, 0, 1) = 0;
  86.  
    blendedImg(x, y, 0, 2) = 0;
  87.  
    }
  88.  
    }
  89.  
    }
  90.  
    blendedImg.display("blendedImg");
  91.  
    }
  92.  
     
  93.  
     
  94.  
    void MyBlending::saveBlendedImg(char* blendedImgAddr) {
  95.  
    blendedImg.save(blendedImgAddr);
  96.  
    }

阶段结果:

4、最后再放上使用上面3个类的主函数的代码吧:

Main.cpp:

#include "stdafx.h"
#include "MyMatching.h"
#include "MyBlending.h"

int main() {
char* inputAddr1 = "Input/1.bmp";
char* inputAddr2 = "Input/2.bmp";

MySift mySift1(inputAddr1, 1);
mySift1.SiftMainProcess();
mySift1.saveImgWithKeypoint("Output/1-2/1_kp.bmp");

MySift mySift2(inputAddr2, 1);
mySift2.SiftMainProcess();
mySift2.saveImgWithKeypoint("Output/1-2/2_kp.bmp");

MyMatching myMatching(mySift1.getKeyPointsCount(), mySift1.getFirstKeyDescriptors(),
mySift2.getKeyPointsCount(), mySift2.getFirstKeyDescriptors());
myMatching.featureMatchMainProcess();
myMatching.drawOriKeypointOnImg(inputAddr1, inputAddr2, "Output/1-2/1_kp_real.bmp", "Output/1-2/2_kp_real.bmp");
myMatching.mixImageAndDrawPairLine("Output/1-2/mixImg.bmp", "Output/1-2/mixImgWithLine.bmp");
myMatching.myRANSACtoFindKpTransAndDrawOut("Output/1-2/mixImgWithLine_fixed.bmp");

MyBlending myBlending(myMatching.getMatchVec().col, myMatching.getMatchVec().row);
myBlending.blendingMainProcess(inputAddr1, inputAddr2);
myBlending.saveBlendedImg("Output/1-2/blendedImg.bmp");

int i;
cin >> i;

return 0;
}

好了,这就差不多了。(其实差很多_(:зゝ∠)_)

其实这份代码普适性不高_(:зゝ∠)_,比如图片是需要先人工排序再扔进去跑的,这个问题想了下应该可以根据转移向量V来进行一定的判别。另外上面也提到了,如果图片之间存在物体放缩,那就不能用上面的方法了(放缩的暂时还想不到解决方案……)。还有就是如果图片的横着的,比如数据集2,就也不能解决了。(想想就很难_(:зゝ∠)_)

如果有大佬能解决上面问题的可以跟我说说,也想了解一下_(:зゝ∠)_

[计算机视觉] 图像拼接 Image Stitching的更多相关文章

  1. OpenCV学习(1)-安装(Windows)

    下载安装 在这里下载.我下载了2.4.9的Windows版本.双击安装即可. 配置环境变量 配置环境变量的目的是为了让系统找到OpenCV的动态链接库.因此需要把动态链接库添加到系统环境变量PATH中 ...

  2. 图像拼接(image stitching)

    # OpenCV中stitching的使用 OpenCV提供了高级别的函数封装在Stitcher类中,使用很方便,不用考虑太多的细节. 低级别函数封装在detail命名空间中,展示了OpenCV算法实 ...

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

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

  4. 基于OpenCV进行图像拼接原理解析和编码实现(提纲 代码和具体内容在课件中)

    一.背景 1.1概念定义 我们这里想要实现的图像拼接,既不是如题图1和2这样的"图片艺术拼接",也不是如图3这样的"显示拼接",而是实现类似"BaiD ...

  5. Computer Vision_18_Image Stitching:A survey on image mosaicing techniques——2013

    此部分是计算机视觉部分,主要侧重在底层特征提取,视频分析,跟踪,目标检测和识别方面等方面.对于自己不太熟悉的领域比如摄像机标定和立体视觉,仅仅列出上google上引用次数比较多的文献.有一些刚刚出版的 ...

  6. Computer Vision_18_Image Stitching: Image Alignment and Stitching A Tutorial——2006(book)

    此部分是计算机视觉部分,主要侧重在底层特征提取,视频分析,跟踪,目标检测和识别方面等方面.对于自己不太熟悉的领域比如摄像机标定和立体视觉,仅仅列出上google上引用次数比较多的文献.有一些刚刚出版的 ...

  7. Computer Vision_18_Image Stitching:Automatic Panoramic Image Stitching using Invariant Features——2007

    此部分是计算机视觉部分,主要侧重在底层特征提取,视频分析,跟踪,目标检测和识别方面等方面.对于自己不太熟悉的领域比如摄像机标定和立体视觉,仅仅列出上google上引用次数比较多的文献.有一些刚刚出版的 ...

  8. Computer Vision_18_Image Stitching: Image Alignment and Stitching——2006

    此部分是计算机视觉部分,主要侧重在底层特征提取,视频分析,跟踪,目标检测和识别方面等方面.对于自己不太熟悉的领域比如摄像机标定和立体视觉,仅仅列出上google上引用次数比较多的文献.有一些刚刚出版的 ...

  9. 图像拼接 SIFT资料合集

    转自 http://blog.csdn.net/stellar0/article/details/8741780 分类: 最近也注意一些图像拼接方面的文章,很多很多,尤其是全景图拼接的,实际上类似佳能 ...

随机推荐

  1. 阿里云rds实例恢复到本地

    摘要: 前提: 1,阿里云数据库备份实例,恢复数据的时候需要将数据恢复到本地数据库,是不能直接恢复到RDS上的. 2,需要在本地服务器上下载一个数据库,尽量和RDS数据库版本保持一致.(我现在用的是5 ...

  2. docker基础:dockerfile的介绍

    Dockerfile 是一个文本格式的配置文件,用户可以使用 Dockerfile 快速创建自定义的镜像.我们会先介绍 Dockerfile 的基本结构及其支持的众多指令,并具体讲解通过执行指令来编写 ...

  3. 准备好要上传到 Azure 的 Windows VHD 或 VHDX

    在将 Windows 虚拟机 (VM) 从本地上传到 Azure 之前,必须准备好虚拟硬盘(VHD 或 VHDX). Azure 仅支持采用 VHD 文件格式且具有固定大小磁盘的第 1 代 VM. V ...

  4. 浅析C#中的Attribute

    原文地址:http://www.cnblogs.com/hyddd/archive/2009/07/20/1526777.html 一.什么是Attribute 先看下面的三段代码: 1.自定义Att ...

  5. 将sqllite3数据库迁移到mysql

    一.安装python mysql module (OneDrive): 1.运行python D:\OneDrive\Work\django\mysqlregistry.py2.http://www. ...

  6. windows的一些好用命令-自己总结:

    在win+R运行框中:     cmd:进入命令行界面     msconfig:可以查看“系统配置”     msinfo32:查看系统信息     services.msc打开"服务&q ...

  7. 开源作业调度框架 - Quartz.NET - Cron表达式测试

    昨天简单写了一下如何使用Quzrtz.NET. 那么问题来了,我设置了Cron表达式之后如何知道是表达式是否按照预期的时间执行了呢? 我找到了些Cron表达式工具生成了表达式,确发现它们基本上没有进行 ...

  8. IIS中添加MIME类型

    今天上传一个html5后台管理模版的时候,在服务器上预览发现网页加载的远程woff类型的字体不显示,如下图所示: 在本地预览的时候,正常加载字体文件应该是这样的: 利用url访问字体文件的时候提示:该 ...

  9. Spirng MVC 重定向传递对象

    在 Spring MVC 中我们会经常遇到重定向. @RequestMapping("/order/saveorder.html") public String saveOrder ...

  10. 【Alpha 冲刺】 10/12

    今日任务总结 人员 今日原定任务 完成情况 遇到问题 贡献值 胡武成 完成app端api编写 未完成 Json格式出了点问题,修复中 孙浩楷 图片在线编辑器插件引入 未完成 耦合了,结果另外一个那边做 ...