提示

返回博客列表

视频缩放不只是 -s:缩放算法、色度采样与"为什么放大后这么糊

有次要处理一批老素材:480p 的 DV 录像,客户要求出 1080p 版本。

我用了最直白的命令:ffmpeg -i old.avi -vf scale=1920:1080 -c:v libx264 -crf 20 out.mp4。出来的画面……确实变大了,但糊得像隔着一层雾。客户不满意,我自己看也不满意。

然后我做了两件事:换缩放算法(默认 bilinear → lanczos),以及处理色度上采样。改完之后同样的源,主观清晰度提升明显。

这篇写这次学到的东西:缩放算法有哪些、各自适合什么、4:2:0 色度到底意味着什么、为什么放大到 1080p 也不可能"真的变清晰",以及怎么用客观指标验证。

TL;DR:-vf scale=W:H:flags=<算法>,缩小用 area 或 bicubic,放大用 lanczos 或 spline,实时用 bilinear。三个容易忽略的点:先确认 SAR(有些 1440×1080 的视频显示时就是 1920×1080,不该按像素尺寸算);4:2:0 的色度分辨率只有亮度的一半,缩放时色度要用足够好的算法(加 full_chroma_int 修正半像素偏移);放大不能凭空增加细节(要真提升画质只能用 AI 超分,那是另一回事)。追求质量用 zscale(需要 libzimg),它支持分别指定亮度和色度算法,还能做正确的色彩空间转换。

目录

一、两类缩放,问题完全不同

降采样(缩小):1080p → 720p。信息是"多余"的,问题在于怎么丢弃。

  • 丢得不好 → 摩尔纹、锯齿、细节消失;
  • 核心是抗混叠(抗锯齿):缩小前要先把高频滤掉,否则采样会产生伪影。

升采样(放大):480p → 1080p。信息是"不足"的,问题在于怎么插值。

  • 插值不好 → 模糊(bilinear)或者振铃/锯齿(过度锐化);
  • 放大不可能恢复已经丢失的细节,只能让已有的细节看起来更平滑自然。

这两类要用不同的算法,用错了质量差别很大。

二、先搞清楚这个视频"显示时多大"(SAR/DAR)

这是我踩的第一个坑:像素尺寸不等于显示尺寸。

很多老视频(DV、DVD、某些电视源)用的是非方形像素:

1440×1080 的 DV 视频,像素宽高比 SAR = 4:3
显示尺寸 = 1440 × (4/3) : 1080 = 1920 : 1080     ← 其实是 16:9

如果你按像素尺寸缩放,等于把它当成 4:3 处理,画面会被横向压扁。

检查:

ffprobe -v error -select_streams v:0 \
  -show_entries stream=width,height,sample_aspect_ratio,display_aspect_ratio \
  -of default=noprint_wrappers=1 input.avi
# width=1440
# height=1080
# sample_aspect_ratio=4:3
# display_aspect_ratio=16:9        ← 这才是显示比例

正确处理(把非方形像素"拉正"):

# 方法 1:缩放到显示尺寸,并把 SAR 设为 1(方形像素)
ffmpeg -i input.avi -vf "scale=1920:1080,setsar=1" -c:v libx264 ... out.mp4

# 方法 2:通用写法(自动按 SAR 计算)
ffmpeg -i input.avi -vf "scale=iw*sar:ih,setsar=1" -c:v libx264 ... out.mp4

setsar=1 一定要加——否则容器里还是标记着 SAR=4:3,播放器会再拉伸一次,画面就错了。

我现在的习惯:处理任何来源不明的视频之前,先看一眼 SAR。这一步只要 5 秒,能避免"画面比例不对"这类返工。

三、ffmpeg 的缩放算法清单

scale 滤镜的 flags 参数(swscale 的算法):

算法 类型 质量 速度 适合
fast_bilinear 双线性(快速版) 差 最快 实时预览
bilinear 双线性 较差 很快 ffmpeg 默认(降采样时)
bicubic 双三次 中 中 通用、放大缩小都还行
bicublin 亮度双三次+色度双线性 中 中 折中
area 面积平均 好(缩小) 中 降采样首选
gauss 高斯 中 中 特殊用途
sinc Sinc 窗 好(缩小) 慢 高质量缩小
lanczos Lanczos(3-tap) 好(放大) 慢 升采样首选
spline 样条 好(放大) 慢 放大,比 lanczos 少一点振铃
neighbor 最近邻 差(有锯齿) 最快 像素艺术(保持硬边缘)
point 同 neighbor

