ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

OpenCV频率域滤波实战:低通、高通与陷波去网纹

OpenCV频率域滤波实战:低通、高通与陷波去网纹

简介:面向图像处理学习者与OpenCV开发者的一份频率域低通滤波实践代码包,聚焦高斯、理想与巴特沃斯三类滤波器的实现与效果对照,适合已掌握C++基础、希望理解频域滤波流程与参数影响的中级读者。压缩包共8个文件、约86KB,以C++源码和VC工程配置为主,并附带bmp测试图像,便于直接编译运行、观察不同截止频率下的平滑效果与振铃差异。已有2155人学习,说明其在频域滤波入门与课程实验场景中具有一定参考价值。读者可借助源码梳理DFT、频谱修改、IDFT及归一化显示的完整链路,对比理想滤波器的陡峭截断、高斯滤波器的平滑衰减和巴特沃斯滤波器的平坦过渡带,进而掌握滤波器构造、频谱中心化与结果可视化等关键细节,也可将其作为图像去噪、平滑处理实验的对照模板。

1. 一张带周期网纹的扫描件:opencv 频率域滤波先解决什么问题

扫描件表面那层等间距的网纹,用均值滤波和中值滤波试过几轮:核小了纹路还在,核大了字口糊成一团。这是空域滤波的局限——它按邻域位置做平滑,分不清“纹理”和“笔画”,只要落在同一个窗口里就一视同仁。opencv 频率域滤波换了个视角:周期性网纹在频谱上是一对对称的亮点,笔画和背景能量集中在低频,用一个只压掉那两个亮点的掩膜,纹路消失,字口基本不动。日常能碰到的同类问题还有工业相机拍金属件的摩尔纹、遥感图的条带噪声、显微图的背景不均。这篇按“选型—代码—进阶—排查”走一遍,把高斯、理想、巴特沃斯三种低通/高通在 OpenCV 里真正跑起来。

2. 滤波器形状、截止频率与填充尺寸:频域滤波的三个选型问题

2.1 为什么卷积能变成乘法:OpenCV 的 dft 链路上发生了什么

空域里做的是卷积,频域里做的是逐点相乘,这条等价关系来自卷积定理。把图像 $f(x,y)$ 和滤波器冲激响应 $h(x,y)$ 都做离散傅里叶变换,得到 $F(u,v)$ 和 $H(u,v)$,那么 $f * h$ 的频谱就是 $F \cdot H$。工程上的意义很直接:空域里要设计一个 51×51 的核在整幅图上滑动,频域里只需要生成一张和图像同尺寸的掩膜,做一次逐元素乘法。滤波器越复杂、核越大,频域这条路的优势越明显。

但 OpenCV 的 dft 有几件事和 numpy 的习惯不一样,第一次上手容易翻车。第一,cv2.dft的输入必须是CV_32F或CV_64F,读进来的uint8要显式转换;第二,默认输出是双通道的复数排列,实部在[:,:,0],虚部在[:,:,1],需要加flags=cv2.DFT_COMPLEX_OUTPUT才拿得到;第三,正变换不缩放,反变换默认也不缩放,这意味着idft(dft(x))得到的是原图的 $M \times N$ 倍,必须靠cv2.DFT_SCALE或手动除回来。

还有一个容易忽略的概念点:DFT 隐含的是循环卷积,不是线性卷积。图像右下角和左上角在频域里是“接在一起”的,如果直接对原图做变换,边界内容会绕回来污染对侧。缓解办法是先 padding,再在反变换后裁掉。padding 的尺寸不是随便补的,OpenCV 提供了getOptimalDFTSize来返回不小于输入的最小快速尺寸,这个尺寸的质因子只包含 2、3、5,DFT 跑得最快。

频谱中心化是另一个必须说清楚的动作。未中心化的频谱,低频在四个角上;中心化之后低频搬到画面正中,圆形和环形滤波器才能用“到中心的距离”来定义。中心化只影响掩膜的设计和频谱的显示,不影响变换本身。np.fft.fftshift默认会对数组的所有轴做移位,遇到双通道的 dft 结果会把通道维也翻过去,必须写成axes=[0,1],反变换时用ifftshift配对。

2.2 理想、高斯、巴特沃斯:三条频响曲线各自的代价

三种低通滤波器的差别,全在“从通带到阻带的过渡方式”上。

