[C++/OpenCV] 数字图像处理:使用OpenCV实现基于L0范数的梯度最小化图像平滑滤波及完整源代码

本文使用C++语言和OpenCV实现基于L0范数的梯度最小化图像平滑滤波,具体原理请参考文献:

"Image Smoothing via L0 Gradient Minimization", Li Xu, Cewu Lu, Yi Xu, Jiaya Jia, ACM Transactions on Graphics, (SIGGRAPH Asia 2011), 2011.

文中提供了MATLAB下的源代码,但是为了跨语言和平台使用,本文将其方法使用OpenCV进行了重写。

  • 说明:

(1)本文代码的实现环境为:Visual Studio 2015, OpenCV 3.1.0 - 32bit

(2)本文代码仅用于RGB彩色图像,无法用于单通道灰度图像,灰度图像的实现要简单些,如果需要可以对本文代码稍作改动即可,以后有时间我也会补充这个缺陷。

(3)因为比较急用,代码实现没有做细致的优化,处理速度比matlab版的稍慢一点点,但影响不大,小图像的处理时间差距可以忽略不计,本文中的测试图像(1280*720尺寸)处理时间比matlab慢估计1-2秒左右(没有精确计算),如果需要可以自行优化,之后我也会进行优化并更新代码。

(4)本文代码共两个文件,头文件myFunction.h和源文件myFunction.cpp,源代码在下面,只要搭建好OpenCV环境,无须做任何修改可以直接调用函数使用。

  • 问题反馈与交流:

邮箱:[email protected],发邮件即可,我可以收到,请勿加QQ。

  • 使用方法(测试代码):
//主程序

#include 
#include 
#include 
#include "myFunction.h"

using namespace cv;
using namespace std;

int main()
{
    //读取图像,必须是三通道彩色图像
	Mat input = imread("f:/temp/image.png");
    //滤波
	Mat output= L0Smoothing(input);    //L0Smoothing即是L0滤波函数
	cout << endl;
    //显示滤波后图像
	imshow("output",output);
	waitKey(0);

	system("pause");
	return 0;
}
  • 源代码:

头文件 myFunction.h:

//头文件 myFunction.h

#pragma once
#include 
#include 

using namespace cv;
using namespace std;

/*****
* 函数名:myInitMatFromArray2d
* 说明:从二维数组中初始化Mat矩阵
* 参数:
*	1.float *t_array:二维数组,t_array[第一维:行数][第二维:列数]
*	2.int t_rows:t_array第一维大小
*	3.int t_cols:t_array第二维大小
*	4.Mat& t_mat:被初始化的Mat矩阵,要求:Mat m_mat(Size(t_cols,t_rows),CV_32F(1))
*****/
void myInitMatFromArray2d(float *t_array, int t_rows, int t_cols, Mat& t_mat);

/*****
* 函数名:myInitMatFromArray3d
* 说明:从三维数组中初始化Mat矩阵
* 参数:
*	1.float *t_array:三维数组,t_array[第一维:通道数][第二维:行数][第三维:列数]
*	2.int t_rows:t_array第二维大小
*	3.int t_cols:t_array第三维大小
*	4.Mat& t_mat:被初始化的Mat矩阵,要求:Mat m_mat(Size(t_cols,t_rows),CV_32F(3))
*****/
void myInitMatFromArray3d(float *t_array, int t_rows, int t_cols, Mat& t_mat);

/*****
* 函数名:myShowMat
* 说明:打印Mat矩阵
* 参数:
*	1.Mat t_mat:待打印的Mat矩阵,不限制通道数,要求t_Mat.depth()==CV_32F||CV_8U
*****/
void myShowMat(Mat t_mat);

/*****
* 函数名:myImshowRGB
* 说明:分通道显示图像
* 参数:
*	1.Mat t_mat:带显示图像矩阵,要求t_Mat.type()==CV_8UC3
*****/
void myImshowRGB(Mat t_mat);

/*****
* 函数名:abs_complex
* 说明:计算复数Mat矩阵的模(或模的平方)
* 参数:
*	1.Mat t_mat:待计算的Mat矩阵,要求t_mat.type()==CV_32FC(2)
*	2.bool is_sqrt==true:可选参数,true时为求模,false时为求模的平方
* 返回值:
*	t_mat的模的矩阵,通常为CV_32FC(1)
*****/
Mat abs_complex(Mat t_mat, bool is_sqrt=true);

/*****
* 函数名:myPow
* 说明:计算Mat矩阵的点幂,对cv::Pow的封装
* 参数:
*	1.Mat t_mat:待计算的Mat矩阵
*	2.double t_n:幂
* 返回值:
*	t_mat的点幂矩阵
*****/
Mat myPow(Mat t_mat, double t_n);

