跳转至

不可见水印移除 — 扩散再生攻击

核心论文"Image Watermarks Are Removable Using Controllable Regeneration from Clean Noise" (ICLR 2025)

特点:需要 GPU(CUDA / MPS),首次运行下载 ~12GB 模型

设计思路

不可见水印(SynthID、StableSignature、TreeRing 等)嵌入在图像的频域/潜空间中,对裁剪、缩放、JPEG 压缩有鲁棒性。传统的信号处理方法(滤波、去噪)难以完全去除。

扩散再生攻击的核心思想:不试图"找到并删除"水印信号,而是将图像作为输入,通过扩散模型"重新生成"一张视觉上相同但潜空间不同的图像。水印在再生过程中被自然破坏。

两条管线

管线 基础模型 分辨率 特点
Default (SDXL) stabilityai/stable-diffusion-xl-base-1.0 1024px 默认,实证击败 SynthID v2
CtrlRegen SG161222/Realistic_Vision_V4.0 + ControlNet + DINOv2 512px ICLR 2025 论文方法,更精细

Default 管线(SDXL)

流程

image → resize ~1024px → VAE encode → 加噪 → denoise → VAE decode → 上采样回原始尺寸

关键实现(img2img_runner.py

pipeline(
    prompt="",              # 空提示!不引入文本语义
    image=image,            # 输入图像
    strength=strength,      # 去噪强度(默认 0.04)
    num_inference_steps=50, # 总步数
    guidance_scale=7.5,     # CFG 引导
    generator=generator,    # 固定种子可复现
)

关键设计决策

  • 空 prompt "":不引入额外文本语义,仅靠图像本身的 latent 进行再生。避免生成内容偏移
  • 极低 strength 0.04effective_steps = max(1, int(50 × 0.04)) = 2——实际只做 2 步去噪。这意味着对图像的扰动极小,视觉几乎无损,但足以破坏水印的频域模式
  • guidance_scale 7.5:标准的 CFG 值

设备自动选择

def get_device():
    if torch.cuda.is_available():
        return "cuda"
    elif torch.backends.mps.is_available():
        return "mps"
    return "cpu"

CUDA 环境下自动使用 float16,CPU/MPS 使用 float32

CUDA 自动修复

当检测到 NVIDIA GPU 存在但 torch.cuda 不可用时,WatermarkRemover 会:

  1. nvidia-smi 解析 CUDA 版本
  2. pip install 对应版本的 PyTorch
  3. os.execl() 重启当前进程
  4. 环境变量 NOAI_CUDA_FIXED 防止无限循环

CtrlRegen 管线(ICLR 2025)

架构

graph TD
    IMG[原图] --> CANNY[Canny 边缘检测]
    IMG --> DINO[DINOv2 图像编码]
    CANNY --> CN[Spatial ControlNet]
    DINO --> IPA[IP-Adapter]
    CN --> SD[SD1.5 Img2Img Pipeline]
    IPA --> SD
    SD --> OUT[再生图像]
    IMG --> COLOR[色彩匹配]
    OUT --> COLOR
    COLOR --> FINAL[最终输出]

模型加载(ctrlregen/engine.py

共加载 5 个模型组件:

组件 模型 ID 用途
Spatial ControlNet yepengliu/ctrlregen/spatialnet_ckp/ Canny 边缘→空间结构控制
基础 SD 模型 SG161222/Realistic_Vision_V4.0_noVAE 生成骨干
自定义 VAE stabilityai/sd-vae-ft-mse 编解码
DINOv2 编码器 facebook/dinov2-giant 语义特征提取(替代 CLIP)
IP-Adapter 权重 yepengliu/ctrlregen/semanticnet_ckp/ 语义注入 UNet

自定义管线(pipeline.py

class CustomCtrlRegenPipeline(
    StableDiffusionControlNetImg2ImgPipeline,
    CustomIPAdapterMixin
):
    pass

Python MRO 保证:标准方法从 diffusers 管线解析,load_ctrlregen_ip_adapter 从 mixin 添加。

IP-Adapter 实现(ip_adapter.py

  • 替换标准 CLIP 为 facebook/dinov2-giant(ViT-Giant,1.1GB)
  • 权重格式:image_proj(投影层) + ip_adapter(注入 UNet 的 cross-attention)
  • 通过 unet._load_ip_adapter_weights(state_dicts) 加载
  • 支持 .safetensors.bin 格式

单图处理流程

# 1. Canny 边缘提取
edges = CannyDetector(image, low_threshold=100, high_threshold=150)

# 2. 管线调用
pipeline(
    prompt="best quality, high quality",
    negative_prompt="monochrome, lowres, bad anatomy, worst quality, low quality",
    image=image,
    control_image=edges,
    controlnet_conditioning_scale=1.0,
    control_guidance_start=0.0,
    control_guidance_end=1.0,
    ip_adapter_image=[image],  # 原图作为语义参考
    guidance_scale=2.0,         # 比 SDXL 的 7.5 低很多
    strength=0.04,
)

分块处理(tiling.py

大图(>512px)分块处理避免显存溢出:

tile_size = 512
overlap = 192

# 余弦渐变混合权重
def make_blend_weight(h, w, overlap):
    # 中心 = 1.0,边缘 = cosine ramp
    weight = np.ones((h, w), dtype=np.float32)
    for i in range(overlap):
        alpha = 0.5 * (1 + np.cos(np.pi * i / overlap))
        weight[i, :] *= alpha
        weight[h-1-i, :] *= alpha
        weight[:, i] *= alpha
        weight[:, w-1-i] *= alpha
    return weight

每个 tile 处理后加权累加到 canvas,最后除以权重总和归一化。

色彩匹配(color.py

扩散再生会偏移色彩,用 color-matcher 库校正:

from color_matcher import ColorMatcher
cm = ColorMatcher()
matched = cm.transfer(src=regen_img, ref=original_img, method='hm-mkl-hm')

hm-mkl-hm = 两遍直方图匹配 + MKL 传输。

InvisibleEngine 外层编排

invisible_engine.py 将上述所有组件串联:

graph TD
    A[输入图像] --> B[EXIF 旋转修正]
    B --> C[下采样到 1024px]
    C --> D[保存临时文件]
    D --> E[Phase 1: YOLO 人脸提取]
    E --> F[WatermarkRemover 移除水印]
    F --> G[Phase 2: 人脸恢复]
    G --> H{humanize > 0?}
    H -->|是| I[Analog Humanizer]
    H -->|否| J[跳过]
    I --> K[上采样回原始分辨率]
    J --> K
    K --> L[输出清洁图像]

关键参数:

参数 默认值 说明
strength 0.04 去噪强度
num_inference_steps 50 总扩散步数
guidance_scale 7.5 (SDXL) / 2.0 (CtrlRegen) CFG 引导强度
humanize 0.0 胶片噪声强度(0=不启用)

强度预设(watermark_profiles.py

水印类型 Strength 说明
StableSignature 0.04 低扰动
DwtEctSVD 0.04 低扰动
RivaGAN 0.04 低扰动
SSL 0.04 低扰动
Hidden 0.04 低扰动
通用默认 0.35 中等
StegaStamp 0.7 高扰动
TreeRing 0.7 高扰动
RingID 0.7 高扰动