理想低通的传递函数是一个硬切的圆:$H(u,v)=1$ 当 $D(u,v) \le D_0$,否则为 0。$D(u,v)$ 是频率点到中心的距离。它在频域是一个矩形窗,对应到空域就是二维 sinc 函数,sinc 有正负旁瓣,和图像卷积之后,强边缘两侧会出现过冲和下冲,也就是振铃。振铃不是参数没调好,是理想低通的数学必然,所以它更适合做频谱分析和教学演示,成片处理里很少单独用。

高斯低通是 $H = \exp(-D^2/(2D_0^2))$。它的傅里叶变换还是高斯,空域没有旁瓣,所以不产生振铃。代价是过渡带最宽,同样的截止频率下边缘最软,去噪的同时细节也一起被抹掉。它适合放在预处理链的前段,或者你只关心低频结构、不关心锐度的场景。

巴特沃斯低通是 $H = 1/(1+(D/D_0)^{2n})$。$n=1$ 时它退化成一种很缓的曲线,$n$ 越大越逼近理想低通的硬切。$n=2$ 到 $4$ 是工程上常用的区间:过渡带比高斯窄,振铃比理想弱。$D_0$ 在这里就是半功率点,$H(D_0)=0.5$,这一点比高斯方便,高斯的 $D_0$ 是标准差,半功率点在 $1.177D_0$ 附近。

滤波器过渡带空域旁瓣振铃典型用途
理想低通无,硬切sinc,旁瓣大明显频谱分析、掩膜演示
高斯低通最宽无无去噪、预处理、背景平滑
巴特沃斯 n=2中等有,衰减快轻微通用低通、去周期性纹理
巴特沃斯 n=5窄接近 sinc明显需要陡峭截止且能接受振铃

选型的判断顺序我一般是这样:先看目标是不是“压掉某段频率、保留边缘”,是的话优先高斯;如果高斯把需要的细节也糊掉了,换巴特沃斯 n=2 并适当抬高 $D_0$;只有在做频谱分析、需要看清楚某个频段被切掉多少时,才用理想低通。把理想低通直接上生产图,振铃会被误认为“图像质量变差了”,然后回头反复调 $D_0$,这是很常见的弯路。

2.3 截止频率 D0 不能写死成像素值

$D_0$ 的单位是频率域的采样点距离,它和图像尺寸强耦合。同一张内容、分别缩放到 256×256 和 2048×2048,$D_0=30$ 在两张图里代表的物理截止频率差了八倍。所以代码里不要写d0 = 30,要写归一化比例。

常用的归一化方式有两种。一种是按短边比例:$D_0 = \text{ratio} \times \min(\text{rows}, \text{cols})$,ratio 取 0.05 到 0.2,具体看你要压掉多粗的纹理。另一种是按图像对角线,适合长宽比差异大的图。我一般用短边比例,因为它对长条图更稳。

参数含义取值建议调大后的影响
ratio截止半径占短边比例0.05~0.2保留更多高频,去噪变弱
D0截止半径(像素)ratio × min(H,W)同上
n巴特沃斯阶数2~4过渡带变窄,振铃增强
sigma高斯标准差与 D0 同量级平滑范围变大
borderpadding 模式REFLECT边界接缝减少

还有一点:$D_0$ 太小时,低频被削得太多,图像整体亮度会往下掉,因为直流分量虽然还在,但低频结构被压掉了。判断方法是看输出均值,和原图均值差超过 5 个灰度级就说明 $D_0$ 偏小了。

3. 用 OpenCV 跑通三种低通:从补齐尺寸到反变换的完整脚本

3.1 getOptimalDFTSize 和 copyMakeBorder:为什么不能直接 dft

直接把原图丢给cv2.dft能跑,但尺寸不是最优时速度会慢一截,而且边界环绕更明显。标准做法是先算出最优尺寸,再补边。补边的方向是“右下补”,左上保持不动,这样反变换后直接取[:rows, :cols]就是原图位置,不用做额外的平移。

import cv2 import numpy as np def pad_to_optimal(img): """把单通道图补到 DFT 最优尺寸,返回补边后的图和原始尺寸""" rows, cols = img.shape[:2] m = cv2.getOptimalDFTSize(rows) n = cv2.getOptimalDFTSize(cols) if m != rows or n != cols: # 参数顺序:top, bottom, left, right,只往右下补 img = cv2.copyMakeBorder( img, 0, m - rows, 0, n - cols, cv2.BORDER_REFLECT ) return img, rows, cols