/*****
* 函数名:diff_v_3d
* 说明:计算Mat矩阵行差分,实现matlab中diff(x,1,1)的功能
* 参数:
*	1.Mat t_mat:待计算的Mat矩阵,要求t_mat.channels()==3
*	2.int t_step=1:预留参数,为了与matlab的diff函数匹配,对函数功能无影响
*	3.int t_dim=1:预留参数,为了与matlab的diff函数匹配,对函数功能无影响
* 返回值:
*	差分结果矩阵,行数比t_mat少1
*****/
Mat diff_v_3d(Mat t_input, int t_step = 1, int t_dim = 1);

/*****
* 函数名:diff_h_3d
* 说明:计算Mat矩阵列差分,实现matlab中diff(x,1,2)的功能
* 参数:
*	1.Mat t_mat:待计算的Mat矩阵,要求t_mat.channels()==3
*	2.int t_step=1:预留参数,为了与matlab的diff函数匹配,对函数功能无影响
*	3.int t_dim=2:预留参数,为了与matlab的diff函数匹配,对函数功能无影响
* 返回值:
*	差分结果矩阵,列数比t_mat少1
*****/
Mat diff_h_3d(Mat t_input, int t_step = 1, int t_dim = 2);

/*****
* 函数名:psf2otf
* 说明:实现matlab中psf2otf函数功能,参数及功能参照matlab中的psf2otf函数
* 参数:
*	1.Mat psf:输入psf矩阵
*	2.Size size:输出尺寸
* 返回值:
*	计算后的结果矩阵
*****/
Mat psf2otf(Mat psf, Size size);

/*****
* 函数名:L0Smoothing
* 说明:计算基于L0梯度最小化的图像平滑滤波
* 参数:
*	1.Mat& t_Im:输入图像,要求t_Im.type()==CV_8UC3
*	2.float t_lambda=0.02:可选参数,请参考论文中论述
*	3.float t_kappa=2.0:可选参数,请参考论文中论述
* 返回值:
*	计算后的图像矩阵,矩阵类型为CV_8UC3
*****/
Mat L0Smoothing(Mat& t_Im, float t_lambda=0.02, float t_kappa=2.0);

源文件 myFunction.cpp: 

//源文件 myFunction.cpp

#include "myFunction.h"

void myInitMatFromArray2d(float *t_array, int t_rows, int t_cols, Mat& t_mat)
{
	for (int i = 0; i < t_rows; i++)
	{
		for (int j = 0; j < t_cols; j++)
		{
			float tmp= *(t_array + j + i*t_cols);
			t_mat.at(i, j) = tmp;
		}
	}
}

void myInitMatFromArray3d(float *t_array, int t_rows, int t_cols, Mat& t_mat)
{
	Mat output[3];
	split(t_mat, output);
	for (int i = 0; i < 3;i++)
	{
		for (int j = 0; j < t_rows;j++)
		{
			for (int k = 0; k < t_cols;k++)
			{
				output[i].at(j, k) = *(t_array + i*t_rows*t_cols + j*t_cols + k);
			}
		}
	}
	merge(output, 3, t_mat);
}

void myShowMat(Mat t_mat)
{
	int chn = t_mat.channels();
	cout << ">>>Mat channels = " << chn << endl;
	Mat *output = new Mat[chn];
	split(t_mat, output);
	for (int c = 0; c < chn; c++)
	{
		cout << "channel " << c << ":" << endl;
		for (int i = 0; i < t_mat.rows; i++)
		{
			for (int j = 0; j < t_mat.cols; j++)
			{
				cout.width(5);
				int t_depth = t_mat.depth();
				if(t_depth==CV_32F)
					cout << output[c].at(i, j) << " ";
				else if(t_depth==CV_8U)
					cout << int(output[c].at(i, j)) << " ";
			}
			cout << endl;
		}
		cout << endl;
	}
	delete output;
}

void myImshowRGB(Mat t_mat)
{
	Mat mat_split[3];
	split(t_mat, mat_split);
	
	imshow("Channel R", mat_split[2]);
	imshow("Channel G", mat_split[1]);
	imshow("Channel B", mat_split[0]);
}


Mat abs_complex(Mat t_mat,bool is_sqrt)
{
	Mat input[2];
	Mat output;
	
	split(t_mat, input);
	pow(input[0], 2.0, input[0]);
	pow(input[1], 2.0, input[1]);
	output = input[0] + input[1];
	if(is_sqrt)
		sqrt(output, output);
	return output;
}

Mat myPow(Mat t_mat, double t_n)
{
	Mat output;
	pow(t_mat, t_n, output);
	return output;
}

