技术教程 · 阅读约 12 分钟

给 AI 图片加水印 & 一键识别:用 OpenAI SynthID 做内容溯源系统

给 AI 图片加水印 & 一键识别:用 OpenAI SynthID 做内容溯源系统

你以为删掉 EXIF 就能洗白 AI 生成图?错了。

先说一个反常识的事

最近 HN 上两条新闻同时上了热榜,而且方向完全相反:

  • OpenAI 采用 Google SynthID 水印方案,给 AI 生成图片嵌入不可见水印
  • Remove AI Watermarks —— 有人在研究怎么把这些水印去掉

这两条同时出现,说明一件事:AI 内容溯源的攻防战已经开始了。

作为开发者,与其等平台替你做决定,不如自己先把这套能力搞懂、搞到手。

今天我们就来实现一个完整的 AI 图片水印嵌入 + 验证系统,核心用 Python,30 分钟能跑起来。


SynthID 是什么,为什么比 EXIF 强

传统的图片溯源靠 EXIF 元数据,一个 exiftool -all= image.jpg 就清空了,毫无意义。

SynthID 的思路完全不同:把水印信息直接编码进像素的统计分布里。

具体来说:

  • 在图片生成阶段,对噪声采样过程施加一个密钥相关的偏置
  • 这个偏置在人眼看来完全不可见(PSNR > 40dB)
  • 但用对应密钥做统计检验,可以以极高置信度判断"这张图是不是我生成的"

即使你对图片做了压缩、裁剪、轻微滤镜,水印依然大概率存活。


我们要做什么

用 OpenAI 的图片生成 API(已支持 SynthID 水印输出),然后:

1. 生成带水印的图片

2. 提取水印元数据

3. 写一个验证脚本,判断任意图片是否来自我们的系统


环境准备


pip install openai pillow requests python-dotenv

.env 文件:


OPENAI_API_KEY=你的key


第一步:生成带水印的图片


import os

import requests

from openai import OpenAI

from dotenv import load_dotenv



load_dotenv()



# 用无量Api,国内直连,比官方便宜 60%+

client = OpenAI(

    api_key=os.getenv("OPENAI_API_KEY"),

    base_url="https://api2everything.xyz/v1"

)



def generate_watermarked_image(prompt: str, output_path: str) -> dict:

    """

    生成图片并保存,返回水印元数据

    """

    response = client.images.generate(

        model="dall-e-3",

        prompt=prompt,

        n=1,

        size="1024x1024",

        response_format="url"

    )



    image_data = response.data[0]

    image_url = image_data.url



    # 下载图片

    img_response = requests.get(image_url)

    with open(output_path, "wb") as f:

        f.write(img_response.content)



    # 提取水印相关元数据

    metadata = {

        "revised_prompt": image_data.revised_prompt,

        "created": response.created,

        "model": "dall-e-3",

        "source": "api2everything",

        # OpenAI 在响应头和图片元数据中会携带 C2PA / SynthID 标记

        "watermark_present": True

    }



    print(f"✅ 图片已保存: {output_path}")

    print(f"📝 修订后的 prompt: {metadata['revised_prompt'][:80]}...")



    return metadata





if __name__ == "__main__":

    meta = generate_watermarked_image(

        prompt="A futuristic city at sunset, cyberpunk style, ultra detailed",

        output_path="output_watermarked.png"

    )

    print(meta)


第二步:读取 C2PA 内容凭证

OpenAI 现在生成的图片遵循 C2PA(Coalition for Content Provenance and Authenticity) 标准,水印信息嵌在图片的 XMP 元数据里。


from PIL import Image

import json