我的选择规则:

缩小(降采样)→ area 或 bicubic
放大(升采样)→ lanczos 或 spline
实时/批量吞吐优先 → bilinear 或 fast_bilinear
缩放到整数倍 → 效果最好(1/2、1/4 这种)
# 缩小
-vf scale=1280:720:flags=area

# 放大
-vf scale=1920:1080:flags=lanczos

# 也可以全局设置(影响所有隐式缩放)
-sws_flags lanczos

一个反直觉的点:bilinear 是 ffmpeg 的默认算法,但它在缩小时质量很差(因为它只用了 2×2 的邻域采样,没有做充分的抗混叠)。缩小时用 area 明显更好——这是很多人"ffmpeg 缩放质量差"印象的来源,其实只是用错了默认算法。

四、4:2:0 意味着什么:色度只有一半分辨率

绝大多数视频是 YUV 4:2:0:

亮度(Y):全分辨率    1920×1080
色度(U/V):1/2 × 1/2   960×540

也就是说:颜色信息的分辨率只有亮度的一半(水平和垂直都是)。

这是"色度子采样"(chroma subsampling),利用了人眼对亮度敏感、对色彩细节不敏感的特性。

对缩放的影响:

  • 缩放时,亮度和色度要分别处理;
  • 如果色度用太差的算法(或者处理不当),会出现彩色边缘锯齿、颜色渗色;
  • 放大时色度的插值更重要(因为要插值出 4 倍的色度像素)。

ffmpeg 的默认行为:亮度和色度用同一个 flags(比如都 lanczos)。大多数情况下够用。

进阶控制(分别指定):

# 用 zscale 分别指定(见下节)
-vf zscale=1920:1080:f=lanczos:c=lanczos

五、色度半像素偏移与 full_chroma_int

这是个比较专业但真实存在的问题。

背景:在 4:2:0 里,色度采样点相对于亮度有一个半像素的偏移(chroma siting)。不同标准的规定不同:

  • MPEG-2:色度水平对齐(left siting);
  • H.264/HEVC(常见的):色度居中(center siting)。

如果缩放时不考虑这个偏移,色度会被"错开"半像素——表现是彩色边缘出现轻微的渗色或者错位,在大面积高对比色彩边缘(比如红色文字)尤其明显。

修复:给 swscale 加 full_chroma_int(用更精确的色度插值)和 full_chroma_inp(考虑输入色度定位):

ffmpeg -i in.mp4 -vf "scale=1280:720:flags=lanczos+full_chroma_int+full_chroma_inp" out.mp4

或者全局:

ffmpeg -sws_flags "+full_chroma_int+full_chroma_inp+accurate_rnd" -i in.mp4 -vf scale=1280:720 out.mp4

这几个 flag 的含义:

flag 作用
full_chroma_int 色度插值用全精度的整数计算(而不是近似的快速路径)
full_chroma_inp 考虑输入色度的子采样位置
accurate_rnd 使用精确的舍入

代价:慢一些(大约 20~40%)。只在对画质要求高的场景用。

我第一次看到这几个 flag 的时候完全不知道是干什么的,直到有一次处理一批带红色字幕的老片,缩放后字幕边缘出现明显的彩色毛边,加上 full_chroma_int 之后消失——这才明白它解决的是什么问题。

六、zscale:更专业的选择

zscale 是基于 zimg 库的高质量缩放/色彩转换滤镜,ffmpeg 需要编译时带 --enable-libzimg。

它比 swscale 强在哪:

  1. 分别指定亮度和色度算法(f= 是亮度,c= 是色度);
  2. 正确的色彩空间转换:支持指定 matrix/primaries/transfer,能做真正的色域转换(比如 BT.709 → BT.2020);
  3. 正确的位深处理与 dither:高位深转低位深时做抖动,避免色带;
  4. 色度定位(chroma location)可指定。
# 高质量缩小(亮度 lanczos,色度 bicubic)
-vf zscale=1280:720:f=lanczos:c=bicubic

# 完整色彩参数(HDR → SDR 的 tone mapping 也用它)
-vf zscale=1920:1080:f=spline36:c=lanczos:min=709:pin=709:tin=709:rin=tv:m=709:p=709:t=709:r=tv

参数含义:

