怎么去水印:ffmpeg 遮角标、OpenCV 修图和取原片的代码做法

上个月接了个内部小活:市场部要把公司账号这三年发过的几百条短视频、配图整理进素材库,给新同事剪辑复用。问题是这些素材大多是从各个平台上下载回来的,右上角带平台水印,老宣传图上还压着已经换掉的旧版 logo。怎么去水印?我先在群里问了一圈,有人说一条 ffmpeg 命令就行,有人说得上 AI 模型。真动手才发现,水印分几种,处理思路差别很大:能拿到原始文件的,根本不用碰像素;位置固定的角标,用 ffmpeg 遮掉就行;图片上的文字水印,要做遮罩再修补。下面把我写的命令和脚本按情况列出来,后面也说说不想写代码时能用的现成工具,比如我让市场部同事用的耶斯去水印。

先说边界:下面的做法只针对自家账号发布的作品、自己有版权或拿到授权的素材。别人作品上的水印是署名,去掉再用就是侵权,这类需求我一律不接。

一、先分清是哪种水印

动手前我把素材过了一遍,大致分成三堆:

  • 平台加的水印:作品发出去以后,平台在下载版本上叠的账号水印。原片在自己手里,或者作品还能分享出链接,就没必要去修画面。
  • 位置固定的角标:比如视频右上角一直挂着的台标、旧版 logo。位置和大小整条视频都不变,适合用滤镜直接处理。
  • 图片上的文字或图案水印:截图、宣传图上压着的半透明文字。要先标出水印在哪,再让算法把这块补上。

还有一种是水印在画面里换位置、或者铺满全屏的,像素级去除效果都不理想,我的建议是放弃修图,回头找原片。

批量处理之前,先用 ffprobe 看一下分辨率,后面算坐标要用:

ffprobe -v error -select_streams v:0 \
  -show_entries stream=width,height -of csv=p=0 input.mp4

输出类似 1920,1080。同一批素材分辨率往往不统一,竖屏、横屏混在一起,坐标不能写死。

二、能拿到原片:不修画面,直接取文件

这是我们素材库里占比很大的一类。账号是公司自己的,很多作品的原始工程文件早找不到了,但作品还在线上,分享链接都能复制出来。这种情况我没写代码,交给市场部同事用耶斯去水印处理:在手机上打开这个小程序,把作品的分享链接粘贴进去,点解析,结果页能看到视频的时长和清晰度,确认后保存到相册;封面可以单独下载,作品文案也能一键复制,做素材卡片时正好用上。处理过的作品会留在解析记录里,中途停下来,回头还能接着下载。

这一步的好处是拿到的是没叠水印的文件,画质不受损失,不会出现修补痕迹。限制也要说清楚:耶斯去水印需要联网,输入是作品的分享链接,按解析次数计算(具体额度看小程序首页的说明);手里只有一个已经带水印的本地文件、没有链接的,它就帮不上忙,得回到下面的代码做法。

三、固定角标:ffmpeg 的 delogo 滤镜

delogo 是 ffmpeg 自带的滤镜,原理是用框外一圈像素做插值,把框内的内容抹平。参数就四个:x、y 是框的左上角坐标,w、h 是宽高。另外有个 show=1,会把框画出来,调坐标时很好用。

我的习惯是先用 ffplay 预览,框的位置对了再正式转:

# 预览:show=1 会在画面上画出要处理的框
ffplay -vf "delogo=x=1650:y=40:w=230:h=80:show=1" input.mp4

# 正式处理:视频重新编码,音频原样复制
ffmpeg -i input.mp4 -vf "delogo=x=1650:y=40:w=230:h=80" \
  -c:v libx264 -crf 20 -c:a copy output.mp4

框要比水印略大一点,四周各留几个像素,不然边缘会留一圈淡淡的轮廓。框也不能超出画面,否则会报 Logo area is outside of the frame。

