随着AIGC内容爆发式增长,如何有效识别和管理AI生成图像成为平台方的核心痛点。本文分享一个基于FastAPI的图像合规检测项目,实现了对中国GB45438-2025标准和国际C2PA标准的双重支持,并附带图像篡改分析能力。

背景:为什么需要图像合规检测?

2025年,中国正式实施《网络安全技术 人工智能生成内容标识办法》(GB 45438-2025),要求所有AI生成内容必须具备隐式标识(元数据嵌入)和显式标识(可见水印)。与此同时,国际上由微软、Adobe、Intel等巨头发起的C2PA联盟也在推动内容来源和真实性标准的普及。

对于内容平台而言,手动审核海量图片既不现实也不高效。于是,我萌生了构建一个自动化图像合规检测API的想法——既能满足国内监管要求,又能与国际标准接轨。


项目架构设计

整个系统采用经典的分层架构

┌─────────────────────────────────┐
│         API Layer               │  FastAPI路由 + Pydantic校验
├─────────────────────────────────┤
│      Policy Engine              │  综合决策引擎
├──────┬────────┬─────────────────┤
│ GB   │  C2PA  │    Forgery      │  三大检测器并行工作
│45438 │ Checker│    Checker      │
└──────┴────────┴─────────────────┘

核心技术选型

层级 技术 选择理由
Web框架 FastAPI 异步高性能,自动生成交互式文档
图像处理 Pillow + NumPy Python生态中最成熟的组合
C2PA验证 c2pa-python 官方SDK的Python绑定
AI视觉 OpenAI兼容API 利用大模型的OCR和语义理解能力
配置管理 python-dotenv 环境变量隔离,便于部署

核心模块一:GB45438检测器

GB45438标准的核心是"双重标识"机制。我的检测器也分为两个子模块。

隐式标识:在字节流中寻找JSON

隐式标识是一段嵌入在图像文件中的JSON数据,结构如下:

{
  "AIGC": {
    "Label": "1",
    "ContentProducer": "某AI平台"
  }
}

Label字段的取值有明确含义:

  • "1" = AI生成
  • "2" = AI合成
  • "3" = 疑似AI生成

实现思路很简单:将图像文件当作二进制文本读取,用正则表达式搜索包含"AIGC"的JSON片段。

def _check_implicit_label(self, image_bytes: bytes):
    text_blob = image_bytes.decode("utf-8", errors="ignore")
    match = re.search(r'(\{.*\"AIGC\".*\})', text_blob)
    
    if not match:
        return False  # 没找到就是没有
    
    raw_json = match.group(1).replace("\x00", "")
    data = json.loads(raw_json)
    label = data.get("AIGC", {}).get("Label", "")
    
    return label in {"1", "2", "3"}

这里有个细节:JPEG文件的EXIF段中可能包含\x00空字节,解析前需要清理。

显式标识:用Vision Model做智能OCR

传统的OCR方案(如Tesseract、easyocr)在处理复杂背景、艺术字体时准确率有限。我尝试了另一种思路:直接用视觉大模型来判断

把整张图片作为输入,让模型回答:“这张图是否有清晰可见的AI生成标识文字?”

prompt = (
    "You are checking GB45438 explicit label compliance. "
    "Judge whether explicit AI-generated label is clearly visible. "
    "Return JSON with keys: status (pass/warn/fail), reason."
)

payload = {
    "model": "gpt-4o-mini",
    "input": [{
        "role": "user",
        "content": [
            {"type": "input_text", "text": prompt},
            {"type": "input_image", "image_url": f"data:image/png;base64,{image_b64}"},
        ],
    }],
}

这种做法的优势在于:

  1. 语义理解:模型能区分"AI生成"字样和普通的装饰文字
  2. 容错性强:即使文字被部分遮挡或变形,模型仍能做出合理判断
  3. 无需预定义关键词:避免了硬编码关键词列表的维护成本

当然,这也意味着需要依赖外部API,我在代码中做了降级处理——如果Vision API不可用,会返回warn状态而非直接失败。

风险等级评估

综合隐式和显式检测结果,给出风险评级:

隐式标识 显式标识 风险等级
✅ 通过 ✅ 通过 low
❌ 未通过 ✅ 通过 medium
❌ 未通过 ❌/⚠️ 未通过/警告 high

核心模块二:C2PA验证器

C2PA(Content Provenance and Authenticity)是一套基于密码学的内容溯源标准。它的核心思想是:每次对图像的编辑操作都会生成一个带签名的"声明"(Claim),所有声明链接成一条不可篡改的链条。

C2PA Manifest的结构

