提示

返回博客列表

视频降噪不只是 -vf hqdn3d:五种降噪滤镜的横评与参数选择

处理一批低照度拍的素材时,我用了最常见的一招:

bash ffmpeg -i noisy.mp4 -vf hqdn3d -c:v libx264 -crf 20 out.mp4

噪点确实少了,但画面糊了——人脸像蒙了一层塑料,而且人物一动就有拖影。客户说"不如不降"。

我去查了 ffmpeg 的降噪滤镜清单,发现不止 hqdn3d 一个——还有 nlmeans、bm3d、fftdnoiz、vaguedenoiser 等好几个,原理完全不同。我拿同一段素材把它们都跑了一遍,对比效果和耗时,最后选出了适合不同场景的方案。

这篇是完整的横评数据和参数经验表。

TL;DR:ffmpeg 里的降噪滤镜按原理分三类:空域+时域滤波(hqdn3d,最快但有拖影)、非局部均值(nlmeans,保细节好,慢)、变换域(bm3d、fftdnoiz、vaguedenoiser,质量最好,最慢)。实测结论:批量/实时 → hqdn3d 用保守参数(时域参数要小,否则拖影);重要内容 → bm3d(质量最好);折中 → nlmeans。另外两个关键点:降噪必须在编码之前做(降噪后码率需求下降 20~40%,体积更小);过度降噪的"塑料感"比噪点更难看,宁可保守。

目录

一、噪声是从哪来的

来源 表现 特点
传感器噪点(高 ISO、低照度) 彩色斑点、暗部明显 最常见
压缩伪影(低码率) 块效应、振铃、蚊子噪点 不是真噪声,降噪效果差
磁带/胶片老化 划痕、颗粒、掉帧 老素材
传输误码 局部块状损坏 少见

重要区分:真噪声(传感器) 和 压缩伪影 的处理方式不同。

  • 真噪声是随机的 → 降噪滤镜有效;
  • 块效应是有结构的 → 要用去块(deblock)而不是降噪(虽然 hqdn3d 的空域滤波能稍微缓解)。

判断方法:看一帧的平坦区域(比如墙面),如果是随机的彩色斑点 → 真噪声;如果是 8×8 的方块边界 → 压缩伪影。

二、三类降噪原理

1. 空域滤波(Spatial)

在一帧内部用邻域像素平均:

当前像素 = 周围 3×3 像素的加权平均

代价:细节和噪声一起被抹掉(边缘变糊)。

2. 时域滤波(Temporal)

利用相邻帧的信息:噪声是随机的,真实内容在时间上是连续的,所以把连续几帧平均就能抵消噪声。

当前像素 = (前一帧 + 当前帧 + 后一帧) / 3

代价:运动区域会拖影(因为运动物体在相邻帧里位置不同,平均之后就"重影"了)。

3. 非局部均值 / 变换域(NLMeans / BM3D)

NLMeans:不只用"周围的像素",而是在整幅图里找相似的块,用它们的加权平均。这样能保留重复纹理(不会被自己的邻域"平均掉")。

BM3D:把相似的块堆叠成 3D 数组,在变换域(DCT/小波)做阈值收缩。目前公认效果最好的传统算法。

代价:慢(NLMeans 比 hqdn3d 慢 10 倍,BM3D 慢 20~30 倍)。

三、ffmpeg 的降噪滤镜清单

ffmpeg -filters 2>/dev/null | grep -iE 'denoise|dnoiz|nlmeans|bm3d|hqdn3d'
滤镜 原理 速度 质量 备注
hqdn3d 空域 + 时域(高斯) 最快 中 最常用,默认参数偏强
nlmeans 非局部均值 慢 好 保细节好
bm3d BM3D(变换域) 最慢 最好 需要编译时带 libgsl?(部分版本)
fftdnoiz FFT 频域滤波 较慢 好 3D(空域+时域)
vaguedenoiser 小波软阈值 中 中 有 wavelet 模式
owdenoise 小波(Overcomplete) 中 中
dctdnoiz DCT 阈值 中 中
removegrain AviSynth 滤镜,ffmpeg 没有

检查你的 ffmpeg 支持哪些:

ffmpeg -filters 2>/dev/null | grep -E ' (hqdn3d|nlmeans|bm3d|fftdnoiz|vaguedenoiser|owdenoise|dctdnoiz) '

