导向滤波(Guided Fliter)显式地利用 guidance image 计算输出图像,其中 guidance image 可以是输入图像本身或者其他图像。导向滤波比起双边滤波来说在边界附近效果较好;另外,它还具有 O(N) 的线性时间的速度优势。细节请查阅论文《Guided Image Filtering》
除了速度优势以外,导向滤波的一个很好的性能就是可以保持梯度,这是bilateral做不到的,因为会有梯度翻转现象。(Preserves edges, but not gradients)。
其中,p为输入图像,I 为导向图,q 为输出图像。在这里我们认为输出图像可以看成导向图I 的一个局部线性变换,其中k是局部化的窗口的中点,因此属于窗口 ωk 的pixel,都可以用导向图对应的pixel通过(ak,bk)的系数进行变换计算出来。同时,我们认为输入图像 p 是由 q 加上我们不希望的噪声或纹理得到的,因此有p = q + n 。
接下来就是解出这样的系数,使得p和q的差别尽量小,而且还可以保持局部线性模型。这里利用了带有正则项的 linear ridge regression(岭回归)
求解以上方程得到a和b在局部的值,对于一个要求的pixel可能含在多个window中,因此平均后得到:
#pragma once
#include
/*
输入:
guidedImg ----引导图像,单通道或者三通道
inputImg ----输入待滤波图像,单通道或者三通道
r ----滤波窗口半径
eps ----截断值eps
输出:
outputImg ----引导图滤波后图像
*/
class GuidedFilter
{
public:
GuidedFilter();
~GuidedFilter();
private:
cv::Mat runGuidedFilter_Gray(cv::Mat I, cv::Mat P, int type, int radius, double eps);
cv::Mat runGuidedFilter_Color(cv::Mat I, cv::Mat P, int type, int radius, double eps);
public:
cv::Mat myGuidedFilter_GrayGuided(cv::Mat guidedImg, cv::Mat inputImg, int radius, double eps);
cv::Mat myGuidedFilter_ColorGuided(cv::Mat guidedImg, cv::Mat inputImg, int radius, double eps);
};
#include "guidedFilter.h"
#include
#include
using namespace std;
using namespace cv;
GuidedFilter::GuidedFilter()
{
}
GuidedFilter::~GuidedFilter()
{
}
//引导图为灰度图像
cv::Mat GuidedFilter::runGuidedFilter_Gray(cv::Mat I, cv::Mat P, int type, int radius, double eps)
{
cv::Size winSize(2 * radius + 1, 2 * radius + 1);
//求I*I, I*P
cv::Mat I2, IP;
multiply(I, I, I2);
multiply(I, P, IP);
//求均值
cv::Mat meanI, meanP, meanI2, meanIP;
cv::boxFilter(I, meanI, type, winSize);
cv::boxFilter(P, meanP, type, winSize);
cv::boxFilter(I2, meanI2, type, winSize);
cv::boxFilter(IP, meanIP, type, winSize);
//求方差/协方差
cv::Mat varI, covIP;
varI = meanI2 - meanI.mul(meanI);
covIP = meanIP - meanI.mul(meanP);
//求系数a, b
cv::Mat a, b;
varI += eps;
cv::divide(covIP, varI, a);
b = meanP - a.mul(meanI);
//a、b窗口内求平均
cv::Mat meanA, meanB;
cv::boxFilter(a, meanA, type, winSize);
cv::boxFilter(b, meanB, type, winSize);
//输出
cv::Mat output;
output = meanA.mul(I) + meanB;
return output;
}
//引导图I为灰度图像,输入图像P为单通道或者三通道图像
cv::Mat GuidedFilter::myGuidedFilter_GrayGuided(cv::Mat guidedImg, cv::Mat inputImg, int radius, double eps)
{
CV_Assert(guidedImg.channels() == 1);
CV_Assert(inputImg.channels() == 1 || inputImg.channels() == 3);
CV_Assert(guidedImg.rows == inputImg.rows && guidedImg.cols == inputImg.cols);
int type = CV_64FC1;
cv::Mat I, P, output;
inputImg.convertTo(P, type);
guidedImg.convertTo(I, type);
//判断输入图像是单通道还是三通道
int channel = inputImg.channels();
switch (channel)
{
case 1:
output = runGuidedFilter_Gray(I, P, type, radius, eps);
break;
case 3:
{
cv::Mat bgr[3], bgrFilter[3];
cv::split(P, bgr);
for (int chan = 0; chan < channel; chan++)
{
bgrFilter[chan] = runGuidedFilter_Gray(I, bgr[chan], type, radius, eps);
}
cv::merge(bgrFilter, channel, output);
break;
}
default:
cout << "err! input image channel should be 1 or 3! " << endl;
break;
}
return output;
}
//引导图I为三通道图像
cv::Mat GuidedFilter::runGuidedFilter_Color(cv::Mat I, cv::Mat P, int type, int radius, double eps)
{
cv::Size winSize(2 * radius + 1, 2 * radius + 1);
int channel = I.channels();
int H = I.rows;
int W = I.cols;
cv::Mat bgr[3], meanI[3];
//引导图各通道的均值
split(I, bgr);
for (int chan = 0; chan < channel; chan++)
{
boxFilter(bgr[chan], meanI[chan], type, winSize);
}
//输入图像均值
cv::Mat meanP;
boxFilter(P, meanP, type, winSize);
//引导图各通道与输入图像相乘并求均值
cv::Mat meanIP[3], IP;
for (int chan = 0; chan < channel; chan++)
{
multiply(bgr[chan], P, IP);
boxFilter(IP, meanIP[chan], type, winSize);
}
//引导图各通道与输入图协方差
cv::Mat covIP[3], meanImulP;
for (int chan = 0; chan < channel; chan++)
{
multiply(meanI[chan], meanP, meanImulP);
covIP[chan] = meanIP[chan] - meanImulP;
}
//求引导图协方差矩阵
cv::Mat varI[9], tmp, mean2Tmp, meanTmp2;
int varIdx = 0;
for (int i = 0; i < channel; i++)
{
for (int j = 0; j < channel; j++)
{
multiply(bgr[i], bgr[j], tmp);
boxFilter(tmp, meanTmp2, type, winSize);//mean(I*I)
multiply(meanI[i], meanI[j], mean2Tmp);//meanI*meanI
varI[varIdx] = meanTmp2 - mean2Tmp;
varIdx++;
}
}
//求a,三通道
cv::Mat a[3];
for (int chan = 0; chan < channel; chan++)
{
a[chan] = cv::Mat::zeros(I.size(), type);
}
cv::Mat epsEye = cv::Mat::eye(3, 3, type);
epsEye *= eps;
//公式(19)
for (int y = 0; y < H; y++)
{
double* vData[9];
for (int v = 0; v < 9; v++)
{
vData[v] = (double*)varI[v].ptr<double>(y);
}
double* cData[3];
for (int c = 0; c < 3; c++)
{
cData[c] = (double *)covIP[c].ptr<double>(y);
}
double* aData[3];
for (int c = 0; c < 3; c++)
{
aData[c] = (double*)a[c].ptr<double>(y);
}
for (int x = 0; x < W; x++)
{
cv::Mat sigma = (cv::Mat_<double>(3, 3) <<
vData[0][x], vData[1][x], vData[2][x],
vData[3][x], vData[4][x], vData[5][x],
vData[6][x], vData[7][x], vData[8][x]
);
sigma += epsEye;
cv::Mat cov_Ip_13 = (cv::Mat_<double>(3, 1) <<
cData[0][x], cData[1][x], cData[2][x]);
cv::Mat tmpA = sigma.inv()*cov_Ip_13;
double* tmpData = tmpA.ptr<double>(0);
for (int c = 0; c < 3; c++)
{
aData[c][x] = tmpData[c];
}
}
}
//求b
cv::Mat b = meanP - a[0].mul(meanI[0]) - a[1].mul(meanI[1]) - a[2].mul(meanI[2]);
//b的均值
cv::Mat meanB;
boxFilter(b, meanB, type, winSize);
//a的均值
cv::Mat meanA[3];
for (int c = 0; c < channel; c++)
{
boxFilter(a[c], meanA[c], type, winSize);
}
cv::Mat output = (meanA[0].mul(bgr[0]) + meanA[1].mul(bgr[1]) + meanA[2].mul(bgr[2])) + meanB;
return output;
}
//引导图I为三通道图像,输入图像P为单通道或者三通道图像
cv::Mat GuidedFilter::myGuidedFilter_ColorGuided(cv::Mat guidedImg, cv::Mat inputImg, int radius, double eps)
{
CV_Assert(guidedImg.channels() == 3);
CV_Assert(inputImg.channels() == 1 || inputImg.channels() == 3);
CV_Assert(guidedImg.cols == inputImg.cols && guidedImg.rows == inputImg.rows);
int type = CV_64F;
int channel = inputImg.channels();
cv::Mat I, P, output;
guidedImg.convertTo(I, type);
inputImg.convertTo(P, type);
//判断输入图像是单通道还是三通道
switch (channel)
{
case 1:
output = runGuidedFilter_Color(I, P, type, radius, eps);
break;
case 3:
{
cv::Mat bgr[3], bgrFilter[3];
cv::split(P, bgr);
for (int chan = 0; chan < channel; chan++)
{
bgrFilter[chan] = runGuidedFilter_Color(I, bgr[chan], type, radius, eps);
}
cv::merge(bgrFilter, channel, output);
break;
}
default:
cout << "err! input image channel should be 1 or 3! " << endl;
break;
}
output.convertTo(output, CV_8U);
return output;
}
#include
#include "guidedFilter.h"
#include
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
//if (argc != 2)
//{
// cout << "Usage: opencv_test " << endl;
// return -1;
//}
//char *imgName = argv[1];
char *imgName = "C:\\Users\\VINNO\\Desktop\\src\\cat.jpg";
Mat inputImg;
inputImg = imread(imgName, 1);
if (!inputImg.data)
{
cout << "No image data" << endl;
return -1;
}
Mat grayImg , guidedImg;
inputImg.copyTo(guidedImg);
GuidedFilter filter;
grayImg = filter.myGuidedFilter_ColorGuided(inputImg, guidedImg, 80, 0.001);
imwrite("./result.jpg", grayImg);
imshow("", grayImg);
waitKey(0);
return 0;
}
[1] K. He, J. Sun, and X. Tang. Guided image filtering. In ECCV, pages 1–14. 2010.
[2] K. He, J. Sun, and X. Tang. Guided image filtering. TPAMI, 35(6):1397–1409, 2013
[3] He K, Sun J. Fast Guided Filter[J]. Computer Science, 2015.