跳到主要内容引导滤波核心原理及 C++/Python 实现代码 | 极客日志编程语言AI算法
引导滤波核心原理及 C++/Python 实现代码
综述由AI生成引导滤波是一种边缘保持平滑滤波方法,由何凯明等人提出。其核心思想是在局部窗口内假设输出图像是引导图像的线性变换,通过最小化重建误差并加入正则化项,在去除噪声的同时保留显著边缘。相比双边滤波,引导滤波无梯度反转问题且计算复杂度为线性 O(N)。详细解析了斜率和截距的计算过程,包括均值、方差及协方差的统计量推导,并通过 3×3 窗口示例演示了滤波逻辑。此外提供了完整的 C++ 和 Python 实现代码,支持灰度及彩色图像的去噪处理,适用于 HDR 压缩、图像增强等场景。
剑仙8.6K 浏览 

一、引导滤波简介
引导滤波(Guided Filter)是由何凯明(Kaiming He)、孙剑(Jian Sun)和唐晓鸥(Xiaoou Tang)于 2010 年提出的一种边缘保持平滑滤波方法,最早发表于 ECCV 2010 论文'Guided Image Filtering',并在 2013 年 IEEE TPAMI 上给出扩展版本。
引导滤波是一种基于局部线性模型的图像滤波技术,其核心思想是:在一个局部窗口内,假设输出图像是引导图像(可以是输入图像本身,也可以是另一幅图像)的线性变换。通过最小化重建误差并加入正则化项,引导滤波能够在有效去除噪声或平滑纹理的同时,严格保留显著边缘结构。与双边滤波相比,引导滤波不存在梯度反转(gradient reversal)问题,且其计算复杂度为线性时间 O(N),非常适合高分辨率图像和实时应用。由于其稳定性、效率和良好的边缘保持特性,引导滤波被广泛应用于图像去噪、HDR 压缩、图像增强、图像融合、语义分割后处理(如 CRF 替代)以及深度学习中的特征引导平滑等场景。
二、引导滤波原理
2.1 线性模型
引导滤波的本质是在每个滤波窗口内,假设输出图 q 与引导图 I 呈线性关系。当用图像自身作为引导图时(自引导滤波),I 和输入图 p 是同一张图,线性模型为:

参数解析:
ω_k 表示以像素 k 为中心的滤波窗口(如 3×3);
a_k:窗口内的斜率(控制边缘保留程度);
b_k:窗口内的截距(控制平滑程度);
目标:找到最优的 a_k、b_k,让输出图 q 尽可能接近输入图 p(误差最小)。
2.2 斜率和截距的计算
斜率 a_k 和截距 b_k 的计算方法,这里用一个 3×3 窗口的具体像素值来演示 (输入图像 p=I,8 位灰度图)。
假设窗口 ω_k 内的像素值如下 (中心像素 k 为 100):

