🎬 第七章 FFmpeg命令行实现音视频特效及复杂滤镜应用
📄 一句话概括

crop 的参数就六个:w、h、x、y、keep_aspect、exact。 尺寸一律往下取偶,只有「超过原图」这一种情况会报错。

📝 本节和前面几节的关系

上一节用实测看了行为,这一节把参数表与表达式补齐。 表达式是后面所有复杂滤镜的基础,iw、ow 这套记号会一直用。

一、全部参数

六个参数,常用的只有前四个:

参数 含义 默认值


w / out_w 裁出来的宽 原宽 h / out_h 裁出来的高 原高 x 左上角横坐标 居中 y 左上角纵坐标 居中 keep_aspect 保持宽高比 0 exact 允许非偶数尺寸 0

w 与 out_w 是一回事,写哪个都行;位置参数只能按 w、h、x、y 的顺序写。 exact=1 理论上能保住奇数尺寸,但后面接编码器照样会把奇数拒掉,所以很少用。

二、尺寸一律取偶

五种尺寸试一遍,看谁被改小、谁会报错:

要的尺寸 实得尺寸 结果


80×45 80×44 被改小 81×45 80×44 被改小 159×89 158×88 被改小 160×90 160×90 原样 162×90 - 报错 returned 22

奇数全被抹成偶数:45 变 44、89 变 88、81 变 80。 超过原图尺寸的 162×90 直接报 returned 22,它是唯一会报错的情况。

注意 81 变 80、159 变 158,都是「往下减」而不是「往上加」。 这意味着你要的 159 宽永远拿不到 160,只能自己往大里写。

三、表达式算尺寸

crop 里可以直接写算式,用 iw、ih、ow、oh 这些变量:

写法 含义 实得


iw/2:ih/2 一半 80×44 iw/4:ih/4 四分之一 40×22 iw-20:ih-10 四周各留一点 140×80 80:44:(iw-ow)/2:(ih-oh)/2 标准居中 80×44

iw/ih 是输入宽高,ow/oh 是输出宽高。(iw-ow)/2 就是标准居中写法。 这些表达式都在滤镜内部算,不是 shell 算好的数,所以能跟着输入尺寸自动适应。

这跟 -vf 里直接写死数字是两回事:写死 80,换个分辨率的输入就错; 写 iw/2,输入怎么变它都取一半。做通用脚本时一律用表达式。

四、五个坑

把这一节的坑一次列全:

坑 表现


宽高超原图 报 returned 22 坐标越界 静默夹住,不报错 写奇数尺寸 静默变偶数 坐标写奇数 静默变偶数 误以为 xy 是中心 画面偏一大截

五个里有四个不报错。crop 属于「错了也照做」的那一类滤镜, 所以裁完一定要抽一帧看一眼,别只看命令有没有报错。

最省事的自检办法:把裁出来的第一帧存成 png,对着眼睛看一下。 一张图的代价,比查半天「为什么人脸少半张」便宜太多。

五、动手 Demo:五种尺寸、四种表达式

下面这段代码先试五种尺寸,被改小的、报错的都会打出来。 再试四种表达式写法,验证 iw、ow 这套记号怎么算。

import av
from fractions import Fraction

TB = Fraction(1, 25)
c0 = av.open(u"s7.mp4")
fr = next(c0.decode(video=0))
base = fr.to_ndarray(format=u"yuv420p")
full = fr.to_ndarray(format=u"rgb24")
W, H = fr.width, fr.height
c0.close()

def size(args):
    g = av.filter.Graph()
    b = g.add_buffer(width=W, height=H, format=u"yuv420p", time_base=TB)
    f = g.add(u"crop", args)
    sk = g.add(u"buffersink")
    b.link_to(f)
    f.link_to(sk)
    g.configure()
    vf = av.VideoFrame.from_ndarray(base, format=u"yuv420p")
    vf.pts = 0
    vf.time_base = TB
    b.push(vf)
    o = sk.pull().to_ndarray(format=u"rgb24")
    return o.shape[1], o.shape[0]

