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

加中文水印的第一步不是写命令,是挑字体。 挑错的信号很明确:四个字画出来像素数一模一样,那就是豆腐块。

📝 本节和前面几节的关系

前面所有文字水印都默认「字能显示出来」。这一节处理「显示不出来」的情况。 中文比英文多一道坎:字形不在字体里,画出来就是方块。

一、四个字体,同一句「水印测试」

字体 包围盒 像素 填充率 四个字一样吗


msyh 96×23 1073 0.49 各不相同 simhei 95×23 1003 0.46 各不相同 simsun 94×22 795 0.38 各不相同 arial 66×16 208 0.20 完全一样

最后一行 arial 的四个字像素数一模一样——这不是巧合,是缺字形的铁证。 它把每个汉字都画成同一个空心方框,也就是俗称的「豆腐块」。

填充率也是线索:真汉字笔画有疏有密,在 0.38 到 0.49 之间; 豆腐块只有一圈框边,填充率明显偏低。

二、三个中文字体怎么选

字体 观感 适合什么


微软雅黑 现代、笔画匀 水印、字幕首选 黑体 更粗、更醒目 小字号也能看清 宋体 横细竖粗 正式文档,不适合小字

同一句文字,黑体的像素是宋体的 1.26 倍。 所以缩到同样大小,黑体更抗压缩,宋体的细横最容易先糊掉。

视频里还有一层额外损失:编码会抹掉高频细节, 宋体那种一像素宽的横线,在低码率下经常整行消失。

三、命令行的写法

-vf "drawtext=text='水印测试':fontfile='C\:/Windows/Fonts/msyh.ttc':fontsize=24"

fontfile 不写会回落到一个编译期默认字体,那个字体通常没有汉字。 所以加中文水印,「指定字体」不是可选项,是必须做的事。

路径里的冒号还要转义,与 6.2.2 讲 movie 时是完全同一条规则。 同一个解析器,同一套坑,学会一次到处用。

四、动手 Demo:四个字体逐个量

用四个字体画同一句「水印测试」,量包围盒、非透明像素与填充率。 再逐字渲染,看四个字的像素数是不是完全一样。

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

TXT = u"水印测试"

def draw(text, fn, size=24):
    f = ImageFont.truetype(u"C:/Windows/Fonts/" + fn, 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=(255, 255, 255, 255))
    return im, bb

for fn in (u"msyh.ttc", u"simhei.ttf", u"simsun.ttc", u"arial.ttf"):
    im, bb = draw(TXT, fn)
    a = np.array(im)[:, :, 3] > 0
    ib = im.getbbox()
    area = (ib[2] - ib[0]) * (ib[3] - ib[1])
    per = [int((np.array(draw(c, fn)[0])[:, :, 3] > 0).sum()) for c in TXT]
    print(u"%-12s %dx%d  px %4d  填充 %.2f  %s"
          % (fn, ib[2] - ib[0], ib[3] - ib[1], int(a.sum()),
             a.sum() / float(area),
             u"四个一样" if len(set(per)) == 1 else u"各不相同"))

实跑输出:

--------------------------------------------------------------------
6.6.1 -vf 的 drawtext 添加中文水印——先挑对字体
--------------------------------------------------------------------

**① 四个字体,同一句「水印测试」**

  字体           包围盒     非透明像素     填充率     四个字一样吗
  --------------------------------------------------------------------
  msyh.ttc       96×23      1073           0.49       各不相同
  simhei.ttf     95×23      1003           0.46       各不相同
  simsun.ttc     94×22      795            0.38       各不相同
  arial.ttf      66×16      208            0.20       完全一样

**最后一行 arial 的四个字像素数一模一样——这不是巧合,是缺字形的铁证。**
**它把每个汉字都画成同一个空心方框,也就是俗称的「豆腐块」。**
**填充率也是线索:真汉字笔画有疏有密,填充率在 0.4 到 0.5 之间;**
**豆腐块只有一个方框的边,填充率明显偏低。**

**② 三个中文字体哪个更合适**

  字体                 观感                 适合什么
  ------------------------------------------------------------------
  msyh(微软雅黑)     现代、笔画匀         水印、字幕首选
  simhei(黑体)       更粗、更醒目         小字号也能看清
  simsun(宋体)       笔画细、横细竖粗     正式文档,不适合小字


**同一句文字,黑体的像素是宋体的 1.26 倍——**
**所以缩到同样大小,黑体更抗压缩,宋体的细横最容易先糊掉。**

**③ 命令行怎么写**

**`-vf "drawtext=text='水印测试':fontfile='C\\:/Windows/Fonts/msyh.ttc':fontsize=24"`**

**`fontfile` 不写会回落到一个编译期默认字体,那个字体通常没有汉字。**
**所以加中文水印,「指定字体」不是可选项,是必须做的事。**
**路径里的冒号还要转义,与 6.2.2 讲 movie 时是同一条规则。**

im.getbbox() 量的是「真正的墨迹范围」,与 textbbox 差一两个像素。 填充率要算得准,必须用墨迹包围盒,用 textbbox 会把留白算进去。

填充率 = 字形像素 ÷ 墨迹包围盒面积。真汉字在 0.4 左右, 豆腐块只有框边,会掉到 0.2 以下,这个指标比看图片更客观。

arial.ttf 在 Windows 上通常不含汉字,但它不会报错,只是画方框。 所以「能渲染」和「渲染对了」是两件事,必须靠数值判断。

30 秒实验

  1. 换字号:把 24 换成 12,看豆腐块的像素数是不是也等比缩。
  2. 换 mac 字体:试 msyhbd.ttc(雅黑粗体),看像素涨多少。
  3. 真跑对比:有 drawtext 的机器上,故意写 fontfile 指向 arial,看画面。

常见误区

  1. 不指定 fontfile。回落到默认字体,中文基本全是方块。
  2. 用 arial 之类西文字体写中文。能渲染,但渲染的是豆腐块。
  3. 用宋体做小字号水印。细横在低码率下会被整行抹掉。
自测三问(点开对答案) 1. 怎么判断渲染出来的是豆腐块? —— 逐字量像素数,四个字完全一样就是豆腐块;再看填充率是否低于 0.2。 2. 三个中文字体哪个最适合视频水印? —— 黑体或雅黑。宋体笔画太细,低码率下容易糊掉。 3. fontfile 为什么不能省? —— 不写会回落到编译期默认字体,那个字体通常没有汉字。

相关笔记