本文使用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范数的梯度最小化图像平滑滤波结果: