geq 的表达式能用坐标、帧号、时间,还有 abs/min/max/sin/if 这些函数。 浮雕那类「有正有负」的运算,必须加 128 的偏置。
上一节讲了参数与两个坑,这一节把官网那张变量表、函数表翻成能用的东西。 六个例子都能跑通,数字都是量出来的。
一、变量表
官网文档列了一整页,常用的就这几个:
变量 含义
X / Y 当前像素的横、纵坐标 W / H 画面的宽、高 N 当前是第几帧,从 0 开始 T 当前时间(秒) SW / SH 表达式的求值步长,一般不用 p(x,y) 取某坐标的像素值(当前平面) lum/cb/cr(x,y) 取某坐标指定平面的值
X 与 Y 大写,N 是帧号、T 是秒——这套命名跟 crop、overlay 的表达式统一。
所以写过 crop 表达式的人,geq 上手很快。
SW、SH 是「求值步长」,意思是「隔几个像素算一次」。
默认 1,也就是每个像素都算;调大一点算得快,但结果会出现块状。
二、函数表
这几个函数凑起来,几乎能做任何逐像素效果:
函数 作用
abs(x) 绝对值 min / max 取小、取大 sin / cos 三角函数,用来做周期变化 if(cond,a,b) 三目:条件成立取 a,否则取 b lt(a,b) / gt(a,b) 小于、大于,返回真假
再复杂的就写不下去了——geq 的表达式不是编程语言,没有循环、没有变量赋值。 想做复杂的事,只能把公式堆得更长,或者拆成多个 geq 串起来。
if 和 lt 一起用就能做「局部处理」:
if(lt(X,80),p(X,Y),255-p(X,Y)) 的意思是「左半边原样、右半边反色」。
实测均值 143.9,介于原样 141.9 与全反色 114.8 之间,符合「只反一半」的预期。
三、六个能跑的表达式
把官网的例子实际跑一遍,看均值落在哪:
效果 表达式 均值
反色 lum='255-p(X,Y)' 114.8 压暗 lum='p(X,Y)0.6' 59.4 轮廓 lum='abs(p(X,Y)-p(X+1,Y))' 50.3 浮雕 lum='p(X,Y)-p(X-1,Y-1)+128':cb='128':cr='128' 130.6 左半反色 lum='if(lt(X,80),p(X,Y),255-p(X,Y))' 143.9 横向渐变 lum='p(X,Y)(X/W)' 65.7
原样是 141.9。反色落到 114.8(差不多是 255 减原值),压暗落到 59.4, 轮廓落到 50.3(相邻差本来就小),浮雕回到 130.6 附近。
浮雕那一条把 cb 与 cr 都钉成 128,画面就是纯灰的——
色度清零等于去色,跟 7.12 的 hue=s=0 一个道理。
横向渐变那条 p(X,Y)*(X/W) 是「左边暗、右边亮」:
X/W 从 0 走到接近 1,相当于给画面乘一个从左到右的斜坡。
四、浮雕为什么以 128 为底
p(X,Y)-p(X-1,Y-1) 是两个相邻像素的差,有正有负。
直接存会一半变黑(负数回绕成 0 附近),画面就废了。
加 128 就是把这个差抬到中间:差为 0 的地方是灰的, 差大的地方偏亮或偏暗,看起来像浮雕的凸起与凹陷。
所以「+128」不是装饰,是必需的偏置。
同理做边缘检测时,加 128 得到「灰底白线」,加 abs 得到「黑底白线」。
实测 abs 那一版均值只有 50.3——负的一半被折成正的,整体偏暗。
两种写法各有用途:要叠在原画面上做「线条特效」用 abs(黑底好叠),
要单独看边缘图用「+128」(灰底更接近人眼习惯)。
五、动手 Demo:六个表达式、三个极值
下面这段代码把六个表达式各跑一遍,量均值与极值。 极值那一列能看出像素有没有被压到 0 或顶到 255。
import av
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 geq(expr):
g = av.filter.Graph()
b = g.add_buffer(width=160, height=90, format=u"yuv420p", time_base=TB)
f = g.add(u"geq", expr)
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)
return sk.pull().to_ndarray(format=u"rgb24")
CASES = ((u"反色", u"lum='255-p(X,Y)'"),
(u"压暗", u"lum='p(X,Y)*0.6'"),
(u"轮廓", u"lum='abs(p(X,Y)-p(X+1,Y))'"),
(u"浮雕", u"lum='p(X,Y)-p(X-1,Y-1)+128':cb='128':cr='128'"),
(u"左半反色", u"lum='if(lt(X,80),p(X,Y),255-p(X,Y))'"),
(u"横向渐变", u"lum='p(X,Y)*(X/W)'"))
print(u"--- 原样 %.1f" % geq(u"lum='p(X,Y)'").mean())
for tag, e in CASES:
o = geq(e)
print(u" %-10s 均值 %6.1f 最小 %3d 最大 %3d"
% (tag, o.mean(), o.min(), o.max()))
实跑输出:
--------------------------------------------------------------------
7.15.2 geq 滤镜的官网介绍
--------------------------------------------------------------------
**① 表达式里能用的变量**
**官网文档列了一整页,常用的就这几个:**
变量 含义
---------------------------------------------------
X / Y 当前像素的横、纵坐标
W / H 画面的宽、高
N 当前是第几帧,从 0 开始
T 当前时间(秒)
SW / SH 表达式的求值步长,一般不用
p(x,y) 取某坐标的像素值(当前平面)
lum/cb/cr(x,y) 取某坐标指定平面的值
**`X` 与 `Y` 大写,`N` 是帧号、`T` 是秒——这套命名跟 crop、overlay 的表达式是统一的。**
**所以写过 crop 的表达式,geq 上手很快。**
**② 常用函数**
函数 作用
------------------------------------------------------
abs(x) 绝对值
min / max 取小、取大
sin / cos 三角函数,用来做周期变化
if(cond,a,b) 三目:条件成立取 a,否则取 b
lt(a,b) / gt(a,b) 小于、大于,返回真假
**这几个函数凑起来,几乎能做任何逐像素效果:反色、浮雕、渐变、闪烁、局部处理。**
**再复杂的就写不下去了——geq 的表达式不是编程语言,没有循环。**
**③ 六个能跑的表达式,实测**
效果 表达式 均值
------------------------------------------------------------------------
反色 lum='255-p(X,Y)' 114.8
压暗 lum='p(X,Y)*0.6' 59.4
轮廓 lum='abs(p(X,Y)-p(X+1,Y))' 50.3
浮雕 lum='p(X,Y)-p(X-1,Y-1)+128':cb='128':cr='128' 130.6
左半反色 lum='if(lt(X,80),p(X,Y),255-p(X,Y))' 143.9
横向渐变 lum='p(X,Y)*(X/W)' 65.7
**原样是 141.9。反色落到 114.8(差不多是 255 减原值),压暗落到 59.4,**
**轮廓落到 50.3(相邻差本来就小),浮雕回到 130.6 附近(以 128 为底)。**
**浮雕那一条把 `cb` 与 `cr` 都钉成 128,画面就是纯灰的——**
**色度清零等于去色,这跟 7.12 的 `hue=s=0` 是一个道理。**
**④ 浮雕为什么以 128 为底**
**`p(X,Y)-p(X-1,Y-1)` 是两个相邻像素的差,有正有负。**
**直接存会一半变黑(负数回绕),加 128 就是把它抬到中间:**
**差为 0 的地方是灰的,差大的地方偏亮或偏暗,看起来像浮雕。**
**所以「+128」不是装饰,是必需的偏置。**
**同理,做「边缘检测」时也常加 128,或者加完再用 `abs` 折回来——**
**加 `abs` 的那一版(第三种写法)均值只有 50.3,因为负的一半被折成正值、整体偏暗。**
六行数跟本节那张表完全对上:反色 114.8、压暗 59.4、轮廓 50.3、浮雕 130.6。
极值那一列全是 0 到 255——说明这六种写法都没溢出。 要是某种写法出现「最大 255 且均值很低」,那多半是回绕了。
浮雕 那一行是唯一同时改了三个平面的:lum 算差值、cb/cr 固定 128。
少写 cb/cr,出来的浮雕会带颜色,不好看。
30 秒实验
- 改阈值:把
lt(X,80)改成lt(X,40),看反色的区域缩到多大。 - 用 Y 做判断:写
if(lt(Y,45),...),做成上下分屏的效果。 - 按帧变:把
X/W换成(N%25)/25.0,看是不是每 25 帧循环一次渐变。
常见误区
- 浮雕不加 128。负的差会回绕成白点,画面一半是刺眼的噪声。
- 拿
abs版当「更好的边缘」。它是黑底白线,叠在原画面上会发黑。 - 指望 geq 写复杂逻辑。没有循环没有变量,堆长了就没人看得懂。
自测三问(点开对答案)
1. geq 里「第几帧」用哪个变量? —— `N`。时间用 `T`(秒)。 2. 浮雕为什么加 128? —— 相邻差有正有负,加 128 把结果抬进 0~255 的合法范围。 3. `abs` 版轮廓与「+128」版有什么区别? —— `abs` 是黑底白线,+128 是灰底、有亮有暗。- ☐ 7.15 完,下一节 7.16.1 生成静音音频