导向滤波之图像融合(C++版) Image Fusion with Guided Filtering

本次代码效果图。
代码效果展示

关于这篇IEEE上高被引的论文的算法还原工作如下:
论文主页
首先关于这篇论文的思路分析,就是通过各种滤波之间的组合,筛选出两张图中各自细节丰富的那一部分,从而经行不同权值的融合,实现双重曝光的融合。如下图所示分为ABC三个步骤。
论文图片
下面按照步骤经行详细操作:
首先A步:
一、对原图I1和I2进行均值滤波,由此得到基层(Base layer) B1和B2。
B1B2
二、用原图I1和I2减去基层B1和B2得到细节层(Detali layer)D1和D2。
D1D2
三、对原图I1和I2进行拉普拉斯滤波,由此得到显著分布图(Saliency map)S1和S2。
S1S2
四、对S1和S2的每个像素点比较,选出对应像素点最大值得到S_max。
S_MAX
五、用S1和S2分别和S_max经行比较,若相等则为1,不等则为0,得到权重分布图(Weight map)P1和P2。
P1P2
六、用原图I为导向,权重图P为被引导图,进行导向滤波,第一次滤波核为9,正则化参数为0.03,得到提炼的权重分布图(Refined weight mapWb1和Wb2),第二次滤波核为5,正则化参数为0.003,得到提炼的权重分布图(Refined weight mapWd1和Wd2)
WBWD
七、将Wb1和Wb2相加得到Wb_max,Wd1和Wd2相加得到Wd_max。再将他们全部除以Wb_max得到归一化的Wb1、Wb2、Wd1、Wd2。
归一化
八、B1 x Wb1+ B2 x Wb2得到B_bar,D1 x Wd1+ D2 x Wd2得到D_bar
B_barD_bar
九、输出图像九等于B_bar+D_bar。
输出图像
输出图像和原图像比较:
比较图
代码先不说,这篇论文我也是给予很高的厚望,但是最后发现,这种方法对于曝光度差别很大的图片效果很不错,但是一旦曝光度差异不大,那么就会出现亮区暗区过渡区间的色彩严重失真。影响观感。最后我仔细端详论文中这两幅图我发现,高曝光那个图,周围的颜色很单一,就是墙的颜色,低曝光那个图中间看似色彩丰富,但是恰恰这一点,让你发现不了里面的是真,妙啊真的妙。
过渡边缘色彩失真
后我自己尝试使用提取图像的光照分量,对光照分量进行单独的引导滤波融合后再与原来的图像融合。细节存在,但是颜色失真。想法失败。失真图片

#include<opencv2/opencv.hpp>
#include <iostream>
#include<vector>
using namespace std;
using namespace cv;

