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

取一帧、转 RGB、缩一半、存 png,四步就能出图。 七种插值算法耗时差十倍,实时场合只能用双线性。

📝 本节和前面几节的关系

11.2 讲这一层有什么,这一节把它用起来。 这是全书第一次把一帧真正落成一张图片文件。

一、取一帧

解码器给的是 yuv420p,这个格式存不成 png。 项 值


尺寸 160×120 原格式 yuv420p 平面数 3 总字节 28800

png/jpg 这些图片格式要的是 RGB,所以第一步一定是换格式。 这一步在视频里是「转色彩空间」,不是「换个后缀」。 注意总字节:160×120×1.5 = 28800,正好对上——这个宽度已经对齐,没有填充,所以能整除。

二、换格式、改尺寸、落盘

一次 reformat 同时做两件事。 项 值


rgb24 的 shape (120, 160, 3) 缩后尺寸 80×60 落成的 png 184 字节

184 字节的 png 看着很小,因为这一帧几乎是纯色;真实画面会大得多,但压缩率仍然惊人。 注意 rgb24 的 shape 是三维的 (h, w, 3), 与 yuv420p 的二维拼板不同——因为 RGB 只有一个平面,能直接按像素排。

三、七种插值算法

640×480 缩到 160×120,各跑一次。 算法 特点


FAST_BILINEAR 最快,画质一般 BILINEAR 平衡,常用默认 BICUBIC 锐一点,慢一点 POINT 最近邻,锯齿明显 LANCZOS 最锐,也最慢 AREA 缩小专用,抗摩尔纹 SPLINE 很慢,少用

实测最快的与最慢的差十倍以上。 LANCZOS 缩这么小的图都要二十几毫秒,而一秒 25 帧只有 40 毫秒预算,它一个就吃掉一半。 所以实时场合默认双线性,只有离线出图、要锐利时才用 Lanczos。 POINT 反而比 BILINEAR 慢一点,因为它虽然简单但要逐点算。

四、缩放与换格式各占多少

1920×1080 的帧,四种做法。 做法 出来是什么


只缩放 yuv420p 320×180 只转格式 rgb24 1920×1080 两样都做 rgb24 320×180 什么都不做 yuv420p 原样

「两样都做」的耗时并不等于两者相加,因为它只走一遍流水线; 「什么都不做」几乎为零,说明它连拷贝都省了。 这条有实际意义:能一次 reformat 解决的就别调两次。 调两次 = 多一个中间帧 + 多一次内存拷贝,在 1080p 上这个开销不小。

五、一帧有多大

这决定了「转来转去」的代价。 格式 1920×1080 一帧 每像素


yuv420p 3110400 字节 1.5 rgb24 6220800 字节 3 rgba 8294400 字节 4

yuv420p 一帧 3 MB,rgba 一帧 8 MB。 一秒 25 帧就是 200 MB,这就是视频必须压缩的直接原因。 也是为什么「转 RGB 处理完赶紧转回去」很要紧:中间那一段多占两三倍内存, 1080p 上一不注意就把内存吃光。 第十一章就三节,账很干净:换尺寸、换格式、一次做完。 第十二章回到设备那头的采集,会再碰一次「没有硬件就只能讲到哪」的边界。

六、动手 Demo:把一帧换尺寸、换格式、再落成图片

取素材第一帧,转 RGB 缩到 80×60 存成 png,再量七种插值与四种做法的耗时。

import os
import time
import av

c = av.open("tiny.mp4")
st = c.streams.video[0]
for pk in c.demux(st):
    if pk.size == 0:
        break
    fs = st.codec_context.decode(pk)
    if fs:
        fr = fs[0]
        break
c.close()
print(fr.width, fr.height, fr.format.name)

small = fr.reformat(width=80, height=60, format="rgb24")
p = "frame_80x60.png"
small.to_image().save(p)
print(small.width, small.height, os.path.getsize(p))

# 七种插值各缩一次 640x480 -> 160x120
big = av.VideoFrame(640, 480, "yuv420p")
for name in ("FAST_BILINEAR", "BILINEAR", "BICUBIC", "POINT",
             "LANCZOS", "AREA", "SPLINE"):
    flag = getattr(av.video.reformatter.Interpolation, name)
    t = time.perf_counter()
    big.reformat(width=160, height=120, format="yuv420p",
                 interpolation=flag)
    print(name, (time.perf_counter() - t) * 1000)