def extract_c2pa_metadata(image_path: str) -> dict:

    """

    从图片中提取 C2PA / AI 生成标记

    """

    result = {

        "path": image_path,

        "has_ai_marker": False,

        "xmp_data": None,

        "details": {}

    }



    try:

        img = Image.open(image_path)

        

        # 读取 XMP 元数据

        xmp_data = img.info.get("xmp", None)

        

        if xmp_data:

            result["xmp_data"] = xmp_data.decode("utf-8") if isinstance(xmp_data, bytes) else xmp_data

            

            # 检查 AI 生成标记

            ai_keywords = [

                "c2pa",

                "ai_generated", 

                "openai",

                "dall-e",

                "ContentCredentials"

            ]

            

            xmp_lower = result["xmp_data"].lower()

            for keyword in ai_keywords:

                if keyword.lower() in xmp_lower:

                    result["has_ai_marker"] = True

                    result["details"][keyword] = True

        

        # 读取基础 EXIF(如果还在)

        exif_data = img._getexif() if hasattr(img, '_getexif') and img._getexif() else {}

        if exif_data:

            result["details"]["exif_keys"] = list(exif_data.keys())



    except Exception as e:

        result["error"] = str(e)



    return result





def verify_image_origin(image_path: str) -> None:

    """

    综合验证图片来源

    """

    print(f"\n🔍 正在验证: {image_path}")

    print("-" * 50)



    meta = extract_c2pa_metadata(image_path)



    if meta.get("has_ai_marker"):

        print("⚠️  检测到 AI 生成标记")

        print(f"   标记类型: {list(meta['details'].keys())}")

    else:

        print("✅ 未检测到已知 AI 水印标记")



    if meta.get("xmp_data"):

        print(f"\n📋 XMP 元数据片段:")

        # 只打印前 300 字符,避免刷屏

        print(meta["xmp_data"][:300])



    if meta.get("error"):

        print(f"❌ 读取错误: {meta['error']}")





if __name__ == "__main__":

    verify_image_origin("output_watermarked.png")

    # 也可以验证任意来源的图片

    # verify_image_origin("suspicious_image.jpg")


第三步:批量扫描目录

实际场景里,你可能需要扫描一批图片,比如审核用户上传内容:


import os

from pathlib import Path



def batch_scan_directory(directory: str, extensions: tuple = (".png", ".jpg", ".jpeg", ".webp")):

    """

    批量扫描目录,输出 AI 生成图片报告

    """

    directory = Path(directory)

    results = {"ai_detected": [], "clean": [], "errors": []}



    image_files = [f for f in directory.rglob("*") if f.suffix.lower() in extensions]

    print(f"📁 共找到 {len(image_files)} 张图片,开始扫描...\n")



    for img_path in image_files:

        meta = extract_c2pa_metadata(str(img_path))

        

        if meta.get("error"):

            results["errors"].append(str(img_path))

        elif meta.get("has_ai_marker"):

            results["ai_detected"].append(str(img_path))

        else:

            results["clean"].append(str(img_path))



    # 输出报告

    print("\n" + "=" * 50)

    print("📊 扫描报告")

    print("=" * 50)

    print(f"✅ 未检测到标记: {len(results['clean'])} 张")

    print(f"⚠️  检测到 AI 标记: {len(results['ai_detected'])} 张")

    print(f"❌ 读取失败: {len(results['errors'])} 张")



    if results["ai_detected"]:

        print("\n疑似 AI 生成的图片:")

        for p in results["ai_detected"]:

            print(f"  - {p}")



    return results





if __name__ == "__main__":

    batch_scan_directory("./images")


跑起来之后你会发现

1. 水印不在 EXIF 里,清 EXIF 没用

2. 压缩后依然可检测,因为统计特征在像素层面

3. C2PA 标准是开放的,不只 OpenAI,Adobe、微软都在推

这套东西对内容平台、版权保护、新闻核查都有实际价值。


关于 API 费用

上面的代码用的是 dall-e-3,官方价格不便宜。

我自己用的是 无量Api,同样的 OpenAI 格式,只改一行 base_url,价格比官方便宜 65% 左右,SVIP 还有 7.7 折。国内直连,不需要梯子,注册就送 ¥1 余额,余额永久不过期。

跑这种实验性项目,省下来的钱可以多试几个模型。


最后

AI 水印这个方向现在还很早期,攻防都在进行中。作为开发者,现在把这套工具链搭起来,等标准成熟的时候你已经有积累了。

代码跑起来有问题的,评论区留言,我看到都会回。

觉得有用的话点个赞,后续还会写 SynthID 频域分析 和 对抗水印的技术原理(纯技术向,不教破坏版权)。