Jacob的全景圖像融合算法系列
OpenCV 尺度不變特征檢測(cè):SIFT、SURF、BRISK、ORB
OpenCV 匹配興趣點(diǎn):SIFT、SURF 和二值描述子
OpenCV 估算圖像的投影關(guān)系:基礎(chǔ)矩陣和RANSAC
OpenCV 單應(yīng)矩陣應(yīng)用:全景圖像融合原理
圖像融合:拉普拉斯金字塔融合算法
繼圖像拼接的課程設(shè)計(jì)之后,對(duì)這方面依舊十分感興趣。很巧合的是,數(shù)圖老師表示剛好手上有這么一個(gè)項(xiàng)目,要用到這方面的知識(shí),可以讓我去作為畢業(yè)設(shè)計(jì)。雖然距離畢業(yè)還遠(yuǎn),不過如果能選到一個(gè)感興趣并且有一定深度的題目還是很好的。這些天在看論文的時(shí)候,了解到這么一個(gè)強(qiáng)大的算法,打算寫篇文記錄下心得吧。
一些圖像融合算法
圖像拼接主要可以分為兩個(gè)步驟:圖像配準(zhǔn)和圖像融合。其中圖像配準(zhǔn)的目的是將圖一場(chǎng)景中不同視角的圖像投影到同一平面并進(jìn)行對(duì)準(zhǔn)。比如我之前這篇博客中使用SIFT特征檢測(cè)和單應(yīng)矩陣的目的,就是進(jìn)行圖像配準(zhǔn)。

經(jīng)過圖像配準(zhǔn)之后,就需要進(jìn)行圖像融合。而圖像融合的目的就是使兩幅圖像的重疊區(qū)域過渡自然且平滑。在上圖中,可以看到明顯的邊界,這對(duì)拼接來(lái)說(shuō)是無(wú)法接受的。這主要是因?yàn)橥獠苛炼鹊淖兓ㄌ炜诊h過了一朵萌萌的云彩?)以及曝光時(shí)相機(jī)參數(shù)不一致導(dǎo)致的。要消除或緩和這種現(xiàn)象,就需要進(jìn)行圖像融合。
主流的圖像融合算法有:
1)加權(quán)平均法。這個(gè)很好理解,即簡(jiǎn)單的使用加權(quán)的方式從左邊過渡到右邊。這種方法效果一般,但算法實(shí)現(xiàn)極其簡(jiǎn)單,速度快。課設(shè)時(shí)我用的就是這個(gè)方法。
2)羽化算法 。這種方法過渡會(huì)比加權(quán)平均法自然,但會(huì)造成不好的模糊效果。
3)拉普拉斯金字塔融合。有的地方也稱為多分辨率融合算法。這種方法是將圖像建立一個(gè)拉普拉斯金字塔,其中金字塔的每一層都包含了圖像不同的頻段。分開不同頻段進(jìn)行融合效果出奇的好。這也是本文主要介紹的方法。
高斯金字塔、拉普拉斯金字塔
之前在寫SIFT相關(guān)博客的時(shí)候說(shuō)到過高斯金字塔。圖像金字塔的意思無(wú)非就是對(duì)原圖進(jìn)行下采樣,然后塞到一個(gè)C++的Vector或者其他什么語(yǔ)言中的數(shù)組里。在可視化的時(shí)候,最大的圖像放在最下面,最小的圖像放在最上面,所以稱為圖像金字塔。

而高斯金字塔的每一層的構(gòu)建步驟分為兩步:首先對(duì)下一層的圖像進(jìn)行高斯模糊。這個(gè)步驟相信讀者都了解,是圖像處理中最基本的概念。然后刪除模糊后的圖像的偶數(shù)行和列,就得到了當(dāng)前層的圖像了。不斷進(jìn)行這個(gè)步驟,最終就得到了高斯金字塔。
拉普拉斯金字塔的構(gòu)造需要用到高斯金字塔。拉普拉斯金字塔第i層的數(shù)學(xué)定義如下