如果某个滤镜没列出,说明编译时没带(用官方静态构建一般都有)。

四、逐个说参数

hqdn3d

-vf "hqdn3d=luma_spatial:chroma_spatial:luma_tmp:chroma_tmp"
参数 默认 说明
luma_spatial 4.0 亮度空域强度(抹细节)
chroma_spatial 3.0 色度空域强度
luma_tmp 6.0 亮度时域强度(拖影的来源)
chroma_tmp 4.5 色度时域强度

默认值偏强(这也是我用默认参数翻车的原因)。常用组合:

# 轻度(推荐起点)
-vf "hqdn3d=2:1:3:3"

# 中度
-vf "hqdn3d=4:3:6:4.5"      # 默认

# 只降色度噪点(保留亮度细节,最保守)
-vf "hqdn3d=0:0:0:6"

最后一个"只降色度"的用法很有用:传感器噪点里最碍眼的是彩色噪点(红绿蓝的斑点),而亮度噪点相对不明显。只降色度可以在几乎不损失细节的前提下让画面干净很多。

nlmeans

-vf "nlmeans=s=1.0:p=7:pc=5:r=15:rc=15"
参数 说明
s 滤波强度(主要调这个,0.5~3.0)
p 亮度块匹配的 patch 大小(默认 7)
r 亮度搜索范围(默认 15,越大越慢越准)
pc / rc 色度版本(可以更小,省时间)

经验值:

# 轻度
-vf "nlmeans=s=0.8:p=7:r=9"

# 中度
-vf "nlmeans=s=1.5:p=7:r=15"

# 强(慢)
-vf "nlmeans=s=3.0:p=7:r=21"

bm3d

-vf "bm3d=sigma=3:block=8:bstep=4:group=1:range=9:hard=0"
参数 说明
sigma 噪声强度估计(1~10,越大降得越狠)
block 块大小(8 或 16)
bstep 块步长(越小越慢越准)
group 分组大小
range 时域搜索范围(0 = 只做空域,>0 用时域)
hard 0 = 软阈值(默认,更自然),1 = 硬阈值

sigma 是关键:它应该匹配素材的实际噪声水平。设太大 → 过度平滑;设太小 → 没效果。

怎么估计 sigma:可以用 bm3d 的 estimate 参数(basic 估计),或者先试几个值看效果。

fftdnoiz

-vf "fftdnoiz=sigma=0.5:amount=1:block=12:overlap=6"
参数 说明
sigma 噪声阈值(0.1~2)
amount 降噪强度(0~1)
block FFT 块大小(8~16)
overlap 块重叠(block 的一半常见)

vaguedenoiser

-vf "vaguedenoiser=threshold=2:method=garrote:nsteps=6:percent=85"
参数 说明
threshold 阈值(越高降得越少)
method hard / soft / garrote(推荐)
nsteps 小波分解层数
percent 保留的能量百分比

五、横评实测

测试条件:1080p30 的夜景素材(明显的高 ISO 噪点),30 秒片段,8 核机器。

评分:3 人盲评"细节保留度" 1~5 分;PSNR 用"人工加噪的干净源"做参考(有原片)。

滤镜 参数 相对耗时 PSNR 提升 细节评分 拖影
不处理 — 1× 0 4.5(细节在,但噪点多) 无
hqdn3d 默认 4:3:6:4.5 1×(8 秒) +2.1 dB 2.6 明显
hqdn3d 保守 2:1:3:3 1× +1.4 dB 3.7 轻微
hqdn3d 仅色度 0:0:0:6 1× +1.1 dB 4.3 轻微
nlmeans s=1.0,r=15 12×(95 秒) +2.8 dB 4.1 无
nlmeans 强 s=2.0,r=15 14× +3.1 dB 3.6 无
bm3d sigma=3,range=9 27×(210 秒) +3.5 dB 4.4 无
bm3d 空域 sigma=3,range=0 18× +3.0 dB 4.3 无
fftdnoiz sigma=0.5 19× +2.5 dB 3.9 无
vaguedenoiser threshold=2 7× +2.2 dB 3.5 无

几个关键发现:

  1. hqdn3d 默认参数的细节评分只有 2.6 ——这就是我翻车的原因,它降得太狠了;
  2. "仅降色度"的性价比极高:PSNR 提升 1.1 dB(不算多),但细节评分 4.3(几乎无损),耗时和不处理差不多;
  3. bm3d 质量最好(+3.5 dB,细节 4.4),但慢 27 倍;
  4. nlmeans 是好的折中:质量接近 bm3d,耗时只有它的一半。

