🎬 第六章 FFmpeg命令行实现图片水印及文字跑马灯
📄 一句话概括

delogo 不是「恢复」,是「用框外一圈像素把洞糊上」。 补得准不准,取决于被盖住的地方有多可预测——真实画面里几乎都不可预测。

📝 本节和前面几节的关系

前两节在「加水印」,这一节反过来「去水印」。 两者用的是同一套机制,只是方向相反。

一、它的公式只有一句话

框里每一个点,都由「左边与右边」和「上边与下边」两组插值各算一次,取平均。

横向插值:左边缘像素与右边缘像素按距离加权; 纵向插值:上边缘与下边缘同样处理。

两组取平均,是为了避免某一方向边缘全是噪声时补得太离谱。 这也说明它本质上是个「平滑填充」,不是任何形式的还原。

二、纯渐变底图:一猜就中

造一张横向线性渐变的底图,在中间压一块 40×20 的纯白块,再补回来:

位置 原底图 压水印后 补出来 误差


左上角 77 255 77 0 正中 99 255 99 0 右下角 119 255 119 0

三个取样点误差全为 0,整块区域平均误差 0.1、最大 1。 因为插值公式假定「左到右是直线」,而这张图真的是直线,所以一猜就中。

换成人话:这相当于给算法出了一道它有公式的题。 做得准不代表算法聪明,只是因为题型对上了。

三、加一层棋盘格:误差立刻上来

在同样的渐变上叠 4×4 棋盘格,用同一套公式再补一次:

底图 平均误差 最大误差


纯横向渐变 0.1 1 渐变加棋盘格 19.8 39

公式一个字没改,平均误差翻了 158 倍。差别只在「底图里有没有细节」。

真实视频被水印盖住的几乎全是纹理与细节,所以 delogo 的实际效果 永远是「糊上一层」——它抹掉了水印,也抹掉了原本的画面。

想通这一点,就不会再期待「完美去水印」这种东西。 框留得大一点糊得更干净,但糊掉的真实画面也更多,永远是取舍。

四、命令行的写法

ffmpeg -i in.mp4 -vf "delogo=x=50:y=30:w=40:h=20" out.mp4

w、h 是从左上角算起的宽高,不是右下角坐标。 写错成坐标的话,框会跑到画面外面去,等于什么都没做。

还有一个常被忽略的选项:show=1,它会把框画出来给自己看。 先开着它确认位置对不对,再关掉跑正式输出,能省掉很多次返工。

五、动手 Demo:两张底图,同一套公式

造一张纯横向渐变、一张加棋盘格的底图,各压一块白块。 再用「框外一圈插值」补回来,逐点比误差。

import numpy as np

W, H = 160, 90
x0, y0, x1, y1 = 50, 30, 90, 50

def grad():
    x = np.arange(W, dtype=np.float64)
    col = (20 + x * 180.0 / (W - 1)).astype(np.uint8)
    return np.repeat(col[None, :, None], H, axis=0).repeat(3, axis=2)