意思是拉普拉斯金字塔每一層的圖像為同一層高斯金字塔的圖像減去上一層的圖像進(jìn)行上采樣并高斯模糊的結(jié)果。說(shuō)的有點(diǎn)繞,可以看網(wǎng)上的這幅圖進(jìn)行理解。

算法原理
1)首先建立兩幅圖像高斯金字塔,然后建立一定層數(shù)的拉普拉斯金字塔。拉普拉斯金字塔的層數(shù)越高,融合效果越好。層數(shù)N作為一個(gè)參數(shù)。
2)傳入一個(gè)mask掩膜,代表了融合的位置。比如說(shuō)想在兩圖的中間進(jìn)行融合,那么掩膜圖像的左半為255,右半為0,反過來(lái)是一樣的。根據(jù)這個(gè)mask建立一個(gè)高斯金字塔,用于后續(xù)融合,層數(shù)為N+1。
3)根據(jù)mask將兩幅圖像的拉普拉斯金字塔的圖像進(jìn)行相加,mask為權(quán)值。相加的結(jié)果即為一個(gè)新的金字塔。同時(shí),兩幅圖像的高斯金字塔的N+1層也進(jìn)行這個(gè)操作,記這個(gè)圖像為IMG1。
4)根據(jù)這個(gè)新的金字塔重建出最終的圖像,重建的過程跟一般的拉普拉斯金字塔一樣。首先對(duì)IMG1上采樣,然后跟新金字塔的頂層相加,得到IMG2。IMG2進(jìn)行上采樣后跟下一層相加,得到IMG3。重復(fù)這個(gè)過程,最終得到的結(jié)果就是拉普拉斯金字塔融合算法的結(jié)果。
因?yàn)閙ask建立金字塔的過程中使用了高斯模糊,所以融合的邊緣是比較平滑的。
算法實(shí)現(xiàn)
類實(shí)現(xiàn)主要參考其他博客
/**************************************************************
* Created by 楊幫杰 on 1/1/2019
* Right to use this code in any way you want without
* warranty, support or any guarantee of it working
* E-mail: yangbangjie1998@qq.com
* Association: SCAU 華南農(nóng)業(yè)大學(xué)
**************************************************************/
#include <opencv2/opencv.hpp>
#include <iostream>
#include <string>
using namespace std;
using namespace cv;
#define IMG1_PATH "/home/jacob/圖片/blend1.jpg"
#define IMG2_PATH "/home/jacob/圖片/blend2.jpg"
/**
* @brief The LaplacianBlending class
* @private leftImg & rightImg 用于拼接的左圖和右圖
* @private blendMask 用于融合的掩膜,值為加權(quán)平均的系數(shù)
* @ref https://blog.csdn.net/abcjennifer/article/details/7628655
*/
class LaplacianBlending {
private:
Mat leftImg;
Mat rightImg;
Mat blendMask;
//Laplacian Pyramids
vector<Mat> leftLapPyr, rightLapPyr, resultLapPyr;
Mat leftHighestLevel, rightHighestLevel, resultHighestLevel;
//mask為三通道方便矩陣相乘
vector<Mat> maskGaussianPyramid;
int levels;
void buildPyramids()
{
buildLaplacianPyramid(leftImg, leftLapPyr, leftHighestLevel);
buildLaplacianPyramid(rightImg, rightLapPyr, rightHighestLevel);
buildGaussianPyramid();
}
void buildGaussianPyramid()
{
//金字塔內(nèi)容為每一層的掩模
assert(leftLapPyr.size()>0);
maskGaussianPyramid.clear();
Mat currentImg;
cvtColor(blendMask, currentImg, CV_GRAY2BGR);
//保存mask金字塔的每一層圖像
maskGaussianPyramid.push_back(currentImg); //0-level
currentImg = blendMask;
for (int l = 1; l<levels + 1; l++) {
Mat _down;
if (leftLapPyr.size() > l)
pyrDown(currentImg, _down, leftLapPyr[l].size());
else
pyrDown(currentImg, _down, leftHighestLevel.size()); //lowest level
Mat down;
cvtColor(_down, down, CV_GRAY2BGR);
//add color blend mask into mask Pyramid
maskGaussianPyramid.push_back(down);
string winName = to_string(l);
imshow(winName,down);
currentImg = _down;
}
}
void buildLaplacianPyramid(const Mat& img, vector<Mat>& lapPyr, Mat& HighestLevel)
{
lapPyr.clear();
Mat currentImg = img;
for (int l = 0; l<levels; l++) {
Mat down, up;
pyrDown(currentImg, down);
pyrUp(down, up, currentImg.size());
Mat lap = currentImg - up;
lapPyr.push_back(lap);
currentImg = down;
}
currentImg.copyTo(HighestLevel);
}
Mat reconstructImgFromLapPyramid()
{
//將左右laplacian圖像拼成的resultLapPyr金字塔中每一層
//從上到下插值放大并與殘差相加,即得blend圖像結(jié)果
Mat currentImg = resultHighestLevel;
for (int l = levels - 1; l >= 0; l--)
{
Mat up;
pyrUp(currentImg, up, resultLapPyr[l].size());
currentImg = up + resultLapPyr[l];
}
return currentImg;
}
void blendLapPyrs()
{
//獲得每層金字塔中直接用左右兩圖Laplacian變換拼成的圖像resultLapPyr
resultHighestLevel = leftHighestLevel.mul(maskGaussianPyramid.back()) +
rightHighestLevel.mul(Scalar(1.0, 1.0, 1.0) - maskGaussianPyramid.back());
for (int l = 0; l<levels; l++)
{
Mat A = leftLapPyr[l].mul(maskGaussianPyramid[l]);
Mat antiMask = Scalar(1.0, 1.0, 1.0) - maskGaussianPyramid[l];
Mat B = rightLapPyr[l].mul(antiMask);
Mat blendedLevel = A + B;
resultLapPyr.push_back(blendedLevel);
}
}
public:
LaplacianBlending(const Mat& _left, const Mat& _right, const Mat& _blendMask, int _levels) ://construct function, used in LaplacianBlending lb(l,r,m,4);
leftImg(_left), rightImg(_right), blendMask(_blendMask), levels(_levels)
{
assert(_left.size() == _right.size());
assert(_left.size() == _blendMask.size());
//創(chuàng)建拉普拉斯金字塔和高斯金字塔
buildPyramids();
//每層金字塔圖像合并為一個(gè)
blendLapPyrs();
};
Mat blend()
{
//重建拉普拉斯金字塔
return reconstructImgFromLapPyramid();
}
};
Mat LaplacianBlend(const Mat &left, const Mat &right, const Mat &mask)
{
LaplacianBlending laplaceBlend(left, right, mask, 10);
return laplaceBlend.blend();
}
int main() {
Mat leftImg = imread(IMG1_PATH);
Mat rightImg = imread(IMG2_PATH);
int hight = leftImg.rows;
int width = leftImg.cols;
Mat leftImg32f, rightImg32f;
leftImg.convertTo(leftImg32f, CV_32F);
rightImg.convertTo(rightImg32f, CV_32F);
//創(chuàng)建用于混合的掩膜,這里在中間進(jìn)行混合
Mat mask = Mat::zeros(hight, width, CV_32FC1);
mask(Range::all(), Range(0, mask.cols * 0.5)) = 1.0;
Mat blendImg = LaplacianBlend(leftImg32f, rightImg32f, mask);
blendImg.convertTo(blendImg, CV_8UC3);
imshow("left", leftImg);
imshow("right", rightImg);
imshow("blended", blendImg);
waitKey(0);
return 0;
}



后記
可以看到,融合結(jié)果簡(jiǎn)直驚艷。話說(shuō)寫完這篇博客的時(shí)候已經(jīng)是2019的元旦了,如果你能看到這里,就祝你新年快樂吧~~
Reference:
圖像融合之拉普拉斯融合
圖像拉普拉斯金字塔融合(Laplacian Pyramid Blending)
【OpenCV入門教程之十三】OpenCV圖像金字塔:高斯金字塔、拉普拉斯金字塔與圖片尺寸縮放