🎬 第七章 FFmpeg命令行实现音视频特效及复杂滤镜应用
📄 一句话概括

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 秒实验

  1. 叠加:写 hflip,vflip,看是不是等于转 180 度。
  2. 换角度:把 PI/2 改成 90,看画面被转成什么样。
  3. 量边角:把 rotate 的输出存 png,量四个角有多少像素被填色。

常见误区

  1. 把 rotate 的 90 当 90 度。它收的是弧度,90 弧度转了十几圈。
  2. 以为 rotate 会扩画布。尺寸不变,转角的位置直接补 fillcolor。
  3. 先裁再转正。转正之后又会多出新的黑角。
自测三问(点开对答案) 1. hflip 与 vflip 有什么区别? —— hflip 左右翻(照镜子),vflip 上下翻(水面倒影)。 2. transpose 转 90 度会不会丢像素? —— 不会,它只搬运,不插值,所以像素一个不丢。 3. rotate 转 45 度之后四个角是什么? —— 是 fillcolor 指定的颜色,默认黑。

相关笔记