ManifestStore
├── Claims[]                    # 声明列表(按时间倒序)
│   ├── actions[]              # 操作记录
│   │   └── action: "c2pa.action/generate"  # AI生成标记
│   ├── assertions[]           # 断言数据(缩略图、哈希等)
│   └── signature              # RSA/ECDSA数字签名
├── issuer: "Adobe Photoshop"  # 签发者
└── verification_result        # 验证结果

SDK兼容性挑战

c2pa-python库的API在不同版本间存在差异。为了增强鲁棒性,我采用了多策略探测的方式:

def _read_manifest_store(self, c2pa_module, image_bytes):
    """尝试多种可能的API调用方式"""
    candidates = [
        ("from_bytes", (image_bytes,)),
        ("read", (image_bytes,)),
        ("load_from_memory", (image_bytes,)),
        ("ManifestStore.from_bytes", (image_bytes,)),
        ("Reader.from_bytes", (image_bytes,)),
    ]
    
    for name, args in candidates:
        try:
            target = c2pa_module
            for part in name.split("."):
                target = getattr(target, part)
            result = target(*args)
            if result is not None:
                return result
        except Exception:
            continue
    
    return None  # 所有方式都失败

这种"试错"模式虽然看起来不够优雅,但在面对不稳定的第三方库时非常实用。

签名验证与信息采集

验证通过后,提取关键信息供后续分析:

def _extract_actions(self, manifest_store):
    """提取所有操作记录"""
    actions = []
    claims = getattr(manifest_store, "claims", None) or []
    
    for claim in claims:
        claim_actions = claim.get("actions", []) if isinstance(claim, dict) else getattr(claim, "actions", [])
        for action in claim_actions:
            value = action.get("action") if isinstance(action, dict) else getattr(action, "action", None)
            if value:
                actions.append(str(value))
    
    return actions

def _extract_issuer(self, manifest_store):
    """提取签发者信息"""
    issuer = getattr(manifest_store, "issuer", None)
    if issuer:
        return str(issuer)
    
    # 从claims中查找
    claims = getattr(manifest_store, "claims", None) or []
    for claim in claims:
        value = claim.get("issuer") if isinstance(claim, dict) else getattr(claim, "issuer", None)
        if value:
            return str(value)
    
    return None

最终返回的结果包含:

  • status: valid / invalid / missing
  • signature_valid: 签名是否有效
  • issuer: 签发者(如"Stable Diffusion")
  • actions: 操作历史列表
  • errors: 错误信息(验签失败时)

核心模块三:图像篡改检测器

这个模块的目标是回答一个问题:这张图是否经过PS等工具编辑过?

我采用了两种互补的技术:ELA误差级别分析和EXIF编辑痕迹检测。

ELA算法:压缩残差分析

ELA(Error Level Analysis)的基本原理是:JPEG图像经过一次压缩后,不同区域的压缩误差应该趋于一致。如果某个区域被编辑过,该区域的压缩残差会显著高于周围。

算法流程:

原始图像 
  → 以质量95重压缩 → 计算像素差值
  → 以质量90重压缩 → 计算像素差值  
  → 以质量85重压缩 → 计算像素差值
  → 取最大值 → 归一化 → 热力图
def _ela_probability_and_map(self, image):
    src = np.asarray(image.convert("RGB"), dtype=np.float32)
    maps = []
    
    for quality in (95, 90, 85):
        buffer = BytesIO()
        image.save(buffer, format="JPEG", quality=quality)
        recompressed = Image.open(BytesIO(buffer.getvalue())).convert("RGB")
        rec_arr = np.asarray(recompressed, dtype=np.float32)
        
        # 逐像素计算绝对差值的均值(RGB三个通道)
        diff = np.abs(src - rec_arr).mean(axis=2)
        maps.append(diff)
    
    # 取三个质量级别中的最大响应
    ela_map = np.max(np.stack(maps, axis=0), axis=0)
    norm = self._normalize_map(ela_map)
    
    # 综合95百分位和均值计算篡改概率
    probability = float(
        min(max(
            float(np.percentile(norm, 95)) * 0.55 + 
            float(norm.mean()) * 0.45,
            0.0
        ), 1.0)
    )
    
    return probability, norm

阈值判定:

  • probability >= 0.75tampered(确认为篡改)
  • 0.45 <= probability < 0.75suspicious(可疑)
  • probability < 0.45clean(干净)

EXIF编辑痕迹:软件指纹识别

除了ELA,还检查图像中是否残留编辑软件的"指纹":

_editing_signatures = (
    "Photoshop", "Adobe", "Lightroom", "GIMP", 
    "Canva", "Meitu", "Snapseed", "ImageMagick", "FFmpeg", "8BIM"
)

