做業要求: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:
因爲.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:
階段結果:
(能夠看到轉移向量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:
四、最後再放上使用上面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,就也不能解決了。(想一想就很難_(:зゝ∠)_)
若是有大佬能解決上面問題的能夠跟我說說,也想了解一下_(:зゝ∠)_