zoukankan      html  css  js  c++  java
  • [计算机视觉] 图像拼接 Image Stitching

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

    作业要求:

            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,就也不能解决了。(想想就很难_(:зゝ∠)_)

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

  • 相关阅读:
    Windows Server环境下消息队列之ActiveMQ实战
    Javascript 面向对象编程(一):封装
    Python常用函数
    redis源码(一):为redis添加自己的列表类型
    导出数据到Excel表格
    Oracle————存储过程与函数
    Solr搜索引擎 — 通过mysql配置数据源
    redis的安装和使用【2】redis的java操作
    MySql(四)Select条件查询
    MySql (二)入门语句和基本操作
  • 原文地址:https://www.cnblogs.com/jackytang/p/9415268.html
Copyright © 2011-2022 走看看