做业要求: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,就也不能解决了。(想一想就很难_(:зゝ∠)_)
若是有大佬能解决上面问题的能够跟我说说,也想了解一下_(:зゝ∠)_