//--------------------------------------------------------------
//函数名:guidedFilter_oneChannel
//函数功能:用于单通道图像(灰度图)的引导滤波函数;
//参数:Mat srcImg:输入图像,为单通道图像;
//参数:Mat guideImg:引导图像,为单通道图像,尺寸与输入图像一致;
//参数:Mat dstImg:输出图像,尺寸、通道数与输入图像吻合;
//参数:const int rad:滤波器大小,应该保证为奇数,默认值为9;
//参数:const double eps :防止a过大的正则化参数,
bool guidedFilter_oneChannel(Mat srcImg, Mat guideImg, Mat dstImg, const int rad = 9, const double eps = 0.01)
{
	//--------------确保输入参数正确-----------
	{
		if (!srcImg.data || srcImg.channels() != 1)
		{
			cout << "输入图像错误,请重新输入图像" << endl;
			return false;
		}

		if (!guideImg.data || guideImg.channels() != 1)
		{
			cout << "输入引导图像错误,请重新输入图像" << endl;
			return false;
		}

		if (guideImg.cols != srcImg.cols || guideImg.rows != srcImg.rows)
		{
			cout << "输入图像与引导图像尺寸不匹配,请重新确认" << endl;
			return false;
		}
		if (dstImg.cols != srcImg.cols || dstImg.rows != srcImg.rows || dstImg.channels() != 1)
		{
			cout << "参数输出图像错误,请重新确认" << endl;
			return false;
		}
		if (rad % 2 != 1)
		{
			cout << "参数“rad”应为奇数,请修改" << endl;
			return false;
		}

	}
	//--------------转换数值类型,并归一化-------------
	srcImg.convertTo(srcImg, CV_32FC1, 1.0 / 255.0);
	guideImg.convertTo(guideImg, CV_32FC1, 1.0 / 255.0);

	//--------------求引导图像和输入图像的均值图
	Mat mean_srcImg, mean_guideImg;
	boxFilter(srcImg, mean_srcImg, CV_32FC1, Size(rad, rad));
	boxFilter(guideImg, mean_guideImg, CV_32FC1, Size(rad, rad));

	Mat mean_guideImg_square, mean_guideImg_srcImg;
	boxFilter(guideImg.mul(guideImg), mean_guideImg_square, CV_32FC1, Size(rad, rad));
	boxFilter(guideImg.mul(srcImg), mean_guideImg_srcImg, CV_32FC1, Size(rad, rad));

	Mat var_guideImg = mean_guideImg_square - mean_guideImg.mul(mean_guideImg);
	Mat cov_guideImg_srcImg = mean_guideImg_srcImg - mean_guideImg.mul(mean_srcImg);

	Mat aImg = cov_guideImg_srcImg / (var_guideImg + eps);
	Mat bImg = mean_srcImg - aImg.mul(mean_guideImg);

	Mat mean_aImg, mean_bImg;
	boxFilter(aImg, mean_aImg, CV_32FC1, Size(rad, rad));
	boxFilter(bImg, mean_bImg, CV_32FC1, Size(rad, rad));

	dstImg = (mean_aImg.mul(guideImg) + mean_bImg);

	//dstImg.convertTo(dstImg, CV_8UC1, 255);//加这句最后返回的是CV_8UC1的图像

	return true;
}
//--------------------------------------------------------------
//函数名:guidedFilter_threeChannel
//函数功能:用于三通道图像(RGB彩色图)的引导滤波函数;
//参数:Mat srcImg:输入图像,为三通道图像;
//参数:Mat guideImg:引导图像,为三通道图像,尺寸与输入图像一致;
//参数:Mat dstImg:输出图像,尺寸、通道数与输入图像吻合;
//参数:const int rad:滤波器大小,应该保证为奇数,默认值为9;
//参数:const double eps :防止a过大的正则化参数,
bool guidedFilter_threeChannel(Mat srcImg, Mat guideImg, Mat dstImg, const int rad = 9, const double eps = 0.01)
{
	//----------------确保输入参数正确-------------
	{
		if (!srcImg.data || srcImg.channels() != 3)
		{
			cout << "输入图像错误,请重新输入图像" << endl;
			return false;
		}

		if (!guideImg.data || guideImg.channels() != 3)
		{
			cout << "输入引导图像错误,请重新输入图像" << endl;
			return false;
		}
		if (guideImg.cols != srcImg.cols || guideImg.rows != srcImg.rows)
		{
			cout << "输入图像与引导图像尺寸不匹配,请重新确认" << endl;
			return false;
		}
		if (rad % 2 != 1)
		{
			cout << "参数“rad”应为奇数,请修改" << endl;
			return false;
		}
	}
	vector<Mat> src_vec, guide_vec, dst_vec;
	split(srcImg, src_vec);
	split(guideImg, guide_vec);

	for (int i = 0; i < 3; i++)
	{
		Mat tempImg = Mat::zeros(srcImg.rows, srcImg.cols, CV_32FC1);
		guidedFilter_oneChannel(src_vec[i], guide_vec[i], tempImg, rad, eps);
		dst_vec.push_back(tempImg);
	}
	merge(dst_vec, dstImg);
	return true;
}

//根据2013年论文"Image Fusion with Guided Filtering"引导滤波的图像融合
//double_exposure_HDR

