跳转至

元数据剥离 — C2PA / EXIF / XMP

文件metadata.pynoai/c2pa.pynoai/cleaner.pynoai/isobmff.pynoai/extractor.py

为什么元数据很重要

社媒平台(Instagram、Facebook、X/Twitter)使用图像元数据中的 AI 标记来显示"Made with AI"标签。元数据中可能泄露:

  • 生成 prompt(用户输入的完整文本)
  • 模型 hash(可追溯到具体模型版本)
  • 种子值、采样器参数
  • 用户账号标识符(通过 C2PA 签名链)

AI 元数据检测(metadata.py

精确匹配的 AI 键名(20+)

parameters, prompt, negative_prompt, workflow, comfyui, sd-metadata,
invokeai_metadata, generation_data, ai_metadata, Dream,
sd:prompt, sd:negative_prompt, sd:seed, sd:steps, sd:sampler,
sd:cfg_scale, sd:model_hash, c2pa, c2pa_chunk, Software

关键词子串匹配

stable_diffusion, comfyui, automatic1111, invokeai, midjourney,
dall-e, dalle, imagen, synthid, google_ai, openai, c2pa

IPTC AI 标记(触发社媒"Made with AI"标签的元凶)

trainedAlgorithmicMedia
compositeSynthetic
algorithmicMedia
compositeWithTrainedAlgorithmicMedia

has_ai_metadata() 检测流程

graph TD
    A[PIL 打开图片] --> B[遍历 img.info 键]
    B --> C{精确匹配 AI 键?}
    C -->|是| DETECTED[检测到 AI 元数据]
    C -->|否| D{子串匹配 AI 关键词?}
    D -->|是| DETECTED
    D -->|否| E[调用 c2pa 库 has_c2pa_metadata]
    E --> F{C2PA 存在?}
    F -->|是| DETECTED
    F -->|否| G[二进制扫描前 512KB]
    G --> H{找到 C2PA UUID / IPTC 标记?}
    H -->|是| DETECTED
    H -->|否| CLEAN[无 AI 元数据]

二进制扫描模式:

  • c2pa / C2PA 字节串
  • C2PA UUID:d8fec3d61b0e483c92975828877ec481(16 字节)
  • IPTC AI marker 字节串

C2PA JUMBF 解析(c2pa.py

C2PA 简介

C2PA(Coalition for Content Provenance and Authenticity)是一种加密签名的内容来源标准。AI 生成图像中嵌入 JUMBF(JSON Unified MetaBox Format)容器,包含:

  • 生成工具信息(如 "OpenAI DALL-E 3")
  • 签名者证书链
  • 操作类型(created / converted / edited)
  • 时间戳

PNG 中的 C2PA 存储

PNG 使用自定义 chunk 类型 caBX 存储 JUMBF 容器。

遍历 PNG chunk

def has_c2pa_metadata(data):
    # 验证 PNG 签名
    assert data[:8] == b'\x89PNG\r\n\x1a\n'

    offset = 8
    while offset < len(data):
        length = struct.unpack(">I", data[offset:offset+4])[0]
        chunk_type = data[offset+4:offset+8]  # 4 字节类型
        chunk_data = data[offset+8:offset+8+length]

        if chunk_type == b'caBX':
            # 检查是否包含 C2PA 签名
            if any(sig in chunk_data for sig in [b'c2pa', b'C2PA', b'jumb', b'JUMBF']):
                return True

        offset += 12 + length  # 4(length) + 4(type) + data + 4(CRC)

JUMBF 字节级解析(_parse_c2pa_chunk()

不依赖官方 c2pa 库,直接在字节级别提取关键信息:

字段 匹配模式 示例
发行者 Google, Adobe, OpenAI, Microsoft, Truepic "Google"
AI 工具 GPT-4o, ChatGPT, Sora, DALL-E, Imagen, Firefly "DALL-E 3"
软件代理 正则 softwareAgent.*?dname 提取软件名
声明生成器 claim_generator, claimGenerator "openai/c2pa/0.1"
动作类型 c2pa.created, c2pa.converted, c2pa.edited "created"
时间戳 正则 \d{14}Z "20240115120000Z"
数字来源类型 trainedAlgorithmicMedia / algorithmicMedia 触发 AI 标签

CBOR 文本提取(_scan_png_c2pa_chunk()

手动解析 CBOR major-type 3(UTF-8 字符串)长度前缀:

0x60-0x77: 直接长度(0-23 字节)
0x78: 后续 1 字节长度
0x79: 后续 2 字节长度

提取内容:name(claim generator)、specVersiondigitalSourceType URL、签名者组织。

C2PA chunk 注入(inject_c2pa_chunk()

将 C2PA chunk 插入到第一个 IDAT chunk 之前(PNG 规范要求非关键 chunk 在数据之前)。

EXIF/XMP 清理(cleaner.py

remove_ai_metadata() 流程

def remove_ai_metadata(input_path, output_path):
    # 1. 分类所有 metadata 键为 AI / 标准 / 其他
    ai_keys, standard_keys, other_keys = _extract_non_ai_metadata(img)

    # 2. EXIF 处理
    exif_dict = piexif.load(exif_bytes)
    # 保留非 AI 字段(Author, Copyright, Title 等)
    # 删除 AI 相关字段

    # 3. 写回
    # PNG: PngInfo.add_text() 重建保留的文本 chunk
    # JPEG: 直接写入清理后的 EXIF bytes
    # DPI 和 gamma 始终保留

ISOBMFF 容器解析(isobmff.py

支持 AVIF、HEIF、HEIC、JPEG-XL(均基于 ISO Base Media File Format)。

顶层 Box 遍历

[size:4][type:4][payload...]

三种 size 编码:

size 值 含义
> 1 32 位标准 size
== 1 后续 8 字节为 64 位 largesize
== 0 box 延伸到文件末尾

C2PA 移除逻辑

def strip_c2pa_boxes(data):
    # 验证 ISOBMFF 签名: data[4:8] == b"ftyp"

    for box_type, box_data in iter_top_level_boxes(data):
        if box_type == b'uuid':
            # 检查 16 字节 UUID 是否匹配 C2PA
            if box_data[:16] == C2PA_UUID:  # d8fec3d6...
                continue  # 跳过(移除)
        elif box_type == b'jumb':
            continue  # JPEG-XL JUMBF 容器,移除

        output.append(box)  # 其他 box 原样保留

关键:像素数据 bit-for-bit 不变,只移除元数据容器。

当前限制

  • AVIF/HEIF/JPEG-XL 的 EXIF/XMP box 尚未清除(仅移除顶层 uuidjumb box)
  • PNG 和 JPEG 已完全覆盖

清理结果对照

来源 清理前 清理后
Stable Diffusion prompt, seed, model_hash, sampler, steps 全部移除
Midjourney EXIF 中的 prompt, model, seed 全部移除
DALL-E 3 C2PA manifest 全部移除
Adobe Firefly Content Credentials 全部移除
Gemini C2PA + EXIF 全部移除
Instagram/Facebook trainedAlgorithmicMedia 标签 移除,不再显示 AI 标签