再看体积影响(降噪后编码,同 CRF 23):

处理 输出体积
不降噪 100%
hqdn3d 默认 68%
hqdn3d 仅色度 82%
bm3d 62%

降噪后体积明显下降——噪点是最"贵"的信息(随机、不可压缩)。所以降噪不只是画质改善,还能省码率。

六、拖影:时域降噪的代价

这是 hqdn3d 最容易出问题的地方。

时域降噪假设"相邻帧的同一位置是同一内容"。当画面里有运动物体时,这个假设不成立:

前一帧:人脸在位置 A
当前帧:人脸在位置 B
平均后:A 和 B 都有淡淡的人脸 → 拖影/鬼影

hqdn3d 的 luma_tmp 参数就是控制这个的。默认值 6.0 相当激进,所以运动画面会有明显拖影。

我的规则:

内容 luma_tmp
静止为主(访谈、监控、PPT) 可以用大一点(6~8)
中等运动 3~4
剧烈运动(赛事、演唱会) 0~2(几乎关掉时域)
有快速摇镜头 0(只做空域)

验证拖影:抽连续 3 帧看运动物体的边缘,或者播放时看运动区域有没有"残影"。

nlmeans / bm3d 的时域版本(range > 0)也会拖影,但它们有运动补偿或者更保守的块匹配,拖影比 hqdn3d 轻得多。

七、降噪和编码的关系

顺序:降噪 → 编码(不能反过来,编码后再降噪是对"已经损伤的画面"再处理)。

为什么要在编码前降噪:

  1. 编码器会浪费大量码率去编码噪点(噪点是高频、随机的,最难压缩);
  2. 降噪后同样画质需要的码率下降 20~40%;
  3. 反过来,如果不降噪直接编码,噪点会导致码率飙升或者画质下降(编码器"顾此失彼")。

实测(同一段夜景,目标 VMAF 93):

处理 需要的码率
不降噪 6800 kbps
bm3d 降噪后 4100 kbps(-40%)

这是个很划算的交易:花 210 秒做降噪,换来 40% 的码率节省(对长期存储和分发来说,收益会持续累积)。

但要注意:降噪本身是有损的。如果内容很干净就不要降噪(白白损失细节)。所以要先判断噪声水平(见第九节)。

八、参数经验表

按噪声程度(我用画面的平坦区域标准差估计):

噪声水平 推荐 参数
很轻微(几乎看不到) 不处理 —
轻微 hqdn3d 仅色度 hqdn3d=0:0:0:4
中等(暗部可见) hqdn3d 保守 或 nlmeans hqdn3d=2:1:3:3 / nlmeans=s=0.8
明显(高 ISO 夜景) nlmeans 或 bm3d nlmeans=s=1.5 / bm3d=sigma=3
严重(极暗环境) bm3d + 先降分辨率? bm3d=sigma=5
老磁带/胶片颗粒 保留颗粒!用轻度 hqdn3d=1:1:2:2

最后一条是个审美的选择:胶片颗粒是内容的一部分(很多导演特意加颗粒),降噪会把"电影感"一起降掉。所以老片/胶片素材我只做很轻的处理,或者干脆不处理。

九、批量流程:先测噪声再定参数

不要对所有素材用同一套参数——先测噪声水平:

def estimate_noise(video_path, samples=10):
    """用一个简单的方法估计噪声:平坦区域的高频能量。"""
    import cv2
    import numpy as np
    cap = cv2.VideoCapture(video_path)
    total = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
    scores = []
    for i in range(samples):
        cap.set(cv2.CAP_PROP_POS_FRAMES, int(total * i / samples))
        ok, frame = cap.read()
        if not ok:
            continue
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY).astype(np.float32)
        # 高通(原图 - 模糊)
        hp = gray - cv2.GaussianBlur(gray, (5, 5), 1.5)
        # 只在平坦区域(低梯度)测量
        grad = np.abs(cv2.Sobel(gray, cv2.CV_32F, 1, 1, ksize=3))
        flat = grad < 5
        if flat.sum() > 1000:
            scores.append(np.abs(hp[flat]).std())
    cap.release()
    return float(np.mean(scores)) if scores else 0.0