def _extract_exif_editing_evidence(self, image, image_bytes):
    indicators = []
    
    # 1. 检查EXIF Software字段
    exif = image.getexif()
    software = str(exif.get(305, "")).strip()
    if software:
        indicators.append(f"EXIF Software={software}")
    
    # 2. 二进制签名扫描
    lowered = image_bytes.lower()
    for sig in self._editing_signatures:
        if sig.lower().encode("utf-8") in lowered:
            indicators.append(f"Binary signature={sig}")
            break
    
    # 3. 多个APP1段(可能表示多次编辑)
    app1_count = image_bytes.count(b"\xff\xe1")
    if app1_count > 1:
        indicators.append(f"Multiple APP1 segments={app1_count}")
    
    # 4. XMP元数据
    if b"<x:xmpmeta" in lowered:
        indicators.append("XMP metadata detected")
    
    # 计算综合分数
    score = min(0.2 + 0.1 * len(indicators), 0.95)
    return ForgeryEvidence(type="exif_trace", score=score, detail="; ".join(indicators))

热力图区域提取

当检测到篡改时,需要定位可疑区域。我使用了连通分量分析来提取最显著的5个区域:

def _extract_regions_from_ela_map(self, ela_map):
    # 盒状模糊平滑去噪
    smoothed = self._box_blur(ela_map.astype(np.float32), radius=6)
    
    # 动态阈值:88百分位或0.42取较大值
    threshold = max(float(np.percentile(smoothed, 88)), 0.42)
    mask = smoothed >= threshold
    
    # 连通分量分析
    components = self._connected_components(mask)
    
    regions = []
    for x1, y1, x2, y2, area in components:
        if area < min_pixels:
            continue
        
        region_scores = smoothed[y1:y2+1, x1:x2+1]
        score = float(np.mean(region_scores))
        
        if score >= 0.35:
            regions.append(HeatmapRegion(
                x=round(x1 / w, 4),
                y=round(y1 / h, 4),
                w=round((x2 - x1 + 1) / w, 4),
                h=round((y2 - y1 + 1) / h, 4),
                score=round(score, 4),
            ))
    
    regions.sort(key=lambda r: r.score, reverse=True)
    return regions[:5]

返回的区域坐标都是归一化的(0~1之间),方便前端绘制叠加层。


策略决策引擎:如何做出最终判断?

三个检测器各自输出结果后,需要一个"大脑"来综合判断。这就是PolicyEngine的职责。

决策规则

class PolicyEngine:
    def decide(self, gb, c2pa, forgery):
        reasons = []
        reject = False
        
        # 规则1:GB45438不合规 → 一票否决
        if not gb.compliant:
            reject = True
        
        # 规则2:C2PA验签失败 → 一票否决
        if c2pa.status == "invalid":
            reject = True
            reasons.append(f"C2PA: 验签失败({'; '.join(c2pa.errors)})")
        
        # 规则3:篡改检测为tampered/suspicious → 添加原因
        if forgery.verdict in {"suspicious", "tampered"}:
            reasons.append(
                f"篡改检测: {forgery.verdict}(概率={forgery.tamper_probability:.4f})"
            )
        
        # 规则4:EXIF编辑痕迹明显 → 添加原因
        exif_trace = next((e for e in forgery.evidence if e.type == "exif_trace"), None)
        if exif_trace and exif_trace.score >= 0.5:
            reasons.append(f"编辑痕迹: {exif_trace.detail}")
        
        # 规则5:篡改概率≥0.75 → 一票否决
        if forgery.tamper_probability >= 0.75:
            reject = True
            reasons.append(f"篡改风险高: 概率={forgery.tamper_probability:.4f}")
        
        # 去重并保持顺序
        reasons = list(dict.fromkeys(reasons))
        
        if not reasons and not reject:
            return "pass", [], True       # 全部通过
        if reject:
            return "reject", reasons, False  # 拒绝
        return "review", reasons, False      # 需要人工审核

三种决策结果

结果 含义 典型场景
pass 通过 有完整GB45438标识 + C2PA签名有效 + 无篡改迹象
review 待审核 缺少某些标识但无明显恶意行为
reject 拒绝 明确违反合规要求或高度疑似篡改

这种分级机制的好处是:既不会放过明显的违规内容,也不会因为细微瑕疵而误杀正常图片。


API接口设计

请求格式

curl -X POST "http://localhost:8000/v1/image/compliance-check" \
  -F "image=@test.jpg"

