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

字号改的是面积,不是边长:16 到 32 边长翻倍,像素数涨到 3.14 倍。 颜色只改值不改形状,四个颜色的非透明像素数完全一样。

📝 本节和前面几节的关系

上一节把文字水印贴上了,但只知道它「在那儿」。 这一节管两件事:它多大、什么颜色,以及它相对画面到底占几成。

一、字号:像素数按面积涨

同一句「水印」,四档字号各画一遍:

fontsize 字形宽 字形高 非透明像素 相对 16


12 24 11 154 0.65× 16 32 15 236 1.00× 24 48 23 481 2.04× 32 64 31 741 3.14×

从 16 到 32,宽高都翻了一倍,像素数却只涨到 3.14 倍。 不到 4 倍,是因为小字号时笔画相对更粗,抗锯齿也占了更大比例。

但趋势仍然是按面积走:字号调大一档,观感上的存在感是翻着上去的。 所以「稍微大一点」这个念头要克制,很容易一下就大到盖住画面。

二、颜色:只改值,不改形状

四个颜色,同一档字号:

颜色 R G B 非透明像素


白 255 255 255 481 红 255 0 0 481 黄 255 255 0 481 半透明白 255 255 255 481

四行的非透明像素数一模一样——颜色只改值,不改形状。 最后一行的通道均值还是满的 255,因为 alpha 是单独一个通道。

这就解释了为什么透明必须靠 alpha,而不是「把颜色调暗一点」。 调暗得到的是灰色字,压在亮背景上仍然是一块实心,透不出底下的画面。

三、字号相对画面到底占几成

同一句 fontsize=24,放到三种画面高度里看:

画面高度 占几成 观感


90 27% 太大,几乎盖住画面 720 3.3% 偏小,勉强能看 1080 2.2% 更小,得放大字号

水印字号通常取画面高度的 3% 到 5%。按这个标准,24 号连 720p 都不够。

所以字号不能照抄。实战里常写 fontsize=h/30 这种比例式, 让同一行命令在 720p 与 1080p 上都合适。

注意 fontsize 本身不认表达式,这个比例得在外面算好再拼进去—— 只有 x、y 是每帧重算的表达式。

四、命令行的写法

-vf "drawtext=text='水印':fontsize=24:[email protected]:x=10:y=10"

fontcolor 后面加 @0.5 就是半透明,取值范围 0 到 1。 它比单独的 alpha 参数直观,也不影响字形的其他属性。

想要描边再加 borderw=2:bordercolor=black, 白色文字配黑色描边,压在亮背景上也能看清——这是加在复杂画面上的常用组合。

五、动手 Demo:四档字号、四个颜色

先按 12、16、24、32 四档字号画同一句话,量字形尺寸与非透明像素。 再固定字号换四种颜色,看像素数变不变。

import numpy as np
from PIL import Image, ImageDraw, ImageFont

FONT = u"C:/Windows/Fonts/msyh.ttc"

def draw(text, size, color=(255, 255, 255, 255)):
    f = ImageFont.truetype(FONT, size)
    bb = ImageDraw.Draw(Image.new(u"RGBA", (8, 8))).textbbox(
        (0, 0), text, font=f)
    im = Image.new(u"RGBA", (bb[2] - bb[0] + 8, bb[3] - bb[1] + 8),
                   (0, 0, 0, 0))
    ImageDraw.Draw(im).text((4 - bb[0], 4 - bb[1]), text, font=f,
                              fill=color)
    return im, bb

rows = []
for size in (12, 16, 24, 32):
    im, bb = draw(u"水印", size)
    rows.append((size, bb[2] - bb[0], bb[3] - bb[1],
                 int((np.array(im)[:, :, 3] > 0).sum())))
base = [r[3] for r in rows if r[0] == 16][0]
for size, w_, h_, n in rows:
    print(u"fontsize %-3d 字形 %dx%d  非透明 %4d  相对 16 %.2f 倍"
          % (size, w_, h_, n, n / float(base)))