print(u"--- 尺寸取偶")
for a in (u"80:45", u"81:45", u"159:89", u"160:90", u"162:90"):
    try:
        w, h = size(a)
        print(u"%-10s -> %d×%d" % (a, w, h))
    except Exception:
        print(u"%-10s -> 报错 returned 22" % a)

print(u"--- 表达式")
for a in (u"iw/2:ih/2", u"iw/4:ih/4", u"iw-20:ih-10", u"80:44:(iw-ow)/2:(ih-oh)/2"):
    w, h = size(a)
    print(u"%-26s -> %d×%d" % (a, w, h))

实跑输出:

--------------------------------------------------------------------
7.4.2 crop 参数说明
--------------------------------------------------------------------

**① 全部参数就这几个**

  参数            含义               默认值
  ---------------------------------------------
  w / out_w       裁出来的宽         原宽
  h / out_h       裁出来的高         原高
  x               左上角横坐标       居中
  y               左上角纵坐标       居中
  keep_aspect     保持宽高比         0
  exact           允许非偶数尺寸     0

**② 尺寸一律往下取偶数**

  要的尺寸     实得尺寸     结果
  ----------------------------------------------
  80×45        80×44        被改小
  81×45        80×44        被改小
  159×89       158×88       被改小
  160×90       160×90       原样
  162×90       -            报错 returned 22

**奇数全被抹成偶数:45 变 44、89 变 88、81 变 80。**
**超过原图尺寸的 162×90 直接报 `returned 22`,它是唯一会报错的情况。**

**③ 用表达式算尺寸**

  写法                          含义             实得
  ---------------------------------------------------------
  iw/2:ih/2                     一半             80×44
  iw/4:ih/4                     四分之一         40×22
  iw-20:ih-10                   四周各留一点     140×80
  80:44:(iw-ow)/2:(ih-oh)/2     居中             80×44

**`iw`/`ih` 是输入宽高,`ow`/`oh` 是输出宽高。`(iw-ow)/2` 就是标准居中写法。**
**这些表达式都在滤镜内部算,不是 shell 算好的数,所以能跟着输入尺寸自动适应。**

**④ 五个容易踩的坑**

  坑                   表现
  -----------------------------------------
  宽高超原图           报 returned 22
  坐标越界             静默夹住,不报错
  写奇数尺寸           静默变偶数
  坐标写奇数           静默变偶数
  误以为 xy 是中心     画面偏一大截

**五个里有四个不报错。crop 属于「错了也照做」的那一类滤镜,**
**所以裁完一定要抽一帧看一眼,别只看命令有没有报错。**

第二段五行完全对上本节那张表:前三行被改小、第四行原样、第五行报错。 注意第五行是被 try 兜住的,程序没有中断。

第三段四行是表达式,iw-20:ih-10 出来 140×80, 说明算术表达式在这里是当数用的,不是当字符串拼的。

代码里 size() 每次都新建一张图。图不能复用, 因为 configure 之后参数就定死了,换参数只能重建。

30 秒实验

  1. 试 exact:给 80:45 加 :exact=1,看能不能真的拿到 45。
  2. 试 keep_aspect:写 crop=80:80:0:0:keep_aspect=1,看尺寸变成多少。
  3. 试表达式:写 iw/3:ih/3,看除不尽时落在哪个偶数上。

常见误区

  1. 以为 exact=1 就能出奇数。后面接编码器照样拒。
  2. 把 159 当成能拿到 160。取偶是往下减,不是往上加。
  3. 表达式里写 // 或 %。crop 只认 trunc、floor 那套函数。
自测三问(点开对答案) 1. crop 里 `ow` 与 `oh` 指什么? —— 输出(裁剪后)的宽与高。`(iw-ow)/2` 就是居中用的表达式。 2. 要 159×89,实际能拿到多少? —— 158×88,两个方向都往下取偶。 3. 参数里哪一个需要显式打开才生效? —— `keep_aspect` 与 `exact`,默认都是 0(关)。

相关笔记