[計算機視覺] 圖像拼接 Image Stitching

 

[計算機視覺] 圖像拼接 Image Stitching

做業要求:html

        一、將多張圖片合併拼接成一張全景圖(看下面效果圖)ios

        二、儘可能用C/C++(老師說用matlab會給很低的分_(:зゝ∠)_,因此下面的代碼所有都用C++來寫)算法

 

效果圖:函數

 

實現大體步驟:post

一、SIFT算法進行圖像特徵提取(SIFT算法是http://blog.csdn.net/v_JULY_v/article/details/6245939找的,不過是用C寫,不太好得到中間結果。爲了方便咱們此次做業使用,我改寫成C++代碼)flex

二、利用RANSAC算法進行圖像特徵匹配ui

三、利用匹配關鍵點進行圖像拼接(Blending)spa

 

實現步驟詳解:.net

一、SIFT算法進行圖像特徵提取:code

        SIFT算法在這裏就不詳細說了,上面的連接已經講的很詳細了(使用上面的代碼要配置opencv環境,挺簡單的,網上不少教程)。我是將上面連接的代碼改寫成C++,封裝了一些方法,使得可以提取中間結果。

        SIFT算法的輸入是圖片,咱們須要的輸出是各個關鍵點的位置、128維描述子(用於關鍵點匹配)。而代碼把一個關鍵點的這些信息都封裝在一個結構體Keypoint裏面。同時,代碼將全部的關鍵點Keypoint保存爲一個鏈表List形式,便可以根據第一個節點訪問到全部的Keypoint節點。

        所以我在改寫後的MySift.h文件裏,添加了幾個方法,一個是SIFT的方法入口SiftMainProcess(),一個是獲取處理後獲得的關鍵點的頭結點方法getFirstKeyDescriptors()。

MySift.h:

 

 
  
MySift.cpp:

 

       因爲.cpp代碼有1000+行,因爲篇幅問題,在這裏就不放出來了_(:зゝ∠)_。有須要的能夠私聊下我哈_(:зゝ∠)_或者直接對着上面連接給的代碼找一下就行了,函數名都同樣的。

 

階段結果:

        黃色圈圈的就是識別出來的關鍵點。

     

 

二、利用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基本一致了)

 

 

三、利用匹配關鍵點進行圖像拼接(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.  
    }

 

 
  
階段結果:

 

四、最後再放上使用上面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,就也不能解決了。(想一想就很難_(:зゝ∠)_)

        若是有大佬能解決上面問題的能夠跟我說說,也想了解一下_(:зゝ∠)_

相關文章
相關標籤/搜索