2.2.1 计算窗口内的 3 个核心统计量
计算斜率 a_k 和截距 b_k,首先需要窗口内的均值和方差,这正是 boxFilter 要干的活。
窗口内像素均值 \bar{I}_k:所有像素的平均值:
\bar{I}_k = \frac{60 + 70 + 80 + 80 + 100 + 120 + 100 + 110 + 130}{9} = \frac{850}{9} ≈ 94.44
窗口内像素平方均值 \bar{I}_k^2:所有像素平方的平均值:
\bar{I}_k^2 = \frac{60^2 + 70^2 + 80^2 + 80^2 + 100^2 + 120^2 + 100^2 + 110^2 + 130^2}{9} = \frac{84700}{9} ≈ 9411.11
窗口内像素与均值的协方差 cov(I,p)_k:因为自引导时 I=p,协方差等于方差 var(I)_k:
var(I)_k = \bar{I}_k^2 - (\bar{I}_k)^2 ≈ 9411.11 - 8918.91 = 492.2
2.2.2 代入公式计算斜率和截距
引导滤波通过最小化误差推导得到 a_k 和 b_k 的计算公式:
参数解析:
自引导时 p=I,\bar{p}_k = \bar{I}_k;
ε 正则化参数 (防止分母为 0,比如取 10)。
代入数值计算:
2.2.3 线性系数的物理意义
斜率 a_k ≈ 0.98(接近 1):说明这个窗口内有明显边缘(像素值从 60 到 130 变化大),滤波时要保留边缘 → 输出 q_i ≈ I_i(几乎不滤波)。
2.3 斜率和截距问题
问题:通过前面步骤讲解,掌握了每个小窗口内的斜率和截距计算方法,那么 a_k ≈ 0.98 和 b_k ≈ 1.89 具体是哪个像素的斜率和截距?是中心像素的吗?还是 3×3 小窗口内全局所有像素的斜率和截距都是一致?
答:a_k ≈ 0.98 和 b_k ≈ 1.89 是以中心像素 100 为核心的 3×3 窗口 ω_k 的全局线性系数,而非仅属于中心像素,但最终会通过加权平均让每个像素获得专属的 a、b,完成整幅图的滤波。
2.4 基于窗口系数,初步计算窗口内所有像素的 q_i
2.4.1 引导滤波的完整逻辑
每个像素会被多个相邻窗口覆盖(比如中心像素会是自身窗口的中心,也是周围 8 个窗口的成员),因此每个像素的最终 a、b 是所有包含该像素的窗口的 a_k 和 b_k 的均值,最终输出是:
2.4.2 完整引导滤波实现过程(基于 3×3 窗口 + 具体像素值)
用以下 3×3 窗口 (记为窗口 W_5,中心像素为第 5 个像素,坐标 (1,1)),如下:
2.4.2.1 计算窗口 W_5 的 a_k 和 b_k
前面已经讲清楚各参数计算的来源,这里直接获取值,如下:
2.4.2.2 基于窗口系数、初步计算窗口内所有像素的 q_i
逐个计算 9 个像素的初步值,各像素的滤波输出(基于像素 5 的窗口系数):
- 像素 1(60):q_1^(5) = 0.98 × 60 + 1.89 = 60.69
- 像素 2(70):q_2^(5) = 0.98 × 70 + 1.89 = 70.49
- 像素 3(80):q_3^(5) = 0.98 × 80 + 1.89 = 80.29
- 像素 4(80):q_4^(5) = 0.98 × 80 + 1.89 = 80.29
- 像素 5(100):q_5^(5) = 0.98 × 100 + 1.89 = 99.89
- 像素 6(120):q_6^(5) = 0.98 × 120 + 1.89 = 119.49
- 像素 7(100):q_7^(5) = 0.98 × 100 + 1.89 = 99.89
- 像素 8(110):q_8^(5) = 0.98 × 110 + 1.89 = 109.69
- 像素 9(130):q_9^(5) = 0.98 × 130 + 1.89 = 129.29
2.4.2.3 考虑多窗口覆盖 (每个像素被多个窗口包含)
引导滤波中,每个像素会被多个相邻窗口覆盖,因此需要对所有包含该像素的窗口的 a_k、b_k 取均值,得到该像素的最终 \bar{a}_i,\bar{b}_i。
以中心像素 5(100)为例:像素 5 是窗口 W5 的中心,同时也是窗口 W_2(以像素 2 为中心)、W_4(以像素 4 为中心)、W_6(以像素 6 为中心)、W_8(以像素 8 为中心)的成员。
假设我们计算出这 4 个相邻窗口的系数,各窗口的系数计算:
- W_2(中心 70):a_2 ≈ 0.95, b_2 ≈ 3.5
- W_4(中心 80):a_4 ≈ 0.96, b_4 ≈ 3.2
- W_6(中心 120):a_6 ≈ 0.98, b_6 ≈ 2.4
- W_8(中心 110):a_8 ≈ 0.97, b_8 ≈ 2.9
像素 5 的最终系数:
\bar{a}_5 = \frac{a_5 + a_2 + a_4 + a_6 + a_8}{5} = \frac{0.98 + 0.95 + 0.96 + 0.98 + 0.97}{5} = 0.968
\bar{b}_5 = \frac{b_5 + b_2 + b_4 + b_6 + b_8}{5} = \frac{1.89 + 3.5 + 3.2 + 2.4 + 2.9}{5} = 2.778
像素 5 最终的滤波输出为:
q_5 = \bar{a}_5 · I_5 + \bar{b}_5 = 0.968 × 100 + 2.778 = 99.578
三、实战代码
3.1 参数设置
3.2 C++ 代码
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
Mat guidedFilterForDenoising(const Mat& I, const Mat& p, int r, float eps) {
CV_Assert(!I.empty() && !p.empty());
CV_Assert(I.size() == p.size());
CV_Assert(r >= 1);
CV_Assert(eps > 0);
int inputType = p.type();
Mat I32f, p32f;
if (I.depth() == CV_8U) {
I.convertTo(I32f, CV_32F, 1.0 / 255.0);
} else {
I32f = I.clone();
}
if (p.depth() == CV_8U) {
p.convertTo(p32f, CV_32F, 1.0 / 255.0);
} else {
p32f = p.clone();
}
Size winSize(2 * r + 1, 2 * r + 1);
Mat mean_I, mean_p;
boxFilter(I32f, mean_I, CV_32F, winSize, Point(-1, -1), true, BORDER_REPLICATE);
boxFilter(p32f, mean_p, CV_32F, winSize, Point(-1, -1), true, BORDER_REPLICATE);
Mat mean_Ip, mean_II;
boxFilter(I32f.mul(p32f), mean_Ip, CV_32F, winSize, Point(-1, -1), true, BORDER_REPLICATE);
boxFilter(I32f.mul(I32f), mean_II, CV_32F, winSize, Point(-1, -1), true, BORDER_REPLICATE);
Mat cov_Ip = mean_Ip - mean_I.mul(mean_p);
Mat var_I = mean_II - mean_I.mul(mean_I);
Mat a = cov_Ip / (var_I + eps);
Mat b = mean_p - a.mul(mean_I);
Mat mean_a, mean_b;
boxFilter(a, mean_a, CV_32F, winSize, Point(-1, -1), true, BORDER_REPLICATE);
boxFilter(b, mean_b, CV_32F, winSize, Point(-1, -1), true, BORDER_REPLICATE);
Mat q32f = mean_a.mul(I32f) + mean_b;
Mat result;
if (inputType == CV_8UC1) {
q32f.convertTo(result, CV_8UC1, 255.0, 0.0);
} else if (inputType == CV_8UC3) {
q32f.convertTo(result, CV_8UC3, 255.0, 0.0);
} else if (inputType == CV_32FC1) {
result = q32f.clone();
} else if (inputType == CV_32FC3) {
result = q32f.clone();
} else {
cerr << "不支持的图像类型!" << endl;
return Mat();
}
return result;
}
Mat guidedFilterRGBForDenoising(const Mat& src, int r, float eps) {
CV_Assert(!src.empty());
CV_Assert(src.channels() == 3);
vector<Mat> channels;
split(src, channels);
for (int c = 0; c < 3; ++c) {
channels[c] = guidedFilterForDenoising(channels[c],
channels[c],
r, eps);
}
Mat dst;
merge(channels, dst);
return dst;
}
int main() {
Mat src = imread("testImages/monarch.png");
if (src.empty()) {
cerr << "图像读取失败,请检查路径!" << endl;
return -1;
}
int r = 3;
float eps = 0.001f;
Mat denoised = guidedFilterRGBForDenoising(src, r, eps);
imshow("原始含噪图像", src);
imshow("引导滤波降噪图像", denoised);
cout << "按任意键退出..." << endl;
waitKey(0);
return 0;
}
3.3 Python 代码
import cv2
import numpy as np
import sys
def guidedFilterForDenoising(I, p, r, eps):
"""
通用引导滤波(保边降噪专用,无耗时优化,精度优先)
Parameters:
I: 引导图(numpy 数组),可以是原图自身(自引导)或其他特征图(异引导)
p: 待滤波的输入图(numpy 数组),需要降噪的图像
r: 滤波窗口半径(降噪核心参数:r 越大,降噪越强,建议 3~15)
eps: 正则化参数(保边核心参数:eps 越小,保边越强;eps 越大,平滑越强,建议 0.001~10)
Returns:
result: 滤波后的降噪图像(numpy 数组),与输入图尺寸、类型一致
Notes:
1. 支持单通道灰度图(uint8/float32)、3 通道彩色图(uint8/float32)
2. 降噪场景建议使用自引导(I=p),既保留边缘又滤除噪声
3. 无任何耗时优化,全程高精度浮点计算,专注降噪效果
"""
if I is None or p is None:
raise ValueError("引导图或输入图不能为空!")
if I.shape != p.shape:
raise ValueError("引导图与输入图尺寸必须一致!")
if r < 1:
raise ValueError("滤波窗口半径 r 必须大于等于 1!")
if eps <= 0:
raise ValueError("正则化参数 eps 必须大于 0!")
input_dtype = p.dtype
h, w = p.shape[:2]
is_color = len(p.shape) == 3 and p.shape[2] == 3
if I.dtype == np.uint8:
I32f = I.astype(np.float32) / 255.0
else:
I32f = I.copy().astype(np.float32)
if p.dtype == np.uint8:
p32f = p.astype(np.float32) / 255.0
else:
p32f = p.copy().astype(np.float32)
win_size = (2 * r + 1, 2 * r + 1)
mean_I = cv2.boxFilter(I32f, cv2.CV_32F, win_size, anchor=(-1, -1), normalize=True, borderType=cv2.BORDER_REPLICATE)
mean_p = cv2.boxFilter(p32f, cv2.CV_32F, win_size, anchor=(-1, -1), normalize=True, borderType=cv2.BORDER_REPLICATE)
I_mul_p = I32f * p32f
I_mul_I = I32f * I32f
mean_Ip = cv2.boxFilter(I_mul_p, cv2.CV_32F, win_size, anchor=(-1, -1), normalize=True, borderType=cv2.BORDER_REPLICATE)
mean_II = cv2.boxFilter(I_mul_I, cv2.CV_32F, win_size, anchor=(-1, -1), normalize=True, borderType=cv2.BORDER_REPLICATE)
cov_Ip = mean_Ip - mean_I * mean_p
var_I = mean_II - mean_I * mean_I
a = cov_Ip / (var_I + eps)
b = mean_p - a * mean_I
mean_a = cv2.boxFilter(a, cv2.CV_32F, win_size, anchor=(-1, -1), normalize=True, borderType=cv2.BORDER_REPLICATE)
mean_b = cv2.boxFilter(b, cv2.CV_32F, win_size, anchor=(-1, -1), normalize=True, borderType=cv2.BORDER_REPLICATE)
q32f = mean_a * I32f + mean_b
if input_dtype == np.uint8:
q32f = np.clip(q32f * 255.0, 0, 255)
result = q32f.astype(np.uint8)
else:
result = q32f.astype(input_dtype)
return result
def guidedFilterRGBForDenoising(src, r, eps):
"""
RGB 彩色图像引导滤波去噪(逐通道自引导)
Parameters:
src: 输入彩色图像(H×W×3,uint8 或 float32,OpenCV 默认 BGR)
r: 引导滤波窗口半径
eps: 正则化参数
Returns:
dst: 去噪后的彩色图像
"""
if src is None:
raise ValueError("输入图像不能为空!")
if src.ndim != 3 or src.shape[2] != 3:
raise ValueError("输入必须是 3 通道彩色图像!")
channels = cv2.split(src)
denoised_channels = []
for c in channels:
denoised_c = guidedFilterForDenoising(c, c, r, eps)
denoised_channels.append(denoised_c)
dst = cv2.merge(denoised_channels)
return dst
def main():
"""
引导滤波降噪测试函数(灰度图示例)
彩色图只需将 cv2.IMREAD_GRAYSCALE 改为 cv2.IMREAD_COLOR 即可
"""
img_path = "Images/removeStrips/lenna.png"
src = cv2.imread(img_path)
if src is None:
print("图像读取失败,请检查路径!", file=sys.stderr)
return -1
r = 3
eps = 0.005
denoised = guidedFilterRGBForDenoising(src, r, eps)
cv2.imshow("原始含噪图像", src)
cv2.imshow("引导滤波降噪图像", denoised)
cv2.imwrite("denoised_image.png", denoised)
print("按任意键退出...")
cv2.waitKey(0)
cv2.destroyAllWindows()
return 0
if __name__ == "__main__":
main()
四、去噪实例效果
使用本博文教程中代码去噪结果如下,左图为原图,右图为引导滤波去噪后结果:
五、总结
本文详细介绍了引导滤波(Guided Filter)的原理与实现方法。文章通过具体示例(3×3 窗口)逐步解析了斜率和截距的计算过程,包括均值、方差及协方差的计算,并演示了如何通过线性模型得到滤波结果。此外,还探讨了窗口系数的物理意义及像素级滤波输出的计算方法,展示了引导滤波在边缘保留和平滑效果上的优势。引导滤波具有线性计算复杂度(O(N)),适用于图像去噪、HDR 压缩、语义分割后处理等场景,是计算机视觉领域的重要滤波技术。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- 随机西班牙地址生成器
随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online