四角定位靠 W-w-8 这类表达式,换分辨率不用改数字。 但 H-h-8 算出来是奇数,落地会被抹掉一行,底边反而比上边窄。
7.9.2 量准了坐标语义,7.9.3 定好了尺寸,这一节把位置做成可复用的表达式。 把主画面也缩成格子,就是下一节 7.10 的九宫格。
一、四角定位
四种角落写法,一律用变量拼,不写死像素:
角落 写法 实际落点
左上 8:8 (8, 8) 右上 W-w-8:8 (72, 8) 左下 8:H-h-8 (8, 36) 右下 W-w-8:H-h-8 (72, 36)
左右是对称的:8 与 72,两边都留 8 列。 上下看着不对称——上是 8,下只剩 9 行。
原因是 H-h-8 算出 37,奇数被抹成 36,底边就多留了一行。
想上下真的一样,要么把小画面高度取成奇数,要么干脆直接写偶数坐标。
这也是为什么工程上宁可直接写 36 而不写 H-h-8:
表达式看着优雅,但一遇到奇数就得回头查表。
二、居中一行搞定
居中不用算,一行表达式:
(main_w-overlay_w)/2:(main_h-overlay_h)/2
实测落在 (40, 22)。手算:(160-80)/2=40,(90-45)/2=22.5,
直接把小数抹掉就是 22,跟量出来的完全一致。
这里又出现「往下取」——不是四舍五入。 要是四舍五入,22.5 会变 23,但实测是 22,说明它只做截断。
main_w 与 overlay_w 是 W 与 w 的长名字,两套都认。
表达式里还能写中文以外的任何算术:+ - * /、括号、min/max 都行。
三、让它动起来
x 里能写帧号 n 与时间 t,三种典型写法:
写法 效果
10+5n:10 每帧右移 5 像素 x='10+20t':y=10 每秒右移 20 像素 'if(lt(t,1),0,80)':10 第一秒在左,之后跳到 80
n 是帧序号,t 是秒。写 t 的写法跨帧率更稳:
换个帧率,n 的增长速度就变了,t 不会。
含逗号的表达式必须整体用单引号包起来,否则逗号会被当成参数分隔符。
if(lt(t,1),0,80) 这种不带引号写,滤镜会以为你多传了参数。
四、表达式里能用哪些变量
常用的就这几个:
变量 含义
W / main_w 主画面宽 w / overlay_w 小画面宽 n 帧序号 t 时间(秒)
H、h、main_h、overlay_h 同理,管高。
大写是主画面、小写是小画面,这个区别必须记牢。
写反了位置就整个错位,而且一声不吭——这是 overlay 最常见的坑。
W-w 写成 w-W 会得到负数,画面直接被推到左边看不见。
五、下一步:多宫格
画中画是「一主一小」。把主画面也缩成小格,就是多宫格。
下一节 7.10 会用 xstack 一次排出四格、九格。
xstack 的思路跟这里完全一样:每个格子的坐标都用 w、h 拼出来。
只不过它一次接收九路输入,比叠 8 次 overlay 干净得多,也不会一层层变糊。
所以这一节的表达式功夫是下一节的地基,别急着跳到 xstack。
六、动手 Demo:四角、居中、边距实测、负边距
下面这段代码把四角与居中的落点都量一遍,再把四条边距算出来, 最后试一次负的边距(小画面整体出界)。
import av
import numpy as np
from fractions import Fraction
TB = Fraction(1, 25)
c0 = av.open(u"s7.mp4")
base = next(c0.decode(video=0)).to_ndarray(format=u"yuv420p")
c0.close()
def pip(pos, w=80, h=45):
g = av.filter.Graph()
b = g.add_buffer(width=160, height=90, format=u"yuv420p", time_base=TB)
cl = g.add(u"color", u"c=blue:s=%dx%d:r=25" % (w, h))
ov = g.add(u"overlay", pos)
sk = g.add(u"buffersink")
b.link_to(ov, 0, 0)
cl.link_to(ov, 0, 1)
ov.link_to(sk)
g.configure()
vf = av.VideoFrame.from_ndarray(base, format=u"yuv420p")
vf.pts = 0
vf.time_base = TB
b.push(vf)
return sk.pull().to_ndarray(format=u"rgb24")
def block(a):
m = (a[:, :, 2] > 180) & (a[:, :, 0] < 70) & (a[:, :, 1] < 70)
cy = m.sum(axis=1)
cx = m.sum(axis=0)
ys = np.where(cy >= cy.max() * 0.5)[0]
xs = np.where(cx >= cx.max() * 0.5)[0]
return (int(xs.min()), int(ys.min()))
print(u"--- 四角与居中")
for tag, pos in ((u"左上", u"8:8"), (u"右上", u"W-w-8:8"),
(u"左下", u"8:H-h-8"), (u"右下", u"W-w-8:H-h-8"),
(u"居中", u"(main_w-overlay_w)/2:(main_h-overlay_h)/2")):
print(u" %-4s %-40s %s" % (tag, pos, block(pip(pos))))
print(u"--- 右下角的边距")
x0, y0 = block(pip(u"W-w-8:H-h-8"))
print(u" 右 %d 列 下 %d 行(写法里写的是 8)"
% (160 - 80 - x0, 90 - 45 - y0))
print(u"--- 负边距也能写")
print(u" -20:8 %s" % (block(pip(u"-20:8")),))
实跑输出:
--------------------------------------------------------------------
7.9.4 画中画的灵活位置
--------------------------------------------------------------------
**① 四角定位用表达式,不写死数字**
**四种角落写法,换分辨率也不用改:**
角落 写法 实际落点
-------------------------------------
左上 8:8 (8, 8)
右上 W-w-8:8 (72, 8)
左下 8:H-h-8 (8, 36)
右下 W-w-8:H-h-8 (72, 36)
**左右两行是对称的:`8` 与 `72`,两边各留 `8` 像素。**
**上下两行看着不对称——`8` 与 `36`,底边只剩 `9` 像素。**
**原因是 `H-h-8` 算出来是 `37`,写进去是奇数,被抹成 `36`。**
**想上下真的一样,得把小画面的高度取成奇数,或者干脆直接写偶数坐标。**
**② 居中也是一行表达式**
`(main_w-overlay_w)/2:(main_h-overlay_h)/2`
**实测落在 `(40, 22)`。**
**手算:`(160-80)/2=40`、`(90-45)/2=22.5`,取整 22,正好对上。**
**这里又出现「往下取」——不是四舍五入,是直接抹掉小数。**
**③ 让它动起来**
**x 里能写帧号 `n` 与时间 `t`:**
写法 效果
-----------------------------------------------------
10+5*n:10 每帧右移 5 像素
x='10+20*t':y=10 每秒右移 20 像素
'if(lt(t,1),0,80)':10 第一秒在左,之后跳到 80
**`n` 是帧序号,`t` 是秒。写 `t` 的写法跨帧率更稳:**
**换个帧率,`n` 的增长速度就变了,`t` 不会。**
**④ 表达式里能用哪些变量**
变量 含义
--------------------------------
W / main_w 主画面宽
w / overlay_w 小画面宽
n 帧序号
t 时间(秒)
**记住 `W` 与 `w` 的大小写区别:大写是主画面,小写是小画面。**
**写反了位置就整个错位,而且一声不吭——这是 overlay 最常见的坑。**
**⑤ 下一步:多宫格**
**画中画是「一主一小」,把主画面也缩成小格,就是九宫格。**
**7.10 会用 xstack 一次排出四格、九格,比叠 8 次 overlay 干净得多。**
第一段五行跟正文那张表完全一致:(8, 8)、(72, 8)、(8, 36)、(72, 36)、(40, 22)。
第二段把边距算出来了:右边 8 列、下边 9 行。
写法里 x 与 y 都减了 8,右边界刚好 8,下边界多出一行——
H-h-8 算成 37 被抹到 36 的结果。
第三段 -20:8 量出来的 x 是 0,因为左边 20 列已经被切掉了。
overlay 照写的坐标摆,看不见的部分就是不画。
注意这一整段的前提:x 也得是偶数。W-w-8 算出 72 刚好是偶数,
要是写的边距是 7,右边就变成 9 了。
30 秒实验
- 改边距:把四角里的 8 全改成 16,看边距是不是真的变成 16。
- 试奇数尺寸:把
80x45改成80x44,看H-h-8还会不会被抹。 - 做动效:把 x 写成
-80+2*n,看小画面是不是从左边滑进来。
常见误区
- 以为
H-h-8会给出对称边距。算出奇数就被抹掉一行。 - 把
W与w写反。位置整个错位,而且不报错。 - 含逗号的表达式不加引号。逗号会被当参数分隔符。
自测三问(点开对答案)
1. `H-h-8` 算出 37,实际落在第几行? —— 第 36 行,奇数被向下抹成偶数。 2. `(main_h-overlay_h)/2` 算出 22.5,落地是几? —— 22,只截断不进位。 3. 为什么建议四角直接写偶数坐标? —— 表达式算出奇数会被抹掉一行,边距就不对称了。- ☐ 7.9 完,下一节 7.10.1 九宫格简介