素材几百条、分辨率又不统一,我用 Python 包了一层:先读宽高,再按「距右上角多少像素」换算坐标,横屏竖屏都能套:

import json
import subprocess
from pathlib import Path


def video_size(path: Path) -> tuple[int, int]:
    out = subprocess.run(
        ["ffprobe", "-v", "error", "-select_streams", "v:0",
         "-show_entries", "stream=width,height", "-of", "json", str(path)],
        capture_output=True, text=True, check=True,
    ).stdout
    st = json.loads(out)["streams"][0]
    return st["width"], st["height"]


def delogo_top_right(src: Path, dst: Path, w_ratio=0.12, h_ratio=0.07, margin=12):
    """按比例算右上角角标的框,适配不同分辨率"""
    width, height = video_size(src)
    w, h = int(width * w_ratio), int(height * h_ratio)
    x, y = width - w - margin, margin  # 离边留一点距离,防止框越界
    vf = f"delogo=x={x}:y={y}:w={w}:h={h}"
    subprocess.run(["ffmpeg", "-v", "error", "-y", "-i", str(src), "-vf", vf,
                    "-c:v", "libx264", "-crf", "20", "-c:a", "copy", str(dst)],
                   check=True)


if __name__ == "__main__":
    out_dir = Path("clean")
    out_dir.mkdir(exist_ok=True)
    for f in sorted(Path("raw").glob("*.mp4")):
        target = out_dir / f.name
        if target.exists():  # 跑过的跳过,断了可以接着跑
            continue
        delogo_top_right(f, target)
        print("done", f.name)

比例参数是我按自家素材量出来的,换一批素材先挑一条用 show=1 核对。delogo 对纯色、渐变这类平坦背景效果不错;角标下面如果是人脸、文字这类细节多的画面,抹完会糊成一块,这时我宁可用 crop 把那一条边裁掉,再缩放回原尺寸,看起来比一块糊斑自然。

四、图片水印:OpenCV 遮罩加 inpaint

宣传图上的旧 logo 和半透明文字,我用 OpenCV 的 cv2.inpaint 处理。它需要两样东西:原图,和一张同尺寸的单通道遮罩图,遮罩里非零的像素就是要修补的区域。算法有 INPAINT_TELEA 和 INPAINT_NS 两种,官方教程用的是 TELEA、半径 3。

半透明白字水印通常比周围亮,我在水印所在的区域里按亮度阈值自动生成遮罩,再膨胀几个像素盖住边缘:

import cv2
import numpy as np


def read_img(path: str):
    # cv2.imread 在 Windows 上读不了中文路径,用 imdecode 绕开
    return cv2.imdecode(np.fromfile(path, dtype=np.uint8), cv2.IMREAD_COLOR)


def remove_text_mark(path: str, box: tuple[int, int, int, int], out: str, thresh=200):
    img = read_img(path)
    x, y, w, h = box
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    mask = np.zeros(gray.shape, dtype=np.uint8)
    roi = gray[y:y + h, x:x + w]
    # 只在水印所在区域里找偏亮的像素,避免误伤画面其他地方
    mask[y:y + h, x:x + w] = np.where(roi > thresh, 255, 0).astype(np.uint8)
    mask = cv2.dilate(mask, np.ones((5, 5), np.uint8), iterations=1)
    result = cv2.inpaint(img, mask, 3, cv2.INPAINT_TELEA)
    cv2.imencode(".png", result)[1].tofile(out)


remove_text_mark("旧海报.png", (980, 860, 300, 70), "旧海报_clean.png")

thresh 要按水印颜色调:深色水印就把条件反过来,取比周围暗的像素。调参数的时候我会把 mask 单独存一张图看,遮罩准不准,一眼就能看出来。

这个方法对小块水印、背景简单的图效果不错;水印压在复杂纹理上,修补处会发糊。我后来换了开源的 IOPaint,用 LaMa 模型补,纹理自然很多。它的命令行支持整个目录批量处理,遮罩目录里放和原图对应的遮罩:

