导向图滤波是一种图像滤波技术,通过一张引导图G,对目标图像P(输入图像)进行滤波处理,使得最后的输出图像大体上与目标图像P相似,但是纹理部分与引导图G相似。其典型应用有两个:保边图像平滑,抠图。
导向滤波是由何凯明等人在2010年发表在ECCV的文章“Guided Image Filtering”文中提到的,后来陆续发表了改进算法快速导向滤波的实现。导向滤波不仅能够实现双边滤波的边缘平滑,而且在检测到边缘附近有很好的表现,可以应用在图像增强、HDR压缩、图像抠图以及图像去雾等场景中。
导向滤波的实现原理如下:
对于普通的线性变换滤波器,输入图像是I,输出图像是S,导向函数为T,导向滤波定义在像素点j处的滤波结果可以表示为下式:
其中,i,j是图像像素的下标,滤波器核函数Wij是图像的加权系数,景点的双边滤波器核给定如下:
其中x是像素坐标Ki是一个归一化参数
参数σs和σr代表空间相似度以及颜色范围相似度的灵敏性
假设导向滤波器存在线性模型如下:
其中窗口函数Wk是S以I为中心在像素k位置形成的线性变换,叙述ak和bk满足相同的线性系数,为确定线性系数,定义的输出相应q应满足下式:
其中ni表示噪声,局部线性模型确定输入与输出相应的边缘,就可以最小化q与I之间的差异,同事保持原线性变换。
最小化窗口函数Wk:
其中Ψ是一个正则化参数,根据线性变换得到系数ak和bk:
其中μk与σk是导向图像I的窗口wk的均值和方差,|w|是像素的总个数,
其中μk与σk是导向图像I的窗口wk的均值与方差,|w|为像素的总个数,其中
导向滤波实现步骤如下:
(1.利用boxFilter滤波器完成均值计算,其中均值包括导向均值、原始均值、互相关均值以及自相关均值
(2.根据均值计算相关系数参数,包括自相关与互相关方差。
(3.计算窗口线性变换参数系数a、b。
(4.根据公式计算参数a、b的均值。
(5.利用参数得到导向滤波输出矩阵S。
图像的导向滤波操作相关代码如下:
//图像的导向滤波操作
#include
#include
#include
#include
using namespace cv;
using namespace std;
//导向滤波器
Mat guidedfilter(Mat &srcImage, Mat &srcClone, int r, double eps);
int main()
{
Mat srcImage = imread("2345.jpg");
if (srcImage.empty())
{
cout << "读入图片错误!" << endl;
system("pause");
return -1;
}
//进行通道分离
vectorvSrcImage, vResultImage;
split(srcImage, vSrcImage);
Mat resultMat;
for (int i = 0; i < 3; i++)
{
//分通道转换成浮点型数据
Mat tempImage;
vSrcImage[i].convertTo(tempImage, CV_64FC1, 1.0 / 255.0);
Mat p = tempImage.clone();
//分别进行导向滤波
Mat resultImage = guidedfilter(tempImage, p, 4, 0.01);
vResultImage.push_back(resultImage);
}
//通道结果合并
merge(vResultImage, resultMat);
imshow("原图像", srcImage);
imshow("导向滤波后图像", resultMat);
waitKey(0);
return 0;
}
Mat guidedfilter(Mat &srcImage, Mat &srcClone, int r, double eps)
{
//转换源图像信息
srcImage.convertTo(srcImage, CV_64FC1);
srcClone.convertTo(srcClone, CV_64FC1);
int NumRows = srcImage.rows;
int NumCols = srcImage.cols;
Mat boxResult;
//下面按照步骤进行导向滤波操作
/
//步骤一:计算均值
boxFilter(Mat::ones(NumRows, NumCols, srcImage.type()),
boxResult,CV_64FC1,Size(r,r));
//生成导向均值mean_I
Mat mean_I;
boxFilter(srcImage, mean_I, CV_64FC1, Size(r, r));
//生成原始均值mean_P
Mat mean_P;
boxFilter(srcClone, mean_P, CV_64FC1, Size(r, r));
//生成互相关均值mean_IP
Mat mean_IP;
boxFilter(srcImage.mul(srcClone), mean_IP,
CV_64FC1, Size(r, r));
Mat cov_IP = mean_IP - mean_I.mul(mean_P);
//生成自相关均值mean_II
Mat mean_II;
//应用盒滤波计算相关均值
boxFilter(srcImage.mul(srcImage), mean_II, CV_64FC1, Size(r, r));
//步骤二:计算相关系数
Mat var_I = mean_II - mean_I.mul(mean_I);
Mat var_IP = mean_IP - mean_I.mul(mean_P);
//步骤三:计算参数系数a,b
Mat a = cov_IP / (var_I + eps);
Mat b = mean_P - a.mul(mean_I);
//步骤四:计算系数a,b的均值
Mat mean_a;
boxFilter(a, mean_a, CV_64FC1, Size(r, r));
mean_a = mean_a / boxResult;
Mat mean_b;
boxFilter(b, mean_b, CV_64FC1, Size(r, r));
mean_b = mean_b / boxResult;
//步骤五:生成输出矩阵
Mat resultMat = mean_a.mul(srcImage) + mean_b;
return resultMat;
}
执行程序后,效果如下所示:
下面简单介绍一下在程序中使用到的几个OpenCV中的相关操作:
1.convertTo( )函数
在使用Opencv中,经常会会出现读取一个图片内容后要把图片内容的像素信息转为浮点并把当前的mat作为矩形进行矩阵计算,那么这里就有一个类型转换问题,在OpenCV中有一个函数可以用于类型的相互转换,也就是convertTo函数
函数的声明如下:
void convertTo( OutputArray m, int rtype, double alpha=1, double beta=0 ) const;
参数说明:
m – 目标矩阵。如果m在运算前没有合适的尺寸或类型,将被重新分配。
rtype – 目标矩阵的类型。因为目标矩阵的通道数与源矩阵一样,所以rtype也可以看做是目标矩阵的位深度。如果rtype为负值,目标矩阵和源矩阵将使用同样的类型。
alpha – 尺度变换因子(可选)。
beta – 附加到尺度变换后的值上的偏移量(可选)。
函数原理:
函数将源矩阵中的像素值转换为目标类型。最后会使用溢出保护函数saturate_cast<> ,以避免转换过程中可能出现的溢出。函数执行下面的运算:
2.mul()函数
Opencv中mul()函数会计算两个Mat矩阵对应位的乘积,所以要求参与运算的矩阵A的行列和B的行列数一致。计算结果是跟A或B行列数一致的一个Mat矩阵。
其相关声明如下:
MatExpr mul(InputArray m, double scale=1) const;
以简单的情况为例,对于2*2大小的Mat矩阵A和B:
对A和B执行mul运算:
1. mul操作不对参与运算的两个矩阵A、B有数据类型上的要求,但要求A,B类型一致,不然报错;
2. Mat AB=A.mul(B),若声明AB时没有定义AB的数据类型,则默认AB的数据类型跟A和B保存一致;
3. 若AB精度不够,可能产生溢出,溢出的值被置为当前精度下的最大值;