经验阈值(1080p):

噪声分数 判断
< 1.5 干净,不处理
1.5~3 轻微,仅色度
3~6 中等,hqdn3d 保守
> 6 明显,nlmeans/bm3d

批量脚本:

def pick_filter(noise: float) -> str:
    if noise < 1.5:
        return ''
    if noise < 3:
        return 'hqdn3d=0:0:0:4'
    if noise < 6:
        return 'hqdn3d=2:1:3:3'
    return 'nlmeans=s=1.5:p=7:r=15'


def process(path, out):
    noise = estimate_noise(path)
    vf = pick_filter(noise)
    cmd = ['ffmpeg', '-y', '-v', 'error', '-i', path]
    if vf:
        cmd += ['-vf', vf]
    cmd += ['-c:v', 'libx265', '-crf', '26', '-preset', 'slow',
            '-c:a', 'copy', out]
    subprocess.run(cmd, check=True)
    return {'file': path, 'noise': noise, 'filter': vf or 'none'}

处理完一定要抽查(抽 3~5 个文件看画面),尤其是"降噪过度"的情况——这个自动检测不出来,只能人看。

十、坑清单

  1. 用 hqdn3d 默认参数 → 太狠,画面糊。用保守参数或仅色度。
  2. 时域参数太大 → 运动拖影。按内容调 luma_tmp。
  3. 对干净素材降噪 → 白白损失细节。先测噪声水平。
  4. 对胶片颗粒做重度降噪 → 电影感没了。颗粒要保留。
  5. 用降噪去修块效应 → 不对症。块效应用 deblock 或者重新编码提高码率。
  6. 降噪放在编码之后 → 已经晚了。必须在编码前。
  7. bm3d 太慢,跑了一整批才发现 → 先测一段算总时间。
  8. 没检查 ffmpeg 是否支持该滤镜 → 某些构建缺 bm3d。
  9. nlmeans 参数给太大(s=5)→ 慢且过度平滑。s 一般 0.5~3。
  10. bm3d 的 sigma 不匹配噪声 → 太小没效果,太大过度平滑。先试几个值。
  11. 降噪后不重新评估码率 → 可以降 CRF 或降码率,省体积。降噪后检查输出体积。
  12. 对所有片段用同一参数 → 一段素材里不同场景噪声不同(暗场景噪多)。理想是分场景处理(复杂,一般不做)。
  13. 降噪后再锐化想"补回细节" → 补不回来,还会放大残留噪声的观感。
  14. 批量处理后不抽查 → 过度降噪只能人眼发现。
  15. 忘了降噪会改变编码器的码率分配 → 如果用了固定码率(ABR),降噪后画质会更好(同样的钱买更多有效信息);如果用 CRF,体积会更小。都是好事,但要重新校准参数。

最后说说这次横评的收获。

最大的认知改变是:降噪是一个"交换"而不是"改善"。

你用"细节"换"干净"。交换比并不固定——它取决于滤镜算法(bm3d 的交换比远好于 hqdn3d)和参数强度。所以:

  • 想清楚你要什么:如果内容的价值在"真实感"(新闻、纪录片、取证),保守降噪甚至不降;如果价值在"观感"(宣传片、短视频),可以激进一点;
  • 用更好的算法而不是更大的强度:hqdn3d 强度加倍,细节损失加倍;换成 bm3d,降噪更多但细节保留更好。"更好的工具"优于"更大的参数"。

还有一个很实用的发现:"只降色度"这个技巧性价比极高。彩色噪点是人眼最敏感的(黑白噪点看起来像"颗粒感",彩色噪点看起来像"脏"),而只降色度几乎不损失亮度细节——用很小的代价解决最碍眼的问题。

这套思路(找到"最碍眼的部分"而不是"全部处理")在其他地方也适用:

  • 编码:ROI 编码只给重要区域加质量;
  • 修复:老片只需要去划痕和去隔行,不需要全面重做;
  • 优化:先优化最慢的那个环节,而不是全部优化。

精准地解决主要矛盾,比"全面处理"更有效,也更省成本。

想亲手试试?用 VidDown 一键解析下载

粘贴视频链接即可解析,多平台支持、网页端即用;下载桌面客户端解锁海外平台本地解析,开通会员更享不限次下载。

顶部