🎬 第十一章 SWScale图像缩放与颜色空间转换
📄 一句话概括

swscale 只做两件事:改尺寸、改格式。 两件事能在一次调用里同时做,不必转两趟。

📝 本节和前面几节的关系

11.1 讲了色彩的原理,这一节讲干这件事的工具。 11.3 用它处理真实的一帧。

一、这一层有多大

本机认得 267 种像素格式,比编解码器还多。 项 值


像素格式总数 267 常用几个 yuv420p/nv12/rgb24/gray

为什么这么多:每种色度采样比例(4:2:0/4:2:2/4:4:4)、每种位深(8/10/12/16 位)、 每种排布都算一种。 常用的其实只有十来个,其余是专业设备与中间格式。 所以看格式表别慌,认住 yuv420p 与 rgb24 就够起步。

二、只做两件事

别看名字里只有 scale,它也管格式。 做什么 参数 典型用途


改尺寸 width/height 缩略图、降分辨率省码率 改格式 format YUV 转 RGB 给图像算法

这两件事可以在一次调用里同时做。 因为底层走的是同一条流水线:先重采样像素、再算颜色。 所以「缩到 320 宽再转 RGB」应该写成一次 reformat, 不要写成两次——两次意味着多分配一个中间帧、多拷一遍内存。

三、五个枚举

控制「怎么换」的那几个开关。 枚举 有几个值 管什么


Interpolation 18 缩放插值算法 Colorspace 5 用哪套换算系数 ColorRange 4 limited 还是 full ColorPrimaries 12 红绿蓝的基准色 ColorTrc 17 伽马曲线

前两个最常用,后三个多数时候不用管。 但「颜色偏了」「暗部断层」这类问题,查的就是后三个。 Interpolation 有 18 个值,常用的只有五个:FAST_BILINEAR、BILINEAR、BICUBIC、POINT、LANCZOS。 其余是给专业修复与特殊场合用的。

四、reformat 的七个参数

全都不给也能调,返回一个原样的副本。 参数 默认 说明


width/height 不变 要的尺寸 format 不变 要的像素格式 src_colorspace 自动 进来时按什么算 dst_colorspace 自动 出去时按什么算 interpolation 默认 缩放用什么算法 threads 自动 几线程

「自动」的意思是从帧自己的属性里读,读不到就用默认。 所以源那一侧的 colorspace、color_range 有没有标对,直接决定颜色准不准。 这就是「元数据不是装饰」的一个实例:同一堆像素,标 601 与标 709 解出来的颜色不一样。 很多素材这两个字段是空的,那就只能按默认猜。

五、平面与 stride

这是这一层最容易踩的坑,也是最该记住的一条。 宽度 line_size buffer_size


4 [16, 16, 16] [64, 32, 32] 16 [16, 16, 16] [64, 32, 32] 17 [32, 16, 16] [128, 32, 32] 160 [160, 80, 80] [640, 160, 160]

line_size 是一行占多少字节,它被对齐到 16 的倍数。 宽 4 的那一行实际占 16 字节,只有前 4 个是有用的像素。 所以 buffer_size 总比「宽 × 高」大,它算的是对齐后的账。 手动拷内存时踩错这一条,会拷到垃圾甚至越界。 为什么非要对齐:SIMD 指令一次读 16 或 32 字节,地址不对齐要么慢一倍、要么直接崩。 这是用空间换速度的典型做法。

六、动手 Demo:SWScale 这一层有哪些东西

数一遍像素格式与五个枚举,再量一遍 line_size 的对齐规律。

import av

print(len(list(av.video.format.names)))

# 五个枚举各有多少值
for mod, name in ((av.video.reformatter, "Interpolation"),
                  (av.video.reformatter, "Colorspace"),
                  (av.video.reformatter, "ColorRange"),
                  (av.video.reformatter, "ColorPrimaries"),
                  (av.video.reformatter, "ColorTrc")):
    print(name, len(list(getattr(mod, name))))

