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)单料,不過(guò)如果能選到一個(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)過(guò)圖像配準(zhǔn)之后沉颂,就需要進(jìn)行圖像融合条摸。而圖像融合的目的就是使兩幅圖像的重疊區(qū)域過(guò)渡自然且平滑。在上圖中铸屉,可以看到明顯的邊界钉蒲,這對(duì)拼接來(lái)說(shuō)是無(wú)法接受的。這主要是因?yàn)橥獠苛炼鹊淖兓ㄌ炜诊h過(guò)了一朵萌萌的云彩彻坛?)以及曝光時(shí)相機(jī)參數(shù)不一致導(dǎo)致的顷啼。要消除或緩和這種現(xiàn)象,就需要進(jìn)行圖像融合昌屉。
主流的圖像融合算法有:
1)加權(quán)平均法钙蒙。這個(gè)很好理解,即簡(jiǎn)單的使用加權(quán)的方式從左邊過(guò)渡到右邊间驮。這種方法效果一般躬厌,但算法實(shí)現(xiàn)極其簡(jiǎn)單,速度快蜻牢。課設(shè)時(shí)我用的就是這個(gè)方法烤咧。
2)羽化算法 。這種方法過(guò)渡會(huì)比加權(quán)平均法自然抢呆,但會(huì)造成不好的模糊效果煮嫌。
3)拉普拉斯金字塔融合。有的地方也稱為多分辨率融合算法抱虐。這種方法是將圖像建立一個(gè)拉普拉斯金字塔昌阿,其中金字塔的每一層都包含了圖像不同的頻段。分開不同頻段進(jìn)行融合效果出奇的好恳邀。這也是本文主要介紹的方法懦冰。
高斯金字塔、拉普拉斯金字塔
之前在寫SIFT相關(guān)博客的時(shí)候說(shuō)到過(guò)高斯金字塔谣沸。圖像金字塔的意思無(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嘀粱,反過(guò)來(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è)新的金字塔重建出最終的圖像,重建的過(guò)程跟一般的拉普拉斯金字塔一樣听诸。首先對(duì)IMG1上采樣坐求,然后跟新金字塔的頂層相加,得到IMG2晌梨。IMG2進(jìn)行上采樣后跟下一層相加桥嗤,得到IMG3赛糟。重復(fù)這個(gè)過(guò)程,最終得到的結(jié)果就是拉普拉斯金字塔融合算法的結(jié)果砸逊。
因?yàn)閙ask建立金字塔的過(guò)程中使用了高斯模糊璧南,所以融合的邊緣是比較平滑的。
算法實(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的元旦了,如果你能看到這里员辩,就祝你新年快樂(lè)吧~~
Reference:
圖像融合之拉普拉斯融合
圖像拉普拉斯金字塔融合(Laplacian Pyramid Blending)
【OpenCV入門教程之十三】OpenCV圖像金字塔:高斯金字塔盒粮、拉普拉斯金字塔與圖片尺寸縮放