给 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 频域分析 和 对抗水印的技术原理(纯技术向,不教破坏版权)。