Mat diff_v_3d(Mat t_input, int t_step, int t_dim)
{
	int cx = t_input.cols;
	int cy = t_input.rows;

	Mat input[3];
	Mat output[3] = { Mat::zeros(cy - 1,cx,CV_32F),Mat::zeros(cy - 1,cx,CV_32F),Mat::zeros(cy - 1,cx,CV_32F) };
	split(t_input, input);

	//Mat output(Size(cx, cy-1), CV_32FC2,Scalar(0));

	for (int i = 0; i < 3; i++)
	{
		for (int j = 1; j < cy; j++)
		{
			Mat line_1(input[i], Rect(0, j - 1, cx, 1));
			Mat line_2(input[i], Rect(0, j, cx, 1));
			Mat line_sub = line_2 - line_1;
			Mat roi_output(output[i], Rect(0, j - 1, cx, 1));
			line_sub.copyTo(roi_output);
		}
	}
	Mat rtnOutput;
	merge(output, 3, rtnOutput);

	return rtnOutput;
}

Mat diff_h_3d(Mat t_input, int t_step, int t_dim)
{
	int cx = t_input.cols;
	int cy = t_input.rows;

	Mat input[3];
	Mat output[3] = { Mat::zeros(cy,cx - 1,CV_32F),Mat::zeros(cy,cx - 1,CV_32F),Mat::zeros(cy,cx - 1,CV_32F) };
	split(t_input, input);

	//Mat output(Size(cx, cy-1), CV_32FC2,Scalar(0));

	for (int i = 0; i < 3; i++)
	{
		for (int j = 1; j < cx; j++)
		{
			Mat line_1(input[i], Rect(j - 1, 0, 1, cy));
			Mat line_2(input[i], Rect(j, 0, 1, cy));
			Mat line_sub = line_2 - line_1;
			Mat roi_output(output[i], Rect(j - 1, 0, 1, cy));
			line_sub.copyTo(roi_output);
		}
	}
	Mat rtnOutput;
	merge(output, 3, rtnOutput);

	return rtnOutput;
}

Mat psf2otf(Mat psf, Size size)
{
	Mat otf = Mat::zeros(size.height, size.width, psf.type());
	
	//填充psf为输出尺寸
	Size dftSize;

	dftSize.width = getOptimalDFTSize(size.width);
	dftSize.height = getOptimalDFTSize(size.height);

	//移动psf中心到四角
	Mat temp(dftSize, psf.type(), Scalar::all(0));

	Mat roipsf(temp, Rect(0, 0, psf.cols, psf.rows));
	psf.copyTo(roipsf);

	Mat psf2 = temp.clone();

	int cx = psf.cols / 2;
	int cy = psf.rows / 2;

	Mat p0(temp, Rect(0, 0, cx, cy));
	Mat p1(temp, Rect(cx, 0, psf2.cols - cx, cy));
	Mat p2(temp, Rect(0, cy, cx, psf2.rows - cy));
	Mat p3(temp, Rect(cx, cy, psf2.cols - cx, psf2.rows - cy));

	Mat q0(psf2, Rect(0, 0, psf2.cols - cx, psf2.rows - cy));
	Mat q1(psf2, Rect(psf2.cols - cx, 0, cx, psf2.rows - cy));
	Mat q2(psf2, Rect(0, psf2.rows - cy, psf2.cols - cx, cy));
	Mat q3(psf2, Rect(psf2.cols - cx, psf2.rows - cy, cx, cy));

	p0.copyTo(q3);
	p1.copyTo(q2);
	p2.copyTo(q1);
	p3.copyTo(q0);

	//计算otf
	Mat planes[] = { Mat_(psf2),Mat::zeros(psf2.size(),CV_32F) };
	Mat complexI;
	merge(planes, 2, complexI);

	dft(complexI, complexI,DFT_COMPLEX_OUTPUT);
	otf = complexI;
	return otf(Range(0, size.height), Range(0, size.width));
}