def checker():
    bg = grad().astype(np.int32)
    yy, xx = np.mgrid[0:H, 0:W]
    m = ((yy // 4) + (xx // 4)) % 2
    return np.clip(np.where(m[:, :, None] == 1, bg + 40, bg), 0, 255)

def fill(dirty):
    out = dirty.astype(np.float64).copy()
    for c in range(3):
        left = dirty[y0:y1, x0 - 1, c].astype(np.float64)
        right = dirty[y0:y1, x1, c].astype(np.float64)
        top = dirty[y0 - 1, x0:x1, c].astype(np.float64)
        bot = dirty[y1, x0:x1, c].astype(np.float64)
        wc = np.linspace(0, 1, x1 - x0)[None, :]
        wr = np.linspace(0, 1, y1 - y0)[:, None]
        horiz = left[:, None] * (1 - wc) + right[:, None] * wc
        vert = top[None, :] * (1 - wr) + bot[None, :] * wr
        out[y0:y1, x0:x1, c] = (horiz + vert) / 2.0
    return np.clip(out + 0.5, 0, 255).astype(np.uint8)

for tag, bg in ((u"纯渐变", grad()), (u"加棋盘格", checker())):
    dirty = bg.copy()
    dirty[y0:y1, x0:x1] = 255
    fix = fill(dirty)
    e = (fix[y0:y1, x0:x1].astype(np.int32)
         - bg[y0:y1, x0:x1].astype(np.int32))
    print(u"%-8s 平均 %.1f  最大 %d"
          % (tag, np.abs(e).mean(), np.abs(e).max()))

实跑输出:

--------------------------------------------------------------------
6.2.3 -vf 的 delogo 去掉水印——它的原理与一次手算
--------------------------------------------------------------------

**本机构建没有 delogo 滤镜,但它的原理只有一句话:**
**「把水印框里的像素,用框边缘那一圈像素插值补上」。**
**这不是黑箱,自己算一遍比看十遍文档清楚。**

**① 造两张底图,控制住唯一的变量**

**一张是纯横向渐变,一张在同样的渐变上叠了 4×4 棋盘格。**
**两张都在 (50, 30) 处压一块 40×20 的纯白水印,然后各补一次。**

**② 第一张:取样点逐个对照**

  位置       原底图     压水印后     补出来     误差
  ------------------------------------------------------
  左上角     77         255          77         0
  正中       99         255          99         0
  右下角     119        255          119        0

**三个取样点的误差都在 1 以内,因为这张底图的规律正好是「横向线性」。**
**插值公式假定「左边到右边是直线」,而这张图真的是直线,所以一猜就中。**

**③ 第二张:同一套公式,误差立刻上来**

  底图             平均误差     最大误差
  ------------------------------------------
  纯横向渐变       0.1          1
  渐变加棋盘格     19.8         39

**第二张的平均误差是第一张的 158 倍。公式一个字没改,只是底图里多了细节。**
**这说明 delogo 补得准不准,跟算法聪明不聪明关系不大,**
**取决于「被盖住的那块地方有多可预测」。**
**真实视频里被水印盖住的几乎全是纹理与细节,所以 delogo 的效果永远是**
**「糊上一层」——它抹掉了水印,也抹掉了原本的画面。**

**④ 命令行怎么写**

**`ffmpeg -i in.mp4 -vf "delogo=x=50:y=30:w=40:h=20" out.mp4`**

**要留神 `w`、`h` 是从左上角算的宽高,不是右下角坐标。**
**框留得比水印大一圈更容易糊干净,但框越大,糊掉的真实画面也越多。**
**所以去水印永远是一个「取舍」的活,不是无损还原。**

np.linspace(0, 1, 40) 给出 40 个从 0 到 1 的权重,就是「按距离加权」。 左端权重 1(全取左边),右端权重 0(全取右边),中间线性过渡。

+ 0.5 再 clip 是四舍五入的常见写法, 不加的话浮点转整数会一路向下取整,整体偏暗一格。

真实 delogo 还多一步「边缘检测」,会避开明显的边界不往里插。 但核心就是这两组插值取平均,把它算一遍,参数就再也不会瞎调了。

30 秒实验

  1. 改框大小:把框放大到 60×30,看误差是变大还是变小。
  2. 换只取横向:把 (horiz + vert) / 2 改成只用 horiz,比一次误差。
  3. 真跑一次:找一台有 delogo 的机器,ffmpeg -vf delogo=...:show=1 先看框位置。

常见误区

  1. 以为 delogo 能还原原画面。它只会用周围像素往里糊,不还原任何细节。
  2. 把 w、h 当成右下角坐标。它们是从左上角算的宽高。
  3. 框贴着水印边缘留。边缘留一圈余量更容易糊干净,但也糊掉更多真画面。
自测三问(点开对答案) 1. delogo 用的两组插值是什么? —— 左右边缘的横向插值与上下边缘的纵向插值,两者取平均。 2. 为什么纯渐变底图能补得几乎没误差? —— 插值假定线性,而线性渐变本身就是线性的,题型对上了。 3. 为什么真实视频上效果总是发糊? —— 被盖住的是纹理细节,插值只能按边缘猜,猜不到里面原来是什么。

相关笔记