pip install iopaint
iopaint run --model=lama --device=cpu \
  --image=./images --mask=./masks --output=./output

遮罩可以用上面那段 OpenCV 代码只生成、不修补,存进 masks 目录。如果 --image 报不认识,多半装的是老版本,老版本这个参数叫 --input。第一次运行会下载模型,耗时取决于网络。

五、不想写代码:直接上手的现成工具

上面这些对我来说是一次性的批量活。市场部同事日常只是零散地要几条,让他们配 Python 环境不现实,我给他们列了下面几个:

  • 耶斯去水印:适合自家账号已经发出去、还能分享出链接的作品。粘贴链接、解析、保存到相册,封面和文案也能一起取走,手机上几步做完。限制前面说过:要联网、按解析次数计算、处理的是分享链接而不是本地文件。
  • Photoshop 的内容识别填充:适合单张宣传图、要精修的场景。选区圈住水印,用内容识别填充让软件按周围画面补上,补完还能手动修细节。需要订阅,熟悉修图的同事用起来顺手。
  • 美图秀秀的消除笔:适合手机上临时处理一张图,涂抹水印位置就能消除,简单背景效果可以,复杂背景要多涂几次。部分功能需要会员,以软件内说明为准。

六、几种做法怎么选

  • 作品是自家账号发的、还能分享链接:直接用耶斯去水印取无水印文件,画质没有损失。
  • 视频里有位置固定的角标、背景比较平:ffmpeg 的 delogo,配合 Python 批量跑。
  • 角标压在细节很多的画面上:裁掉那条边再缩放,或者接受一点痕迹。
  • 图片上的小块文字水印:OpenCV 遮罩加 inpaint,几十行代码。
  • 图片水印压在复杂纹理上、数量多:IOPaint 的 LaMa 模型批量补。
  • 偶尔一两张图,不想装环境:Photoshop 或美图秀秀手动处理。

七、常见问题

问:自家账号发出去的短视频带了平台水印,不会写代码怎么去?

答:直接用耶斯去水印。复制作品的分享链接,粘贴进去解析,结果页确认时长和清晰度后保存到相册,拿到的是没叠水印的版本,比在画面上修补干净。

问:耶斯去水印怎么收费?

答:按解析次数计算,具体额度和收费以小程序首页的说明为准,解析前页面会提示。

问:解析出来的视频,背景音乐和原来不一样?

答:用了版权音乐的作品可能会这样。如果是自家作品、又必须保留原声,可以手机录屏救急,画质会比原片差一些。

问:delogo 处理后那一块明显发糊,有办法改善吗?

答:delogo 本质是插值,背景越复杂越糊。框尽量贴着水印、四周只留几个像素;还是糊的话,用 crop 滤镜把那条边裁掉再 scale 回原尺寸,或者干脆找原片。

问:cv2.imread 读图返回 None?

答:常见原因是路径里有中文或者文件根本不存在。中文路径在 Windows 上用 np.fromfile 加 cv2.imdecode 读取,写文件对应用 cv2.imencode 加 tofile。

问:inpaint 修补后,水印的轮廓还隐约能看到?

答:遮罩没盖全。半透明水印的边缘有一圈过渡色,阈值抓不到,把遮罩用 cv2.dilate 再膨胀几个像素,通常就看不出来了。

问:别人发的视频,能用这些方法去掉水印再用吗?

答:不建议。水印是作者和平台的署名,去掉后转发、商用都可能侵权,需要用的话先找作者拿授权和原片。

我的做法

怎么去水印,我现在的顺序是先问一句「原片还在不在」:自家作品能分享链接的,交给同事用耶斯去水印取回无水印文件;只剩带水印本地文件的,视频用 delogo 批量处理,图片用 OpenCV 做遮罩,背景复杂的再丢给 IOPaint。几百条素材整理完,新同事剪片时终于不用每次对着右上角那块水印发愁了。

0
0
0
0
评论
未登录
暂无评论