Mat L0Smoothing(Mat& t_Im, float t_lambda, float t_kappa)
{
	float lambda = t_lambda;
	float kappa = t_kappa;

	Mat S;
	t_Im.convertTo(S, CV_32FC3);
	
	S = S / 255.0;
	float betamax = 100000;

	float arr_fx[1][2] = { {1,-1} };
	float arr_fy[2][1] = { {1},{-1} };
	Mat fx(Size(2, 1), CV_32F);
	Mat fy(Size(1, 2), CV_32F);
	myInitMatFromArray2d((float *)arr_fx, 1, 2, fx);
	myInitMatFromArray2d((float *)arr_fy, 2, 1, fy);

	int N = t_Im.rows;	//height
	int M = t_Im.cols;	//width
	int D = t_Im.channels();

	Mat otfFx = psf2otf(fx, Size(M, N));
	Mat otfFy = psf2otf(fy, Size(M, N));

	Mat S_split[3];
	split(S, S_split);
	Mat Normin1[3];
	dft(S_split[0], Normin1[0], DFT_COMPLEX_OUTPUT);
	dft(S_split[1], Normin1[1], DFT_COMPLEX_OUTPUT);
	dft(S_split[2], Normin1[2], DFT_COMPLEX_OUTPUT);

	Mat Denormin2_single = myPow(abs_complex(otfFx), 2.0) + myPow(abs_complex(otfFy), 2.0);
	Mat Denormin2_arr[3] = { Denormin2_single,Denormin2_single,Denormin2_single };
	Mat Denormin2;
	merge(Denormin2_arr, 3, Denormin2);

	float beta = 2 * lambda;

	while (beta < betamax)
	{
		Mat Denormin;
		addWeighted(Denormin2, beta, NULL, 0, 1,Denormin);


		Mat h = diff_h_3d(S);
		copyMakeBorder(h, h, 0, 0, 0, 1, BORDER_CONSTANT, Scalar(0));
		h.col(h.cols - 1) = S.col(0) - S.col(S.cols - 1);

		Mat v = diff_v_3d(S);
		copyMakeBorder(v, v, 0, 1, 0, 0, BORDER_CONSTANT, Scalar(0));
		v.row(v.rows - 1) = S.row(0) - S.row(S.rows - 1);


		Mat t;
		{
			Mat tmp = myPow(h, 2.0) + myPow(v, 2.0);
			Mat tmp_compare(tmp.size(), CV_32F, Scalar::all(lambda / beta));
			Mat tmp_split[3];
			split(tmp, tmp_split);
			Mat tmp_sum = tmp_split[0] + tmp_split[1] + tmp_split[2];
			Mat t_single = tmp_sum >= tmp_compare;
			Mat t_arr[3] = { t_single,t_single,t_single };
			merge(t_arr, 3, t);
			t = t / 255;
		}
		t.convertTo(t, CV_32F);

		h = h.mul(t);
		v = v.mul(t);
		
		Mat Normin2[3];
		{
			Mat Normin2_mat;
			Mat Normin2_h = diff_h_3d(h)*(-1.0);
			copyMakeBorder(Normin2_h, Normin2_h, 0, 0, 1, 0, BORDER_CONSTANT, Scalar::all(0));
			Normin2_h.col(0) = h.col(h.cols - 1) - h.col(0);

			Mat Normin2_v = diff_v_3d(v)*(-1.0);
			copyMakeBorder(Normin2_v, Normin2_v, 1, 0, 0, 0, BORDER_CONSTANT, Scalar::all(0));
			Normin2_v.row(0) = v.row(v.rows - 1) - v.row(0);

			Normin2_mat = Normin2_h + Normin2_v;

			Mat Normin2_mat_split[3];
			split(Normin2_mat, Normin2_mat_split);
			dft(Normin2_mat_split[0], Normin2[0], DFT_COMPLEX_OUTPUT);
			dft(Normin2_mat_split[1], Normin2[1], DFT_COMPLEX_OUTPUT);
			dft(Normin2_mat_split[2], Normin2[2], DFT_COMPLEX_OUTPUT);
		}
		
		Mat FS[3];
		Mat Denormin_split[3];
		split(Denormin, Denormin_split);
		for (int i = 0; i < 3; i++)
		{
			addWeighted(Normin1[i], 1.0, Normin2[i], beta, 0, FS[i]);
			Mat Denormin_arr[2] = { Denormin_split[i],Denormin_split[i] };
			Mat tmp;
			merge(Denormin_arr, 2, tmp);
			divide(FS[i], tmp, FS[i]);
		}

		Mat output_rgb[3];
		for (int i = 0; i < 3; i++)
		{
			dft(FS[i], output_rgb[i], DFT_INVERSE + DFT_REAL_OUTPUT + DFT_SCALE);
		}

		
		merge(output_rgb, 3, S);
		beta = beta*kappa;
		cout << ".";
	}

	S = S*255.0;
	S.convertTo(S, CV_8UC3);
	return S;
}

  • 测试结果:

原图像:

L0范数的梯度最小化图像平滑滤波结果:

[C++/OpenCV] 数字图像处理:使用OpenCV实现基于L0范数的梯度最小化图像平滑滤波及完整源代码_第1张图片

你可能感兴趣的:(C/C++,OpenCV,数字图像处理,OpenCV,数字图像处理,C++,平滑滤波,L0范数)