给一个框,怎么把画面塞进去又不压扁:disable 硬拉、decrease 塞进、increase 盖住。 decrease 留下的空白靠 pad 补。
前三节都在算「缩放成多大」。这一节换个角度:先给一个框,再看画面怎么落进去。 平台要求固定尺寸时,用的就是这个参数。
一、一个框,三种塞法
命令行长这样:
scale=640:480:force_original_aspect_ratio=decrease
看到 force_original_aspect_ratio 这么长的名字,其实只问一件事:
框的比例和画面的比例不一样时,听谁的。
三个取值三条路:硬拉、缩进去、放大盖住。
二、三个取值的实测
同一段 160×90 的素材,塞进 640×480 的框:
取值 含义 实际输出
disable 默认,直接拉扯到目标尺寸 640×480 decrease 缩到能塞进框内的最大尺寸 640×360 increase 放大到能盖住框的最小尺寸 853×480
disable 就是不用这个参数,画面被硬拉到 640×480,比例全乱。 decrease 保住 16:9,缩成 640×360:宽顶满,高只用了 360。
increase 也保住 16:9,放大到 853×480:高顶满,宽超出 213 像素。 853 这个数字有点怪——它是 480 乘 16/9 算出来的,除不尽。
注意 853 是奇数。decrease 与 increase 都不管奇偶,
要偶数得另外加 force_divisible_by=2,7.2.8 会讲。
三、换个框再看一次
同样用 decrease,框的宽高比变了,谁顶满也跟着变:
目标框 decrease 输出 谁顶满
640:480 640×360 宽顶满 80:80 80×45 宽顶满 200:100 178×100 高顶满 100:200 100×56 宽顶满
四行里总有一个方向正好等于目标值——这就是 decrease 的定义: 在这个框里能放多大就放多大,不越界、不改比例。
第三行是唯一「高顶满」的:框又宽又扁,画面得先满足高度。 其余三行都是框比画面更「高」,所以宽度先到顶。
四、留下的空白要靠 pad 补
640×360 塞进 640×480 的框,上下各空 60 像素。 要输出固定尺寸的文件,就得再接一个 pad:
scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2
这一对组合是「等比缩放加补黑边」的标准写法。 少了 pad,输出尺寸就不是 640×480,平台那边直接不收。
顺序也不能反:先 pad 再 scale,补出来的黑边会跟着一起被缩, 黑边的宽度就不对了。
五、increase 为什么反而少用
increase 会让画面超出框外,多出来的部分通常要靠 crop 再裁掉。 两步都做,等于先放大再丢像素,画质白损一次。
除非你要的就是「铺满不留黑边」,否则 decrease 加 pad 更划算。 视频墙、封面图这类「宁可留黑边也不能裁掉内容」的场合,一律 decrease。
一句话记法:decrease 是「装进去」,increase 是「盖上去」。 装进去不会丢内容,盖上去一定丢。
六、动手 Demo:三种塞法,三种尺寸
下面这段代码把三种取值各跑一次,再换四个框看谁顶满。 最后把 decrease 与 pad 串起来,量补边前后差多少。
import av
from fractions import Fraction
TB = Fraction(1, 25)
c0 = av.open(u"s7.mp4")
base = next(c0.decode(video=0)).to_ndarray()
c0.close()
def size(expr):
g = av.filter.Graph()
p = g.add_buffer(width=160, height=90, format=u"yuv420p", time_base=TB)
sc = g.add(u"scale", expr)
sk = g.add(u"buffersink")
p.link_to(sc)
sc.link_to(sk)
g.configure()
vf = av.VideoFrame.from_ndarray(base, format=u"yuv420p")
vf.pts = 0
vf.time_base = TB
p.push(vf)
o = sk.pull().to_ndarray(format=u"rgb24")
return o.shape[1], o.shape[0]
for mode in (u"disable", u"decrease", u"increase"):
e = u"640:480:force_original_aspect_ratio=" + mode
w, h = size(e)
print(u"%-9s -> %d×%d" % (mode, w, h))
print(u"--- 换四个框")
for tgt in (u"640:480", u"80:80", u"200:100", u"100:200"):
w, h = size(tgt + u":force_original_aspect_ratio=decrease")
tw, th = [int(x) for x in tgt.split(u":")]
tag = u"宽顶满" if w == tw else (u"高顶满" if h == th else u"都没满")
print(u"%-9s -> %d×%d %s" % (tgt, w, h, tag))
print(u"--- decrease 之后补边")
g = av.filter.Graph()
p = g.add_buffer(width=160, height=90, format=u"yuv420p", time_base=TB)
sc = g.add(u"scale", u"200:130:force_original_aspect_ratio=decrease")
pd = g.add(u"pad", u"200:130:(ow-iw)/2:(oh-ih)/2:black")
sk = g.add(u"buffersink")
p.link_to(sc)
sc.link_to(pd)
pd.link_to(sk)
g.configure()
vf = av.VideoFrame.from_ndarray(base, format=u"yuv420p")
vf.pts = 0
vf.time_base = TB
p.push(vf)
o = sk.pull().to_ndarray(format=u"rgb24")
print(u"scale 之后 %s,pad 之后 %d×%d"
% (u"200×112", o.shape[1], o.shape[0]))
实跑输出:
--------------------------------------------------------------------
7.2.5 使用 force_original_aspect_ratio 进行缩放
--------------------------------------------------------------------
**① 这个参数解决的问题:给了一个框,怎么把画面塞进去又不压扁。**
`scale=640:480:force_original_aspect_ratio=decrease`
**② 三个取值的实测**
取值 含义 640×480 的实际输出
----------------------------------------------------------------
disable 默认,直接拉扯到目标尺寸 640×480
decrease 缩到能塞进框内的最大尺寸 640×360
increase 放大到能盖住框的最小尺寸 853×480
**disable 就是不用这个参数,画面被硬拉到 640×480,比例全乱。**
**decrease 保住 16:9,缩成 640×360——宽顶满,高只用了 360。**
**increase 也保住 16:9,放大到 853×480——高顶满,宽溢出了 213 像素。**
**③ 换个目标框再看一次**
目标框 decrease 输出 是宽顶满还是高顶满
----------------------------------------------------
640:480 640×360 宽顶满
80:80 80×45 宽顶满
200:100 178×100 高顶满
100:200 100×56 宽顶满
**四行里总有一个方向正好等于目标值——这就是 decrease 的定义:**
**在这个框里能放多大就放多大,而且不越界、不改比例。**
**④ decrease 留下的空白要靠 pad 补**
**640×360 塞进 640×480 的框,上下各空 60 像素。**
**要输出固定尺寸的文件(比如平台要求 640×480),就得再接一个 pad:**
`scale=640:480:force_original_aspect_ratio=decrease,pad=640:480:(ow-iw)/2:(oh-ih)/2`
**这一对组合是「等比缩放 + 补黑边」的标准写法,7.2.6 会把它跑一遍。**
**⑤ increase 为什么反而少用**
**increase 会让画面超出框外,多出来的部分通常要靠 crop 再裁掉。**
**两步都做,等于先放大再丢像素,画质白损一次。**
**除非你要的是「铺满不留黑边」,否则 decrease 加 pad 更划算。**
前三行跟本节那张表逐条对上:disable 硬拉、decrease 缩进、increase 盖住。 接着四行里只有 200:100 是高顶满,其余都是宽顶满。
最后一段里「scale 之后 200×112」是写死的字符串,因为它只是中间量。 要量它也简单:把 pad 拿掉,单独跑一次 scale 就行。
注意 increase 出来的 853×480,那个 853 是除不尽的 853.33 取整。 所以等比缩放永远会有半个像素的误差,这点误差在 pad 与 crop 里会被放大。
30 秒实验
- 换框:把
640:480改成480:640,看 decrease 变成多少。 - 加整除:给 increase 那一行加
force_divisible_by=2,看 853 变成几。 - 去掉 pad:把 pad 那段删掉,量输出尺寸是不是 200×112。
常见误区
- 以为 decrease 会补边。它只缩,空白得自己 pad。
- 用 increase 又 crop。先放大再裁,白损一次画质。
- 把 pad 写在 scale 前面。黑边会跟着被缩,宽度就不对了。
自测三问(点开对答案)
1. decrease 与 increase 的区别是什么? —— decrease 缩到能塞进框内,increase 放大到能盖住框。 2. decrease 之后为什么还要 pad? —— decrease 只保证不越界,输出尺寸比框小,pad 才能补到刚好。 3. increase 出来的 853 为什么是奇数? —— 480 乘 16/9 得 853.33,取整就是 853,它不管奇偶。- ☐ (本节完,下一节 7.2.6 使用 pad 选项填充黑边)