图像水印技术最初是为了知识产权保护和复制保护而开发的,如好莱坞工作室对DVD的水印。随着生成性AI模型的发展,水印的应用也在演变。例如,美国白宫的行政命令、加州法案、欧盟AI法案和中国AI治理规则都要求AI生成的内容能够被轻松识别,并将水印作为检测和标记AI生成图像的推荐或强制措施。
这篇论文试图解决的是图像水印技术在处理小面积水印和图像部分编辑时的限制问题。具体来说,论文中提到传统图像水印技术主要面临的挑战包括:
为了解决这些问题,论文《Watermark Anything with Localized Messages》提出了一种名为“Watermark Anything Model (WAM)”的深度学习模型,用于局部图像水印。
WAM的目标是将水印信号的强度与其像素表面面积解耦,与传统水印技术不同。WAM模型包括一个嵌入器和一个提取器。嵌入器用于将信息不可见地嵌入图像像素中,提取器用于分割接收到的图像成水印和非水印区域,并从被检测为水印的区域中恢复一个或多个隐藏消息。
通过这种方式,WAM能够提供新的功能,例如定位拼接图像中的水印区域,并从多个小区域中提取不同的32位消息,即使这些区域不超过图像面积的10%。
WAM将水印任务重新定义为一个分割任务,这意味着它不仅仅检测整个图像是否含有水印,而是能够识别出图像中哪些具体的像素被水印了。
这种方法与传统的水印技术不同,后者通常只对整个图像做出全局决策。WAM的提取器为每个像素输出一个向量,指示该像素是否被水印以及水印中隐藏的消息。
WAM的嵌入器负责将水印信息嵌入到图像中,同时确保这种嵌入对肉眼是不可见的。嵌入器由以下几个部分组成:
watermarked_image = original_image + α * watermark_signal
。
提取器的任务是检测图像中的水印,并从中提取嵌入的消息。提取器的结构类似于图像分割网络,由以下几个主要部分组成:
目标: 在第一阶段,目标是获得一个鲁棒且可定位的局部水印,该水印可以在图像的一部分中隐藏一个 nbitsnbits 位的消息,而不太在意水印的不可感知性。这一阶段不包括任何感知损失,目的是在经过严重增强后实现完美的定位和解码。
过程:
损失函数:
ℓdet(θ)=−1h×w∑i=1h×w[yidet,⋆log(yidet(θ))+(1−yidet,⋆)log(1−yidet(θ))]ℓdet(θ)=h×w−1i=1∑h×w[yidet,⋆log(yidet(θ))+(1−yidet,⋆)log(1−yidet(θ))]
ℓdec(θ)=−1nbits×∑i=1h×wyidet,⋆∑i=1h×wyidet,⋆∑k=1nbits[mklog(yi,kdec(θ))+(1−mk)log(1−yi,kdec(θ))]ℓdec(θ)=nbits×∑i=1h×wyidet,⋆−1i=1∑h×wyidet,⋆k=1∑nbits[mklog(yi,kdec(θ))+(1−mk)log(1−yi,kdec(θ))]
目标: 在第二阶段,目标是解决第一阶段训练出的模型水印可见性问题,并使其能够处理图像中的多个水印。
过程:
损失函数:
DBSCAN算法在WAM中被用于从图像中提取多个水印。以下是使用DBSCAN算法提取多个水印的详细步骤:
DBSCAN算法会输出一些中心点和每个被识别为水印像素的像素的分配。这些中心点代表最终解码的消息,它们是二进制单词。
在WAM的训练过程中,可能会在同一图像中嵌入多个不同的水印。使用DBSCAN算法,可以不需要预先知道隐藏消息的数量,就能从局部解码消息中识别出不同的水印区域。
WAM处理多水印的关键在于其训练过程和提取器的设计,使其能够识别和解码图像中的多个独立水印。以下是处理多水印的具体步骤:
WAM通过固定分辨率操作和插值技术来处理高分辨率图像,以下是具体步骤:
Just-Noticeable-Difference(JND)图是一种模拟人眼视觉系统的敏感度的模型,它用于确定图像中每个像素的最大可感知变化量。
JND图基于两个主要的视觉现象:亮度适应(Luminance Adaptation,LA)和对比度掩蔽(Contrast Masking,CM)。
JND图的计算通常涉及以下步骤:
可以看到,使用JND图调制水印强度的图像,相较于不使用JND图,与原始图像具有更小的差异,不使用JND图的水印图像有明显的噪声。
我们的实验部署配置如下: GPU 3090 * 4 Ubuntu 20.04 PyTorch 2.1.2 Python 3.10 Cuda 11.8
# 导入所需的库
import os
import numpy as np
from PIL import Image
import torch
import torch.nn.functional as F
from torchvision.utils import save_image
# 导入特定于项目的函数和类
from watermark_anything.data.metrics import msg_predict_inference
from notebooks.inference_utils import (
load_model_from_checkpoint,
default_transform,
create_random_mask,
unnormalize_img,
plot_outputs,
msg2str
)
# 检测是否有可用的CUDA设备,如果没有则使用CPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 定义一个函数来加载图像
def load_img(path):
# 使用PIL库打开图像,并将其转换为RGB格式
img = Image.open(path).convert("RGB")
# 应用默认的图像转换
img = default_transform(img).unsqueeze(0).to(device)
return img
# 从指定的检查点加载模型
exp_dir = "checkpoints"
json_path = os.path.join(exp_dir, "params.json")
ckpt_path = os.path.join(exp_dir, 'checkpoint.pth')
wam = load_model_from_checkpoint(json_path, ckpt_path).to(device).eval()
# 设置随机种子以确保结果的可重复性
seed = 42
torch.manual_seed(seed)
# 设置参数
img_dir = "assets/images" # 包含原始图像的目录
num_imgs = 2 # 从文件夹中水印图像的数量
proportion_masked = 0.5 # 图像被水印的比例(0.5表示50%的图像)
# 创建输出文件夹
output_dir = "outputs"
os.makedirs(output_dir, exist_ok=True)
# 定义一个32位的消息,将其嵌入到图像中
wm_msg = wam.get_random_msg(1) # [1, 32]
print(f"Original message to hide: {msg2str(wm_msg[0])}")
# 遍历目录中的每个图像
for img_ in os.listdir(img_dir)[:num_imgs]:
# 加载并预处理图像
img_pt = load_img(os.path.join(img_dir, img_)) # [1, 3, H, W]
# 将水印信息嵌入到图像中
outputs = wam.embed(img_pt, wm_msg)
# 创建一个随机掩码,只水印图像的一部分
mask = create_random_mask(img_pt, num_masks=1, mask_percentage=proportion_masked) # [1, 1, H, W]
img_w = outputs['imgs_w'] * mask + img_pt * (1 - mask) # [1, 3, H, W]
# 在水印图像中检测水印
preds = wam.detect(img_w)["preds"] # [1, 33, 256, 256]
mask_preds = F.sigmoid(preds[:, 0, :, :]) # [1, 256, 256], 预测掩码
bit_preds = preds[:, 1:, :, :] # [1, 32, 256, 256], 预测比特
# 预测嵌入的消息并计算比特准确率
pred_message = msg_predict_inference(bit_preds, mask_preds).cpu().float() # [1, 32]
bit_acc = (pred_message == wm_msg).float().mean().item()
# 保存水印图像和检测掩码
mask_preds_res = F.interpolate(mask_preds.unsqueeze(1), size=(img_pt.shape[-2], img_pt.shape[-1]), mode="bilinear", align_corners=False) # [1, 1, H, W]
save_image(unnormalize_img(img_w), f"{output_dir}/{img_}_wm.png")
save_image(mask_preds_res, f"{output_dir}/{img_}_pred.png")
save_image(mask, f"{output_dir}/{img_}_target.png")
plot_outputs(img_pt.detach(), img_w.detach(), mask.detach(), mask_preds_res.detach(), labels = None, centroids = None)
# 打印每张图像的预测消息和比特准确率
print(f"Predicted message for image {img_}: {msg2str(pred_message[0])}")
print(f"Bit accuracy for image {img_}: {bit_acc:.2f}")
我们以下面的图像为例,添加的水印编码为01000100010000101110101111111100。
下图是嵌入水印部分的图像掩码与提取器预测的图像区域,提取器基本可以完全预测出水印位置,且提取的水印的比特准确率为100%。
我们将原始图像、水印图像和两图像的差异进行对比,可以看到,两图像在添加水印的掩码处存在差异,但原始图像与水印图像几乎不能用肉眼分辨出差异,算法具有良好的不可见性。
我们在图像中嵌入两个水印进行进一步的测试,并进行掩码和聚类的可视化,得到了如下的结果。
上图嵌入的水印为11101000001111101101010110000000和01101111010111010101001011111111,提取到的水印为11101000001111101101010110000000和11101111010111010101001011011111,比特准确率分别为100%和93.75%。
上图嵌入的水印为11101000001111101101010110000000和01101111010111010101001011111111,提取到的水印为11101000001111101101010110000000和01101111010111010101001011111111,比特准确率分别为100%和100%。
本文介绍了一种名为Watermark Anything Model (WAM)的深度学习模型,用于实现局部图像水印技术。WAM能够在保持图像不可见性的同时,对输入图像进行修改,并在接收到的图像中分割出水印和非水印区域,从而恢复隐藏的信息。
该模型经过两阶段训练,首先在低分辨率下进行训练以实现鲁棒性,然后在高分辨率下进行微调以提高不可见性,并能够处理多个水印。实验结果表明,WAM在不可见性和鲁棒性方面与现有最先进方法相当,特别是在对抗图像修复和拼接方面表现出色,即使在高分辨率图像上也能准确地定位水印区域,并从多个小区域中提取不同的32位消息。
总体而言,这篇论文提出了一种新的图像水印方法,能够在保持不可见性的同时提供对编辑操作的鲁棒性,并引入了定位和提取多个水印的新功能,为图像水印领域带来了新的视角和技术进步。
我们在附件中,在论文原代码的基础上,实现了一键的训练、推理、可视化,由于COCO数据集较大,因此读者若要自行训练模型,需下载COCO数据集。