导向滤波

基本原理

导向滤波(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中,因此平均后得到:
在这里插入图片描述

最后得到的算法为:
在这里插入图片描述

示例演示

头文件GuidedFilter.h

#pragma once

#include <opencv2\opencv.hpp>


/*
输入:
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);


};

源文件GuidedFilter.cpp

#include "guidedFilter.h"
#include <iostream>
#include <string>

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 <opencv2\opencv.hpp>
#include "guidedFilter.h"
#include <iostream>

using namespace std;
using namespace cv;

int main(int argc, char** argv)
{
    //if (argc != 2)
    //{
    //    cout << "Usage: opencv_test <image path>" << 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.

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值