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 秒实验
- 试 exact:给
80:45加:exact=1,看能不能真的拿到 45。 - 试 keep_aspect:写
crop=80:80:0:0:keep_aspect=1,看尺寸变成多少。 - 试表达式:写
iw/3:ih/3,看除不尽时落在哪个偶数上。
常见误区
- 以为 exact=1 就能出奇数。后面接编码器照样拒。
- 把 159 当成能拿到 160。取偶是往下减,不是往上加。
- 表达式里写
//或%。crop 只认 trunc、floor 那套函数。
自测三问(点开对答案)
1. crop 里 `ow` 与 `oh` 指什么? —— 输出(裁剪后)的宽与高。`(iw-ow)/2` 就是居中用的表达式。 2. 要 159×89,实际能拿到多少? —— 158×88,两个方向都往下取偶。 3. 参数里哪一个需要显式打开才生效? —— `keep_aspect` 与 `exact`,默认都是 0(关)。- ☐ (本节完,下一节 7.4.3 复杂滤镜 nullsrc、crop、overlay 结合使用)