参数 含义
w / h 目标尺寸
f 亮度(luma)缩放算法
c 色度(chroma)缩放算法
min/pin/tin 输入的 matrix / primaries / transfer
m/p/t 输出的 matrix / primaries / transfer
rin / r 输入/输出的色彩范围(tv=limited, pc=full)
d / di dither 方式(位深降低时用)

什么时候值得用 zscale:

  • 做 HDR → SDR 转换(tone mapping);
  • 高质量母版处理;
  • 跨色域转换(BT.709 ↔ BT.2020);
  • 对色度质量特别敏感的内容。

日常缩放用 swscale(scale 滤镜)就够了——差别在大多数内容上看不出来。

检查你的 ffmpeg 有没有 zscale:

ffmpeg -filters 2>/dev/null | grep zscale

七、缩放前后该做什么处理

顺序很重要,做错了会放大问题:

处理 应该在缩放前还是后 理由
降噪 前 噪点会被缩放"抹匀"变成更难看的块状;先降噪再缩放更干净
锐化 后 缩放后锐化才有意义(缩放前的锐化会被插值冲掉)
去隔行 前 隔行素材必须先去隔行,否则缩放会把两场混在一起(梳齿状伪影)
裁剪 前(先裁后缩放) 缩放后再裁等于浪费了处理量
色彩转换 后 在空间域处理完再做色彩映射

一个典型的完整处理链(老素材修复):

ffmpeg -i old.avi \
  -vf "cropdetect-safe,yadif=1,hqdn3d=2:1:3:3,scale=1920:1080:flags=lanczos+full_chroma_int,unsharp=5:5:0.8:5:5:0.0" \
  -c:v libx264 -crf 20 -pix_fmt yuv420p \
  -c:a aac -b:a 128k out.mp4

顺序是:去隔行 → 降噪 → 缩放 → 锐化。

关于锐化:缩放后轻微锐化能显著改善"糊"的观感,但过度锐化会产生白边(halo),看起来更假。我一般只在放大时用,参数很保守(unsharp=5:5:0.5)。

八、尺寸对齐与"非标准"分辨率

编码器对尺寸有要求:

  • H.264/H.265 在 4:2:0 下要求宽高都是偶数(因为色度是 1/2);
  • 某些硬件编码器要求 16 的倍数;
  • 4:2:2 的水平方向要求 2 的倍数。

ffmpeg 的错误提示:

[libx264 @ 0x...] width not divisible by 2 (1281x720)

解决办法:

# 方法 1:自动向下取偶数(-2 而不是 -1)
-vf scale=1280:-2

# 方法 2:用 crop 裁掉奇数那一行/列
-vf crop=floor(iw/2)*2:floor(ih/2)*2

# 方法 3:用 pad 补(不推荐,会加黑边)

-2 的含义:自动计算另一个维度,并保证结果是偶数。-1 只保证比例不保证偶数——这个区别很多人不知道,导致"明明用了 -1 还是报错"。

需要 16 的倍数时(某些硬件编码器):

-vf "scale=trunc(iw/16)*16:trunc(ih/16)*16"

或者用 pad 补到 16 的倍数(加上黑边,但保持画面比例):

-vf "scale=1280:720,pad=ceil(iw/16)*16:ceil(ih/16)*16"

九、实测对比

降采样 1080p → 720p(同一源,VMAF 以"用 lanczos 缩放到同样尺寸再编码"为参考)

算法 相对耗时 主观评价 说明
bilinear(默认) 1.0× 细节丢失明显,边缘有锯齿 默认就是它,难怪大家觉得 ffmpeg 缩放差
area 1.4× 干净,无明显伪影 推荐
bicubic 1.5× 略锐于 area,边缘有小振铃 也不错
lanczos 2.1× 锐利,但有轻微振铃 缩小用它有点"过"
sinc 3.0× 最锐,振铃最明显 一般不用

升采样 480p → 1080p

算法 相对耗时 主观评价
bilinear 1.0× 糊,明显
bicubic 1.6× 尚可,稍糊
lanczos 2.3× 清晰,边缘锐利,轻微振铃
spline 2.5× 清晰,振铃最少
neighbor 0.8× 块状锯齿(不推荐,除非像素艺术)

结论:

  • 缩小:area(性价比最高);
  • 放大:lanczos 或 spline(spline 的振铃更少,看内容选)。