//函数名:double_exposure_HDR
//函数功能:用于同一场景的不同曝光融合
//参数:Mat Mat I1:输入的短曝光图像光照分量
//参数:Mat Mat I2:输入的长曝光图像光照分量
//返回:Mat 融合图像
Mat double_exposure_HDR(Mat I1, Mat I2)
{
	//论文中的A步Two-Scale Image Decomposition(双尺度分解)****************************************************
	Mat B1, B2;

	//average filtering得到基础层base layer
	blur(I1, B1, Size(3,3));
	blur(I2, B2, Size(3,3));

	//imshow("短曝光基础层", B1);
	//imshow("长曝光基础层", B2);

	//相减得到细节层detail layer
	Mat D1, D2;
	D1 = I1 - B1;
	D2 = I2 - B2;

	//imshow("短曝光细节层", D1);
	//imshow("长曝光细节层", D2);

	//论文中的B步Weight Map Construction With Guided Filtering(构建权重图)*********************************
	Mat H1;
	Mat	H2;
	//对原图来一次拉普拉斯滤波
	Laplacian(I1, H1,0.3);
	Laplacian(I2, H2,0.3);

	//排除负值
	H1 = abs(H1);
	H2 = abs(H2);

	//imshow("短曝光拉普拉斯滤波", H1);
	//imshow("长曝光拉普拉斯滤波", H2);

	//高斯滤波一次
	Mat S1, S2;
	GaussianBlur(H1, S1, Size(11, 11), 9);
	GaussianBlur(H2, S2, Size(11, 11), 9);


	//imshow("短曝光显著像素分布图Saliency map", S1);
	//imshow("长曝光显著像素分布图Saliency map", S2);

	//再去根据论文的像素显著性 和 空间连续性 求权重图 P1  P2

	Mat P1;
	Mat P2;

	P1 = Mat(I1.rows, I1.cols, CV_8UC3);
	P2 = Mat(I1.rows, I1.cols, CV_8UC3);

	//再求之前要求一个总的显著分布图s_max
	Mat S_max;
	S_max = Mat(I1.rows, I1.cols, CV_8UC3);
	for (int k = 0; k < I1.channels(); k++) 
	{
		for (int j = 0; j < I1.cols; j++)
		{
			for (int i = 0; i < I1.rows; i++)
			{
				S_max.at<Vec3b>(i, j)[k] = ((S1.at<Vec3b>(i, j)[k] > S2.at<Vec3b>(i, j)[k]) ? S1.at<Vec3b>(i, j)[k] : S2.at<Vec3b>(i, j)[k]);
			}
		}
	}
	//imshow("最大显著分布图", S_max);

	//下面求那两个权重分布图P1,P2
	for (int k = 0; k < I1.channels(); k++)
	{
		for (int j = 0; j < I1.cols; j++)
		{
			for (int i = 0; i < I1.rows; i++)
			{
				P1.at<Vec3b>(i, j)[k] = (S1.at<Vec3b>(i, j)[k] == S_max.at<Vec3b>(i, j)[k]) ? 1 : 0;

				P2.at<Vec3b>(i, j)[k]= (S2.at<Vec3b>(i, j)[k] == S_max.at<Vec3b>(i, j)[k]) ? 1 : 0;

			}
		}
	}
	//imshow("短曝光权重分布图weight map", P1*255);//乘以255是为了可视化,白色代表1黑色代表0
	//imshow("长曝光权重分布图weight map", P2*255);//乘以255是为了可视化,白色代表1黑色代表0

	//这样我们就得到了关于各个源图像的粗略权重值。
	//但是由于多个图像间容易产生噪声,并可能存在不完全对齐等问题,很容易造成融合后的图像有伪影等。
	//因此在基于空间连续性的思想下,需要对得到的权重图进行导向滤波。
	Mat Wb1, Wb2;
	Wb1 = Mat(I1.rows, I1.cols, CV_32FC3);
	Wb2 = Mat(I1.rows, I1.cols, CV_32FC3);

	guidedFilter_threeChannel(P1, I1, Wb1,9, 0.03);
	guidedFilter_threeChannel(P2, I2, Wb2,9, 0.03);

	//imshow("短曝光提炼权重分布图refined weight map Wb1", Wb1*255);//乘以255是为了可视化
	//imshow("长曝光提炼权重分布图redined weight map Wb2", Wb2*255);//乘以255是为了可视化
	Mat Wd1, Wd2;
	
	Wd1 = Mat(I1.rows, I1.cols, CV_32FC3);
	Wd2 = Mat(I1.rows, I1.cols, CV_32FC3);

	guidedFilter_threeChannel(P1, I1, Wd1, 5, 0.003);
	guidedFilter_threeChannel(P2, I2, Wd2, 5, 0.003);

	//imshow("短曝光提炼权重分布图refined weight map Wd1", Wd1*255);//乘以255是为了可视化
	//imshow("长曝光提炼权重分布图redined weight map Wd2", Wd2*255);//乘以255是为了可视化

	//得到A步骤中四个图对应的四个权重图

	//论文中的C步Two-Scale Image Reconstruction(融合重建)****************************************************
	
	Mat Wb_max, Wd_max;
	Wb_max = Mat(I1.rows, I1.cols, CV_32FC3);
	Wd_max = Mat(I1.rows, I1.cols, CV_32FC3);
	Wb_max = Wb1 + Wb2;
	Wd_max = Wd1 + Wd2;

	//归一化
	for (int k = 0; k < I1.channels(); k++)
	{
		for (int j = 0; j < I1.cols; j++)
		{
			for (int i = 0; i < I1.rows; i++)
			{
				Wb1.at<Vec3f>(i, j)[k] /= Wb_max.at<Vec3f>(i, j)[k];
				Wb2.at<Vec3f>(i, j)[k] /= Wb_max.at<Vec3f>(i, j)[k];
				Wd1.at<Vec3f>(i, j)[k] /= Wd_max.at<Vec3f>(i, j)[k];
				Wd2.at<Vec3f>(i, j)[k] /= Wd_max.at<Vec3f>(i, j)[k];
			}
		}
	}
	
	//imshow("归一化短曝光提炼权重分布图Wb1", Wb1*255);
	//imshow("归一化长曝光提炼权重分布图Wb2", Wb2*255);
	//imshow("归一化短曝光提炼权重分布图Wd1", Wd1*255);
	//imshow("归一化长曝光提炼权重分布图Wd2", Wd2*255);
		
	Mat B_bar, D_bar;
	B_bar = Mat(I1.rows, I1.cols, CV_8UC3);
	D_bar = Mat(I1.rows, I1.cols, CV_8UC3);

	for (int k = 0; k < I1.channels(); k++)
	{
		for (int j = 0; j < I1.cols; j++)
		{
			for (int i = 0; i < I1.rows; i++)
			{

				B_bar.at<Vec3b>(i, j)[k] = B1.at<Vec3b>(i, j)[k] * Wb1.at<Vec3f>(i, j)[k]
					+ B2.at<Vec3b>(i, j)[k] * Wb2.at<Vec3f>(i, j)[k];

				D_bar.at<Vec3b>(i, j)[k] = D1.at<Vec3b>(i, j)[k] * Wd1.at<Vec3f>(i, j)[k]
					+ D2.at<Vec3b>(i, j)[k] * Wd2.at<Vec3f>(i, j)[k];
			}
		}
	}
	//融合输出
	Mat out;

	out = B_bar + D_bar;
	//imshow("B_bar", B_bar);
	//imshow("D_bar", D_bar);
	
	return out;
}

int main() 
{
	Mat src_UE = imread("C:\\Users\\hl\\Desktop\\I1.jpg", IMREAD_COLOR);
	Mat src_OE = imread("C:\\Users\\hl\\Desktop\\I2.jpg", IMREAD_COLOR);

	Mat out;

	out=double_exposure_HDR(src_UE,src_OE);

	imshow("过曝光", src_OE);
	imshow("欠曝光", src_UE);
	imshow("引导滤波后", out);

	waitKey();
	return 0;
}

https://blog.csdn.net/u013921430/article/details/99695647
声明:关于导向滤波C++函数模块部分借鉴了不用先生 2019-08-18 10:28:05的一篇【图像处理】引导滤波(guided image filtering)

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