# stride:宽多少、一行实际占多少
for w in (4, 8, 16, 17, 160):
    f = av.VideoFrame(w, 4, "yuv420p")
    print(w, [p.line_size for p in f.planes],
          [p.buffer_size for p in f.planes])

# 不给参数也能 reformat
g = av.VideoFrame(8, 8, "yuv420p")
print(g.reformat().format.name)

实跑输出:

--------------------------------------------------------------------
SWScale 这一层有哪些东西
--------------------------------------------------------------------

**① 本机认得多少种像素格式**

  项               值
  -----------------------------------------------
  像素格式总数     267
  常用几个         yuv420p/nv12/rgb24/gray

**267 种,比编解码器还多。** 因为每种色度采样比例、每种位深都算一种。
**常用的其实只有十来个**,其余是各种专业格式。

**② swscale 其实只做两件事**

  做什么     参数              典型用途
  --------------------------------------------------------
  改尺寸     width/height     生成缩略图、降低码率
  改格式     format            YUV 转 RGB 送给图像算法

**两件事可以在一次调用里同时做。**
**所以「缩放 + 转色彩空间」只要一次 reformat,不必转两趟。**

**③ 五个枚举,控制「怎么换」**

  枚举               有几个值     管什么
  -----------------------------------------------------
  Interpolation      18           缩放插值算法
  Colorspace         5            用哪套换算系数
  ColorRange         4            limited 还是 full
  ColorPrimaries     12           红绿蓝的基准色
  ColorTrc           17           伽马曲线

**前两个最常用,后三个多数时候不用管。**
**但一旦出问题(颜色偏、暗部断层),查的就是后三个。**

**④ reformat 的七个参数**

  参数               默认     说明
  ------------------------------------------------
  width/height      不变     要的尺寸
  format             不变     要的像素格式
  src_colorspace     自动     进来时按什么算
  dst_colorspace     自动     出去时按什么算
  interpolation      默认     缩放用什么算法
  threads            自动     几线程,默认够用

**全都不给也能调**,它返回一个原样的副本——**这一条用一个空调用就能验。**

**⑤ 平面与 stride**

  宽度     line_size         buffer_size         有效字节
  -----------------------------------------------------------
  4        [16, 16, 16]      [64, 32, 32]        24
  8        [16, 16, 16]      [64, 32, 32]        48
  16       [16, 16, 16]      [64, 32, 32]        96
  17       [32, 16, 16]      [128, 32, 32]       100
  160      [160, 80, 80]     [640, 160, 160]     960

**`line_size` 是「一行占多少字节」,它会被对齐到 16 的倍数。**
**宽 4 的那一行,实际占 16 字节,只有前 4 个有用。**

**这就是 `buffer_size` 总比「宽 × 高」大的原因**——**它算的是对齐后的账。**
**手动拷内存时踩错这一条,会拷到垃圾甚至越界。**

267 种像素格式,五个枚举分别是 18/5/4/12/17。 line_size 对齐到 16,宽 4 与宽 16 的 buffer_size 一样大。

30 秒实验

  1. 用 Interpolation.POINT 缩一张图,与 BICUBIC 比锯齿(提示:肉眼看边缘)。
  2. 打印 av.video.format.names 里含 420 的所有名字。
  3. 建一个 1920×1080 的 yuv420p 帧,看 line_size 是不是正好 1920。

常见误区

  1. 以为 swscale 只缩放。 它也换像素格式与色彩空间。
  2. 分两次调 reformat。 一次能同时做完,两次多一次拷贝。
  3. 把 buffer_size 当成「宽 × 高」。 它含对齐填充。
自测三问(点开对答案) 1. swscale 做哪两件事? —— 改尺寸与改像素格式(含色彩空间)。 2. `line_size` 为什么比宽度大? —— 它被对齐到 16 的倍数,方便 SIMD 与预取。 3. 遇到「颜色偏」先查什么? —— 先查 colorspace 与 color_range,再查编码。

相关笔记