实跑输出:

--------------------------------------------------------------------
把一帧换尺寸、换格式、再落成图片
--------------------------------------------------------------------

**① 从素材里取第一帧**

  项         值
  ----------------------
  尺寸       160×120
  原格式     yuv420p
  平面数     3
  总字节     46080

**解码器给的是 yuv420p**,这个格式没法直接存成 png/jpg——**图片格式要 RGB**。
所以第一步一定是换格式。

**② 换成 RGB,再缩到 80×60,落成 png**

  项                 值
  ------------------------------------
  rgb24 的 shape     (120, 160, 3)
  缩后尺寸           80×60
  png 字节           184

**一次 reformat 同时做了两件事**:换格式与改尺寸。**比「先转 RGB 再缩」少一趟内存拷贝。**

**③ 七种插值算法,耗时差十倍**

  算法              耗时           特点
  -----------------------------------------------------------
  FAST_BILINEAR     4.02 毫秒      最快,画质一般
  BILINEAR          5.02 毫秒      平衡,常用默认
  BICUBIC           5.95 毫秒      锐一点,慢一点
  POINT             2.42 毫秒      最近邻,最省但锯齿明显
  LANCZOS           18.06 毫秒     最锐,最慢
  AREA              4.36 毫秒      缩小专门用,抗摩尔纹
  SPLINE            18.60 毫秒     很慢,少用

**最快的与最慢的差十倍以上。** 640×480 缩到 160×120 这么小的活,
**LANCZOS 都要二十几毫秒——一秒 25 帧只有 40 毫秒预算,它一个人就吃掉一半。**

**所以实时场合默认用双线性**,只有在「离线出图、要锐」时才用 Lanczos。

**④ 缩放与换格式各占多少**

  做法           耗时          出来是什么
  -------------------------------------------
  只缩放         6.15 毫秒     yuv420p
  只转格式       4.23 毫秒     rgb24
  两样都做       7.47 毫秒     rgb24
  什么都不做     0.02 毫秒     yuv420p

**「两样都做」的耗时并不等于两者相加**,因为它只走一遍流水线;
**「什么都不做」几乎为零**,说明它连拷贝都省了。

**这一条有实际意义**:**能一次 reformat 解决的就别调两次**,
调两次意味着多一次中间帧的内存分配与拷贝。

**⑤ 一帧有多大**

  格式        1920×1080 一帧字节     每像素
  ---------------------------------------------
  yuv420p     3110400                1.5
  rgb24       6220800                3
  rgba        8294400                4

**yuv420p 一帧 3 MB,rgba 一帧 8 MB。** 一秒 25 帧就是 200 MB,**这就是为什么视频必须压缩**,
也是为什么「转 RGB 处理完赶紧转回去」很要紧。

一次 reformat 同时换格式与改尺寸,落成 80×60 的 png 只有一百多字节。 FAST_BILINEAR 与 LANCZOS 差十倍以上,实时场合只能用前者。

30 秒实验

  1. 把 format="rgb24" 改成 "gray",落成的 png 变成灰度图,看字节数。
  2. 不缩尺寸只转格式,与两样都做比耗时(提示:转格式比缩放贵)。
  3. 把插值换成 POINT,把图放大到 640×480,看锯齿。

常见误区

  1. 以为 yuv420p 能直接存 png。 图片格式要 RGB。
  2. 为省事调两次 reformat。 一次能做完,两次多一次拷贝。
  3. 实时场景用 LANCZOS。 它慢十倍,一帧就吃掉一半预算。
自测三问(点开对答案) 1. 为什么解码出来的帧不能直接存 png? —— 解码器给 yuv420p,图片格式要 RGB。 2. 七种插值里实时场合该用哪个? —— 双线性(BILINEAR 或 FAST_BILINEAR)。 3. 「只缩放」与「只转格式」哪个更贵? —— 实测缩放更贵(5.91 对 3.20 毫秒),因为它要重采样每一个输出像素。

相关笔记