getOptimalDFTSize返回的是不小于输入的最小快速尺寸,比如 500 会返回 500(因为 500=2²×5³),501 会返回 512。补边模式选BORDER_REFLECT而不是默认的BORDER_CONSTANT,原因是常量补零会在边界制造一个从图像灰度到 0 的硬跳变,这个跳变在频域里是宽带高频,反变换后表现为边界一圈亮线或暗线。反射补边让边界两侧的灰度连续,接缝会轻很多。如果图像本身边界 content 差异很大,BORDER_REPLICATE也可以作为备选。

3.2 三种低通掩膜的生成函数

掩膜生成的共同前提是先有一张“到中心距离”的网格。因为已经做了 fftshift,低频在(rows//2, cols//2),所以网格的坐标原点是中心而不是左上角。

def radius_grid(shape): """生成以频谱中心为原点的距离网格""" rows, cols = shape u = np.arange(rows) - rows // 2 v = np.arange(cols) - cols // 2 return np.sqrt(u[:, None] ** 2 + v[None, :] ** 2).astype(np.float32) def ideal_lowpass(shape, d0): d = radius_grid(shape) return (d <= d0).astype(np.float32) def gaussian_lowpass(shape, d0): d = radius_grid(shape) return np.exp(-(d ** 2) / (2.0 * d0 ** 2)).astype(np.float32) def butterworth_lowpass(shape, d0, order=2): d = radius_grid(shape) return (1.0 / (1.0 + (d / d0) ** (2 * order))).astype(np.float32)

三个函数的返回值都是(rows, cols)的float32,值域在 0 到 1 之间。d0传进来之前要先按短边比例算好。order只对巴特沃斯有效,取 2 到 4;写成 5 以上时振铃会明显增强,这一点在 2.2 的对比表里已经说过。

注意radius_grid里用的是rows // 2而不是(rows-1)/2。当尺寸是偶数时两者一致,是奇数时差半个像素,对圆形滤波器的影响可以忽略。但如果你的图补边后是奇数尺寸,而你又严格要求中心对齐,可以改成浮点中心,代价是网格计算稍慢。

3.3 正变换、中心化、滤波、反变换与裁剪回原尺寸

把前面的零件串起来,完整链路是六步:转浮点、补边、dft、fftshift、乘掩膜、ifftshift + idft + magnitude + 裁剪。

def freq_lowpass(gray, kind='gaussian', ratio=0.1, order=2): """gray: uint8 单通道;kind: ideal/gaussian/butterworth""" img = gray.astype(np.float32) padded, rows, cols = pad_to_optimal(img) m, n = padded.shape # 正变换:输出双通道复数 dft = cv2.dft(padded, flags=cv2.DFT_COMPLEX_OUTPUT) # 中心化:只移前两个轴,通道轴不能动 dft_shift = np.fft.fftshift(dft, axes=[0, 1]) d0 = ratio * min(rows, cols) if kind == 'ideal': H = ideal_lowpass((m, n), d0) elif kind == 'gaussian': H = gaussian_lowpass((m, n), d0) else: H = butterworth_lowpass((m, n), d0, order) # 逐点相乘:H 是二维,dft_shift 是三维,扩一维对齐 filtered = dft_shift * H[:, :, None] # 反变换:先逆中心化,再 idft,DFT_SCALE 负责 1/(m*n) f_ishift = np.fft.ifftshift(filtered, axes=[0, 1]) back = cv2.idft(f_ishift, flags=cv2.DFT_SCALE | cv2.DFT_COMPLEX_OUTPUT) out = cv2.magnitude(back[:, :, 0], back[:, :, 1]) # 裁回原尺寸并截断到 uint8 范围 return np.clip(out[:rows, :cols], 0, 255).astype(np.uint8)

几个参数和写法值得单独说明。cv2.dft的DFT_COMPLEX_OUTPUT必须加,否则拿到的是打包格式,取实部虚部会错位。np.fft.fftshift的axes=[0,1]是硬性要求,不写的话通道维也会被移位,后续乘掩膜时实部和虚部会被交换,输出颜色和结构全乱。H[:, :, None]是把二维掩膜扩成(m,n,1),靠 numpy 广播和双通道相乘,等价于实部虚部分别乘同一个系数。

cv2.idft的DFT_SCALE做的是除以元素总数 $m \times n$,正好抵消正变换的放大。如果忘了这个 flag,输出值会是原图的几万倍,astype(np.uint8)之后几乎全白。最后np.clip是保险,浮点误差可能让个别像素落到 255 以上或 0 以下。

3.4 用合成条纹图做一次自检

拿真实图调参之前,先用一张合成的、你知道 ground truth 的图跑一遍,能快速确认链路没写错。

# 造一张带竖条纹的测试图 canvas = np.full((512, 512), 200, np.uint8) cv2.rectangle(canvas, (120, 180), (400, 330), 40, -1) for x in range(0, 512, 8): cv2.line(canvas, (x, 0), (x, 511), 120, 1) for kind in ['ideal', 'gaussian', 'butterworth']: out = freq_lowpass(canvas, kind=kind, ratio=0.08) print(kind, 'mean=', out.mean(), 'std=', out.std())

三个输出放在一起看:条纹应该都被压掉,矩形的边缘保留程度不同。理想低通的矩形边缘两侧会出现明暗相间的细纹,这就是振铃;高斯边缘最软但没有纹;巴特沃斯介于两者之间。如果理想低通的输出没有振铃、或者高斯的输出出现了振铃,说明掩膜生成或中心化有问题,先回去查radius_grid的中心坐标和fftshift的 axes。均值这一列也有用:三种滤波器的输出均值应该接近 200 左右,如果某个结果均值掉到 150 以下,说明 $D_0$ 太小,低频被过度压制。

4. 高通、陷波与彩色图:频域滤波的三种进阶用法

4.1 高通不是简单 1 减低通:直流分量要单独处理

高通掩膜最直接的写法是 $H_{hp} = 1 - H_{lp}$。数学上没错,但直接拿去做会得到一个均值接近 0 的输出:直流分量对应的 $H_{lp}(0,0)=1$,减完之后变成 0,图像整体亮度被抽走了。astype(np.uint8)会把负值截成 0、正值截成 255,看起来就是一张黑白噪点图。

处理办法有三种。第一种是滤波后归一化:cv2.normalize(out, out, 0, 255, cv2.NORM_MINMAX),适合只关心边缘结构、不关心绝对灰度的场景,比如纹理分析。第二种是加偏移:out = out + 128,保留直流附近的相对关系,适合做锐化预览对比。第三种是保留直流:只在 $D > D_0$ 的区域做提升,中心低频区不置零。

def highpass_from_lowpass(H, keep_dc=True, dc_radius=3): hp = 1.0 - H if keep_dc: d = radius_grid(H.shape) hp[d <= dc_radius] = 1.0 return hp.astype(np.float32)

dc_radius给 2 到 4 个像素,把中心极小的一块低频保下来,输出均值就不会塌。这个参数不需要精确,它只影响整体亮度,不影响高频增强的效果。

高通还有一个边界问题比低通更明显。低通把高频压掉,边界跳变本身也被压掉了,所以接缝不明显;高通反而把 padding 边界处的反射跳变放大了,输出图的四条边会出现一圈亮线。解决办法是在 3.1 的补边之后、dft 之前,先把边界几个像素用cv2.copyMakeBorder的内侧内容做羽化,或者干脆在最终结果上裁掉 5 个像素的边。我一般选后者,简单直接。

4.2 周期性条纹的去除:从频谱亮点到陷波器

低通能去掉细密条纹,但条纹周期较大时,它的能量落在中频,低通要压掉它就得以牺牲大量细节为代价。这时候用陷波:只在频谱上那几个亮点位置做局部衰减,其他频率原样保留。

找亮点可以手工看,也可以半自动。把中心化后的幅度谱做 log 变换,屏蔽掉中心低频区,剩下的极大值就是周期性噪声的谱峰。

def find_peaks(dft_shift, exclude_ratio=0.05, topk=6): mag = np.log1p(cv2.magnitude(dft_shift[:, :, 0], dft_shift[:, :, 1])) m, n = mag.shape mask = np.ones_like(mag, np.float32) cv2.circle(mask, (n // 2, m // 2), int(exclude_ratio * min(m, n)), 0, -1) score = mag * mask idx = np.argsort(score.ravel())[-topk:] return [(i // n, i % n) for i in idx]

exclude_ratio屏蔽中心低频,避免把直流和主要结构误判成噪声峰;topk取 4 到 8,因为一对对称亮点会产生两个峰,取太多会把真实纹理也陷掉。拿到坐标后构造陷波掩膜:

def notch_mask(shape, centers, r=4.0): H = np.ones(shape, np.float32) rows, cols = shape for cy, cx in centers: u = np.arange(rows)[:, None] - cy v = np.arange(cols)[None, :] - cx H *= 1.0 - np.exp(-(u ** 2 + v ** 2) / (2.0 * r ** 2)) return H

r是陷波半径,取 3 到 6。太小压不干净,太大会在空域产生新的振铃。陷波器本质上是多个高斯凹陷的乘积,所以它比理想陷波温和,不会引入明显的环形伪影。如果条纹方向单一,只需要陷掉垂直于条纹方向的那一对峰,对称位置会自动处理,因为实图像的频谱是共轭对称的。

4.3 彩色图转 YCrCb 只处理亮度通道

三通道 BGR 直接cv2.dft会报错,因为 dft 只接受单通道或双通道。拆成三个通道分别滤波再合并,能跑,但每个通道的低频结构略有差异,分别压制之后通道间的比例关系被破坏,结果常常发灰或者偏色。

更稳的做法是转 YCrCb,只对 Y 通道做频域滤波,Cr 和 Cb 原样保留。

def color_freq_lowpass(bgr, ratio=0.1, kind='gaussian'): ycrcb = cv2.cvtColor(bgr, cv2.COLOR_BGR2YCrCb) y, cr, cb = cv2.split(ycrcb) y2 = freq_lowpass(y, kind=kind, ratio=ratio) merged = cv2.merge([y2, cr, cb]) return cv2.cvtColor(merged, cv2.COLOR_YCrCb2BGR)

Y 是亮度,人眼对它的高频变化最敏感,纹理和噪声也主要集中在这里;Cr、Cb 是色度,分辨率要求低,保持原样不会带来视觉上的损失。如果处理的是色彩噪声,可以再对 Cr、Cb 做一次小半径的低通,但半径要比 Y 通道小一半以上,否则会出现色彩渗出。

同态滤波也是在这个结构上做的:对 log 之后的亮度通道做频域高通,再 exp 回来,用来压掉光照不均。它的掩膜不是 4.1 里的简单高通,而是 $H = (\gamma_H - \gamma_L)(1 - \exp(-c D^2/D_0^2)) + \gamma_L$,$\gamma_H$ 取 1.5 到 2.0,$\gamma_L$ 取 0.3 到 0.5。这个公式在 OpenCV 里没有现成函数,需要自己写,但骨架和 4.1 完全一样。

5. 频域滤波的五个高频翻车点:排查顺序与常见问题

5.1 输出全黑或全白

现象:滤波后的图几乎全是 0 或者全是 255,看不到任何结构。

原因有两类。一类是反变换没缩放,cv2.idft没加DFT_SCALE,输出值域是原图的 $m \times n$ 倍,astype(np.uint8)之后全部饱和。另一类是取了实部却忘了取模,复数结果的实部有正有负,直接截断会丢掉一半信息。

解决:确认cv2.idft的 flags 里带了cv2.DFT_SCALE,或者手动out / (m * n);确认最后一步用的是cv2.magnitude(back[:,:,0], back[:,:,1])而不是back[:,:,0]。排查时先打印out.min()和out.max(),正常值域应该在 0 到 255 附近,超出这个范围就是缩放问题。

5.2 图像中心出现十字亮线或环形伪影

现象:输出图中心有一条横竖交叉的亮线,或者出现同心圆环。

原因:np.fft.fftshift没有指定axes,默认对最后一维也做了移位,双通道的实部虚部被交换;或者滤波器掩膜的中心坐标和频谱中心不匹配,低频被切在了错误的位置。

解决:正变换和反变换都用axes=[0,1],且fftshift和ifftshift配对出现。掩膜生成时打印一下H[rows//2, cols//2],低频处应该是最大值(低通)或 0(高通),如果不是,说明radius_grid的中心算错了。环形伪影则是理想低通振铃的典型表现,换高斯或降巴特沃斯阶数。

5.3 图像边缘一圈接缝

现象:输出图的四条边有明显亮线或暗线,越靠边越明显。

原因:padding 用了BORDER_CONSTANT补零,边界处灰度从图像值硬跳到 0,这个跳变在频域是宽带高频,反变换后回到边界位置。高通滤波会把这个跳变进一步放大。

解决:补边改用BORDER_REFLECT或BORDER_REPLICATE,做完反变换后严格按[:rows, :cols]裁回原尺寸。如果接缝还在,检查是不是裁剪时多裁或少裁了几个像素——补边是往右下补的,左上不动,裁剪也应该是从左上开始。

5.4 彩色图分通道处理之后颜色发灰

现象:三个通道分别滤波再合并,结果饱和度明显下降,或者整体偏青、偏红。

原因:BGR 三个通道的低频分布不完全一致,分别压制后通道间的比例被改变。人眼对色度变化的容忍度低,比例一偏就能看出来。

解决:转 YCrCb 或 LAB,只处理亮度通道,色度通道原样保留。如果确实需要处理色度噪声,Cr、Cb 的滤波半径取 Y 通道的三分之一到二分之一,并且优先用高斯而不是理想低通,避免色度通道的振铃在视觉上被放大。

5.5 振铃被当成噪声,反复调 D0 无效

现象:强边缘两侧出现明暗相间的细纹,调大 $D_0$ 纹路变细但不会消失,调小 $D_0$ 纹路变宽更明显。

原因:这是理想低通或高阶巴特沃斯的吉布斯现象,根源是频域的硬切,不是参数问题。继续调 $D_0$ 只是在改变振铃的宽度,不会消除它。

解决:换高斯低通,或者把巴特沃斯阶数降到 2。如果必须用陡峭的截止,可以在滤波前先对图像做一次轻微的高斯模糊,把最锐的边削弱,再进频域,振铃的幅度会小很多。另一个办法是只对需要压制的频段做滤波,其余频段保留原频谱,用掩膜做局部混合,而不是全图统一处理。

6. 一次参数扫描:把三种低通的截止频率做成可复现的对比表

调参最怕凭感觉。$D_0$ 从 0.05 试到 0.2,三种滤波器一共十二组,肉眼一张张看很容易记混。我的习惯是先跑一遍网格,用两个客观指标把结果排出来,再挑两三张细看。

清晰度用拉普拉斯方差衡量,值越大边缘越锐;对比度用输出标准差,值越大层次越开。这两个指标都不完美,但足够把“糊过头”和“没滤干净”这两种极端排掉。

import itertools def sweep(gray, ratios=(0.05, 0.08, 0.12, 0.2)): rows = [] for kind, ratio in itertools.product( ['ideal', 'gaussian', 'butterworth'], ratios): out = freq_lowpass(gray, kind=kind, ratio=ratio) lap_var = cv2.Laplacian(out, cv2.CV_64F).var() rows.append((kind, ratio, round(out.mean(), 2), round(out.std(), 2), round(lap_var, 2))) return rows

跑完之后按lap_var排序,通常能看到一条清晰的趋势:ratio 越大,拉普拉斯方差越高,但高频噪声也回来得越多。高斯在同样的 ratio 下方差最低,理想低通最高,巴特沃斯居中。如果某张图在 ratio=0.12 时方差突然跳变,多半是振铃开始出现,翻回去看那张图就能确认。

kindratiomeanstdlap_var
gaussian0.05199.842.118.3
gaussian0.12200.155.646.7
butterworth0.08199.551.261.4
ideal0.08198.953.888.2

这张表是合成条纹图上的典型量级,真实图会不一样,但相对关系稳定:理想低通的拉普拉斯方差总是最高,因为它把边缘过冲也算了进去;高斯的均值最稳,因为它没有振铃带来的局部极值。我在一个批次两百多张扫描件的项目里,最后选的是高斯 ratio=0.09 做去网纹,再叠一次轻度 unsharp mask 把字口拉回来。这个组合不是理论最优,是那张表加上几十张目视挑出来的。

血泪经验是别在循环里反复读写磁盘,freq_lowpass每次都会重新算radius_grid,图多的时候把网格缓存下来能省一半时间。另外记得把中间结果存成 16 位 PNG 再做指标统计,存成 JPEG 之后压缩噪声会污染拉普拉斯方差,排序结果会失真。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表