耗时的影响:在"转码本来就要几秒/帧"的场景里,缩放多花 2 倍时间(缩放本身只占总耗时的 5~15%)影响不大。只有在实时转码(直播)时才需要考虑速度,那时候用 bilinear。

十、放大到 1080p 的现实预期

必须说清楚的一件事:放大不能凭空增加细节。

480p 的源里没有的细节,放大到 4K 也不会有。缩放算法能做的是:

  1. 让已有的边缘更平滑(减少锯齿感);
  2. 让插值更"自然"(避免块状、模糊);
  3. 配合锐化让主观清晰度提升一点。

但它不能恢复:
- 已经丢失的纹理(衣服的织纹、皮肤细节);
- 被压缩掉的块状/模糊(源本身是低码率 480p 的话,放大只会放大压缩伪影);
- 隔行、噪点这些损伤(要在缩放前单独处理)。

如果客户期待"放大后像高清一样清晰":

  • 技术上只能靠 AI 超分(Real-ESRGAN 等,项目里有专门一篇讲);
  • 而且超分也有代价:可能产生幻觉细节、处理极慢(实时倍数 0.1~2x)、对老片效果不稳定。

我的交付话术:"放大到 1080p 后画面会比原来更平滑、在电视上没有拉伸感,但清晰度受限于原始素材。如果要接近原生高清的观感,需要做 AI 超分,耗时是普通转码的 20~50 倍,我可以先做一集样片给您看效果。"

给客户一个样片永远比解释有用。 我现在遇到这类需求,都是先出 30 秒样片(普通缩放 vs AI 超分),让客户自己对比选择。

十一、坑清单

  1. 用默认的 bilinear 缩小 → 细节丢失、锯齿。用 area。
  2. 放大用 bilinear → 糊。用 lanczos/spline。
  3. 忽略 SAR → 1440×1080 的视频被当成 4:3 处理,画面变形。
  4. 忘了 setsar=1 → 播放器再拉伸一次。
  5. 用 -1 而不是 -2 自动计算尺寸 → 奇数尺寸报错。
  6. 缩放不处理色度质量 → 彩色边缘毛边。加 full_chroma_int。
  7. 先缩放再降噪 → 噪点被抹成块状。先降噪。
  8. 先缩放再去隔行 → 梳齿伪影被放大。先去隔行。
  9. 缩放前锐化 → 白等着被插值冲掉,白费功夫。缩后锐化。
  10. 过度锐化 → 白边(halo),看着更假。保守参数。
  11. 期待放大能变清晰 → 物理上不可能。要跟客户说清楚,或者做 AI 超分样片。
  12. 缩放后再裁剪 → 浪费算力,且画质更差(二次采样)。
  13. 缩放和编码的分辨率不一致 → 缩放 720p 但编码器按 1080p 的码率给,浪费;或者反过来画质不够。
  14. 高位深转 8bit 不做 dither → 渐变区域出现色带。用 zscale 的 dither,或者保持 10bit。
  15. HDR 素材直接缩放 → 需要 tone mapping(项目里色彩空间那篇讲过),直接缩放会得到发灰的画面。
  16. 缩放后画质变差却以为是编码器问题 → 先单独输出缩放结果(不编码)看一眼,定位到底是哪一步的问题。

最后说说这次之后养成的习惯。

处理任何"画质不满意"的问题时,先把流程拆开单独验证:

# 1. 只缩放,不编码(输出 raw 或者无损),看缩放本身有没有问题
ffmpeg -i in.mp4 -vf scale=1920:1080:flags=lanczos -c:v libx264 -crf 0 -an scale_only.mp4

# 2. 用同样的源,只改编码参数,看编码的影响
ffmpeg -i in.mp4 -vf scale=1920:1080:flags=lanczos -c:v libx264 -crf 23 -an encode_test.mp4

# 3. 对比

很多时候"画质差"是多个环节叠加的结果(缩放算法差 + 码率不够 + 色度处理粗糙),单独看每一环才能定位。

还有一个体会:"默认参数"不等于"合适的参数"。ffmpeg 的缩放默认是 bilinear,是因为它快、通用,不是为了质量。类似的情况还有:AAC 默认码率、CRF 默认值、preset 默认 medium——这些都是"能用"的起点,不是"最优"的答案。

所以每当我要用某个工具的默认参数做正式产出时,都会问一句:"这个默认值是为了什么场景选的?我的场景是什么?" 这一问,经常能发现可以改进的地方。

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

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

顶部