hflip 左右翻、vflip 上下翻、transpose 转 90 度、rotate 转任意角度。 前三个不改尺寸,rotate 也不改尺寸但会留下补色的死角。
上一节改了时间方向,这一节改空间方向。 vflip 是 7.8 倒影的必备零件,transpose 是修竖拍视频的常用招。
一、四个滤镜,各管一件事
先认清楚四个名字:
滤镜 干什么 尺寸变不变
hflip 左右翻(照镜子) 不变 vflip 上下翻(水面倒影) 不变 transpose 转 90 度 宽高互换 rotate 任意角度 不变
前两个不需要参数,写上去就生效。后两个要写角度或者方向。 它们都是「几何变换」,跟 scale 那种「重采样改大小」不是一类。
二、hflip 真的左右翻了吗
只看画面容易骗自己,看边缘三列的均值最直接:
位置 原图三列均值 翻后三列均值
靠左三列 109.8 191.2 靠右三列 191.2 109.8
左边三列 109.8 变成 191.2,右边三列 191.2 变成 109.8——两边的数换了个位置。 这就是左右翻转的证据:不是「看起来像」,是左边缘的内容真的跑到了右边缘。
素材里有一条随帧右移的红色竖条,所以左右边缘亮度差得明显。 换成左右对称的图,这个办法就失效了,得改用角点比对。
三、transpose 的四种取值
四个取值,两两成对:
取值 效果 输出尺寸
clock 顺时针 90 度 90×160 cclock 逆时针 90 度 90×160 clock_flip 顺时针再左右翻 90×160 cclock_flip 逆时针再左右翻 90×160
四个都是 90×160,因为转 90 度必然宽高互换。
只转 90 度时 transpose 比 rotate 好:它不插值,像素一个不丢。
clock_flip 与 cclock_flip 是给「转完发现是镜像的」那种场景准备的。
比如手机前置摄像头自拍,转正之后还得再翻一次才对。
四、rotate 任意角度,但会留下死角
rotate 的参数是一个角度表达式,可以写度数也可以写弧度:
写法 输出尺寸 左上角像素
PI/2 160×90 [0, 0, 0] PI/4 160×90 [0, 0, 0] 45*PI/180 160×90 [0, 0, 0] PI/2:fillcolor=red 160×90 [253, 0, 0]
注意 rotate 的输出尺寸不变,永远 160×90。
转 45 度之后四个角空出来了,默认补黑,所以左上角是 0;
写 fillcolor=red 就补成红的,左上角变成 253。
要「转完还能看全」,得先算旋转后的外接矩形,再用 scale 与 pad 拼; 那套算法比较绕,日常能用 transpose 解决的就不上 rotate。
还有一个细节:PI/2 这种写法里 PI 是内置常量,不区分大小写。
写 90 不行——rotate 的角度单位默认是弧度,90 弧度差不多转了 14 圈。
五、常见组合
四个滤镜的典型用法压成一张表:
想要的效果 链
照镜子 hflip 水面倒影 vflip 手机竖拍转正 transpose=clock 轻微歪斜校正 rotate=-2*PI/180
竖拍转正那个最常用:手机拍的视频常带旋转元数据,播放器认, 但有些老播放器不认,就得真转一遍再存。
歪斜校正要放在裁剪之前:先转正再裁边,否则裁完再转又会出现新的黑角。
六、动手 Demo:边缘均值、四种 transpose、三档 rotate
下面这段代码先用边缘三列均值证明 hflip 真的左右翻, 再把 transpose 四种取值与 rotate 三种写法各跑一次。
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")
rgb = fr.to_ndarray(format=u"rgb24")
W, H = fr.width, fr.height
c0.close()
def one(name, args=None):
g = av.filter.Graph()
b = g.add_buffer(width=W, height=H, format=u"yuv420p", time_base=TB)
f = g.add(name, args) if args else g.add(name)
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")
h = one(u"hflip")
print(u"原图 左 %.1f 右 %.1f" % (rgb[:, :3].mean(), rgb[:, -3:].mean()))
print(u"hflip 左 %.1f 右 %.1f" % (h[:, :3].mean(), h[:, -3:].mean()))
print(u"--- transpose")
for a in (u"clock", u"cclock", u"clock_flip", u"cclock_flip"):
o = one(u"transpose", a)
print(u"%-12s -> %dx%d" % (a, o.shape[1], o.shape[0]))
print(u"--- rotate")
for a in (u"PI/2", u"PI/4", u"PI/2:fillcolor=red"):
o = one(u"rotate", a)
print(u"%-20s -> %dx%d 左上 %s" % (a, o.shape[1], o.shape[0], o[0, 0].tolist()))
实跑输出:
--------------------------------------------------------------------
7.6 视频翻转与旋转
--------------------------------------------------------------------
**① 四个滤镜,各管一件事**
滤镜 干什么 尺寸变不变
---------------------------------------------------
hflip 左右翻(照镜子) 不变
vflip 上下翻(水面倒影) 不变
transpose 转 90 度 宽高互换
rotate 任意角度 不变
**前两个不需要参数,写上去就生效。后两个要写角度或者方向。**
**② hflip 真的左右翻了吗**
位置 原图三列均值 翻后三列均值
--------------------------------------------------
靠左三列均值 109.8 191.2
靠右三列均值 191.2 109.8
**左边三列 109.8 变成 191.2,右边三列 191.2 变成 109.8——两边的数换了个位置。**
**这就是左右翻转的证据:不是「看起来像」,是左边缘的内容真的跑到了右边缘。**
**③ transpose 的四种取值**
取值 效果 输出尺寸
-----------------------------------------------
clock 顺时针 90 度 90×160
cclock 逆时针 90 度 90×160
clock_flip 顺时针再左右翻 90×160
cclock_flip 逆时针再左右翻 90×160
**四个都是 90×160,因为转 90 度必然宽高互换。**
**只转 90 度时 `transpose` 比 `rotate` 好:它不插值,像素一个不丢。**
**④ rotate 任意角度,但会留下死角**
写法 输出尺寸 左上角像素
---------------------------------------------------
PI/2 160×90 [0, 0, 0]
PI/4 160×90 [0, 0, 0]
45*PI/180 160×90 [0, 0, 0]
PI/2:fillcolor=red 160×90 [253, 0, 0]
**注意 rotate 的输出尺寸不变,永远 160×90。**
**转 45 度之后四个角空出来了,默认补黑,所以左上角是 0;**
**写 `fillcolor=red` 就补成红的,左上角变成 253。**
**要「转完还能看全」,得先算梯形外接矩形,再用 scale 与 pad 拼;**
**那套算法比较绕,日常能用 transpose 解决的就不上 rotate。**
**⑤ 常见组合**
想要的效果 链
-------------------------------------
照镜子 hflip
水面倒影 vflip
手机竖拍转正 transpose=clock
轻微歪斜校正 rotate=-2*PI/180
**竖拍转正那个最常用:手机拍的视频常带旋转元数据,播放器认,**
**但有些老播放器不认,就得真转一遍再存。**
前两行是 hflip 前后的边缘均值,两边的数正好互换。 这比肉眼看图可靠,也顺便说明了「翻」是像素搬运,不是重画。
中间四行 transpose 全是 90×160,方向不同但尺寸一样。 要区分 clock 与 cclock,得看图,光看尺寸分不出来。
最后三行 rotate 都保持 160×90,只有 fillcolor 那一行左上角是红的。 说明转完之后空出来的角确实是拿 fillcolor 填的。
30 秒实验
- 叠加:写
hflip,vflip,看是不是等于转 180 度。 - 换角度:把
PI/2改成90,看画面被转成什么样。 - 量边角:把 rotate 的输出存 png,量四个角有多少像素被填色。
常见误区
- 把 rotate 的 90 当 90 度。它收的是弧度,90 弧度转了十几圈。
- 以为 rotate 会扩画布。尺寸不变,转角的位置直接补 fillcolor。
- 先裁再转正。转正之后又会多出新的黑角。
自测三问(点开对答案)
1. hflip 与 vflip 有什么区别? —— hflip 左右翻(照镜子),vflip 上下翻(水面倒影)。 2. transpose 转 90 度会不会丢像素? —— 不会,它只搬运,不插值,所以像素一个不丢。 3. rotate 转 45 度之后四个角是什么? —— 是 fillcolor 指定的颜色,默认黑。- ☐ (本节完,下一节 7.7 视频填充 pad 滤镜)