支持的文件类型:所有image/* MIME类型的文件。

响应结构

{
  "overall_compliant": false,
  "policy_level": "evidence",
  "gb45438": {
    "compliant": false,
    "risk_level": "high",
    "checks": [...]
  },
  "c2pa": {
    "status": "missing",
    "signature_valid": false,
    "errors": ["manifest not found"]
  },
  "final_decision": "reject",
  "reasons": [
    "C2PA: 验签失败(manifest not found)",
    "篡改检测: tampered(概率=0.8234)",
    "编辑痕迹: EXIF Software=Adobe Photoshop 2024; Binary signature=8BIM",
    "篡改风险高: 概率=0.8234"
  ]
}

部署与使用

环境准备

创建api/.env配置文件:

# Vision API配置(用于显式标识检测)
VISION_BASE_URL=https://your-openai-compatible-endpoint/v1
VISION_API_KEY=your_api_key
VISION_MODEL=gpt-4o-mini
VISION_DISABLE_THINKING=true

# C2PA配置
C2PA_STRICT_MODE=true

快速启动

cd api
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
uvicorn main:app --reload

访问http://localhost:8000可以看到内置的测试页面,直接上传图像即可体验检测功能。

健康检查

curl http://localhost:8000/healthz
# 返回: {"status":"ok"}

项目亮点总结

1. 双重标准兼容

同时支持中国GB45438-2025和国际C2PA标准,一套系统满足国内外不同平台的合规需求。这对于出海企业尤其有价值——不需要为不同市场维护两套检测系统。

2. 隐私保护优先

  • 无持久化存储:所有图像处理在内存中完成,不写入磁盘
  • 无额外资源暴露:热力图以overlay格式直接返回,不提供独立的URL
  • 最小化数据收集:仅返回检测结果,不记录用户身份信息

3. 智能决策而非简单规则

通过多源证据融合(元数据+视觉分析+密码学验证),做出比单一检测更可靠的判断。特别是引入Vision Model进行显式标识检测,大幅提升了准确率。

4. 模块化可扩展

每个检测器都是独立的类,可以轻松替换或升级:

  • 想换OCR引擎?修改GB45438Checker中的_judge_explicit_label_with_vision_model方法
  • 想增强篡改检测?扩展ForgeryChecker添加新的分析算法
  • 想调整决策逻辑?修改PolicyEngine中的规则权重

5. 生产级鲁棒性

  • SDK兼容性探测机制应对第三方库API变化
  • Vision API降级处理保证服务可用性
  • 完善的异常捕获和错误信息返回

应用场景

内容平台自动审核

社交媒体、图片分享平台可以集成此API,在用户上传环节自动筛查AI生成内容。对于标记为reject的图片可以直接拦截,review的图片送入人工审核队列,pass的图片正常发布。

新闻机构事实核查

新闻媒体在发布图片前,可通过此工具验证图片来源和完整性。如果C2PA签名无效且ELA检测显示篡改,就需要进一步核实图片真实性,防止虚假图像传播。

电商平台商品图监管

检测商家上传的商品图片是否经过不当修饰。虽然一定程度的修图是允许的,但过度篡改(如改变产品本质特征)可能构成欺诈,需要平台介入。

司法取证辅助

在法律案件中,此工具可作为初步筛查手段。虽然不能替代专业的司法鉴定,但能快速识别明显被篡改的图像,提高办案效率。


局限性与改进方向

当前局限

  1. ELA算法的局限性:对于非JPEG格式(如PNG、WebP)的检测效果较差,因为这些格式使用无损压缩。
  2. Vision API依赖:显式标识检测依赖外部API,存在网络延迟和成本问题。
  3. C2PA库稳定性c2pa-python仍处于实验阶段,API可能发生变化。
  4. 误报率:对于天然纹理复杂的图像(如森林、人群),ELA可能产生较高的误报。

未来改进方向

  1. 支持更多图像格式:添加对PNG、WebP等格式的专门检测算法
  2. 本地OCR备选方案:当Vision API不可用时,降级到本地OCR引擎
  3. 深度学习篡改检测:引入专门的CNN模型(如ForensicsNet)提升准确率
  4. 批量处理能力:支持异步批量检测,提升吞吐量
  5. 缓存机制:对相同图像的哈希值进行缓存,避免重复检测

结语

构建这个项目的过程中,我深刻体会到图像合规检测是一个跨学科的领域,涉及计算机视觉、密码学、自然语言处理等多个方向。没有银弹式的解决方案,只有通过多技术融合才能接近可靠的结果。

希望这篇文章能为你提供一些启发。如果你也面临类似的合规挑战,不妨从这个项目出发,根据自己的业务需求进行定制和扩展。

项目源码已开源,欢迎Star和贡献!
源代码

本文作者是一名后端工程师,专注于AI基础设施和内容安全领域。如有问题或建议,欢迎在评论区交流。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