for tag, col in ((u"白", (255, 255, 255, 255)), (u"红", (255, 0, 0, 255)),
                 (u"黄", (255, 255, 0, 255)),
                 (u"半透明白", (255, 255, 255, 128))):
    im, bb = draw(u"水印", 24, col)
    a = np.array(im)
    m = a[:, :, 3] > 0
    print(u"%-6s R %d G %d B %d  非透明 %d"
          % (tag, a[:, :, 0][m].mean(), a[:, :, 1][m].mean(),
             a[:, :, 2][m].mean(), int(m.sum())))

实跑输出:

--------------------------------------------------------------------
6.3.2 drawtext 控制文字颜色及大小
--------------------------------------------------------------------

**① 字号:像素数按面积涨,不是按边长涨**

  fontsize     字形宽     字形高     非透明像素     相对 16
  -------------------------------------------------------------
  12           24         11         154            0.65×
  16           32         15         236            1.00×
  24           48         23         481            2.04×
  32           64         31         741            3.14×

**从 16 到 32,字形宽高都翻了一倍,非透明像素却只涨到 3.14 倍。**
**不到 4 倍的原因是:小字号时笔画相对更粗,抗锯齿也占了更大的比例。**
**但整体趋势仍然按面积走——字号调大一档,观感上的存在感是翻着上去的,**
**不是线性上去的。**

**② 颜色:只改值,不改形状**

  颜色         R       G       B       非透明像素
  ---------------------------------------------------
  白           255     255     255     481
  红           255     0       0       481
  黄           255     255     0       481
  半透明白     255     255     255     481

**四行的非透明像素数完全一样——颜色只改值,不改形状。**
**但最后一行的通道均值还是满的 255,因为 alpha 是单独一个通道:**
**画出来是「白」,贴上去才「半透」——这就是为什么透明必须靠 alpha 通道,**
**而不是「把颜色调暗一点」。**

**③ 命令行怎么写**

**`-vf "drawtext=text='水印':fontsize=24:[email protected]:x=10:y=10"`**

**`fontcolor` 后面加 `@0.5` 就是半透明,取值范围 0 到 1。**
**它比 `alpha` 参数直观,也不影响字形的其他属性。**

**④ 字号与画面的比例**

  画面高度     字号 24 占几成     观感
  ------------------------------------------------------
  90           27%                太大,几乎盖住画面
  720          3.3%               偏小,勉强能看
  1080         2.2%               更小,得放大字号

**同一句 `fontsize=24`,在 160×90 上占了四分之一高,在 1080p 上只占 2%。**
**水印字号通常取画面高度的 3% 到 5%,按这个标准,24 号连 720p 都不够。**
**所以字号不能照抄,得按画面高度换算——**
**实战里常用 `fontsize=h/30` 这种写法,让它随分辨率自动缩放。**

先把四行都算出来存进 rows,找到 16 那一档的像素数当基准,再统一打印。 这样就不会出现「拿自己当基准」的 1.00 倍假象。

a[:, :, 0][m] 是「取红色通道,再按 alpha 掩码筛出字形像素」, 算均值时只算字形,背景的透明像素不参与,否则均值会被稀释。

半透明那一行通道均值仍是 255,因为它测的是颜色值不是透明度。 想量透明度得看 alpha 通道的均值。

30 秒实验

  1. 改字号:把 24 换成 48,看像素数是几倍,与你猜的一样吗。
  2. 加描边:用 ImageDraw.text(..., stroke_width=2) 模拟 borderw,看像素涨多少。
  3. 真跑对比:有 drawtext 的机器上,同一句命令换 fontsize 看 tw 的变化。

常见误区

  1. 以为字号翻倍像素也翻倍。是按面积涨,接近 4 倍。
  2. 把颜色调暗当透明用。那不是透明,是灰色,透不出底下画面。
  3. 在 fontsize 里写 h/30。它只接受数字,表达式只有 x、y 支持。
自测三问(点开对答案) 1. 字号从 16 到 32,非透明像素涨几倍? —— 3.14 倍,不是 4 倍:小字号笔画相对更粗,抗锯齿占比更大。 2. 为什么四个颜色的非透明像素数一样? —— 颜色只改像素值,不改哪些像素被画到,形状由字形决定。 3. 水印字号一般取画面高度的多少? —— 3% 到 5%。`fontsize=24` 在 1080p 上只有 2.2%。

相关笔记