【传知代码】基于扩散模型的无载体图像隐写术(论文复现)

34 阅读9分钟

前言:当涉及信息隐藏和安全传输时,无载体图像隐写术是一个备受关注的热门话题。借助扩散模型,这种隐写术为信息的隐藏和嵌入提供了全新的可能性。在本文中,我们将深入探讨基于扩散模型的无载体图像隐写术,并探讨其原理、应用和未来发展。通过这篇文章,我们将揭开这一令人着迷的领域的面纱,为读者带来全新的知识和见解。

本文所涉及所有资源均在传知代码平台可获取

概述

目前,图像隐写技术主要是基于载体图(cover image)的方法,这些方法通常存在泄露隐藏图(secret image)的风险,以及对退化容器图(container image)不鲁棒的风险。受到最近扩散模型(diffusion models)进展的影响,作者揭示了扩散模型所具备的两大特点:一是能够在不进行训练的情况下完成两幅图像间的转换,二是对有噪声的数据具有天生的稳健性。这些建议的特点有助于提高图像隐写任务的安全与稳健性。这是第一次尝试将扩散模型融入到图像隐写的研究中。相较于基于载体图的图像隐写技术,作者所提出的CRoSS框架在控制能力、稳健性以及安全性上都展现出了显著的优越性。

论文:地址,讲述了图像隐写的内容:

图片.png 在本论文中,无载体图影像隐写任务由三影像与两过程所构成:此三影像指代秘密影像xsec,容器影像xcont与揭示影像xrev;此两过程则分别为隐藏历程与揭示历程。我们希望将秘密图像xsec隐藏起来,并在容器图像xcont中进行隐藏操作。在通过互联网进行传输之后,容器图像xcont有可能会出现退化现象,因此我们得到了容器图像的退化图像x’cont,并通过这一过程来提取并展示图像xrev。根据上述定义,我们可以将隐藏过程视为秘密图像xsec和容器图像xcont之间的翻译,将揭示过程视为隐藏过程的反向过程,如下图所示:

1712046470273_截屏20240402162738.png

文中采用条件扩散模型对秘密图像加密,使其变换到容器图像上,利用DDIM反转实现图像分布与噪声分布的双向变换,使可逆图像转换成为可能。这种方式使容器图像可以顺利地恢复到秘密图像,如下图所示:

1712056031137_截屏20240402190639.png

原理实现方法

CRoSS是一种基于扩散模型的无载体图像隐写术,其隐藏过程的实现原理主要包括以下几个步骤,通过以下步骤,CRoSS能够实现对信息的隐写和提取,保障信息的安全传输并同时保持图像质量:

1)提取图像特征:CRoSS首先会对载体图像进行特征提取,以获取图像的空间域和频域特征。

2)分割载体图像:接下来,CRoSS将载体图像分割成不同的区域,以便在每个区域中隐藏待传输的信息。

3)加密待隐藏信息:在隐藏信息之前,CRoSS会对待隐藏信息进行加密,以增加信息的安全性。

4)信息嵌入:在每个区域中,CRoSS会通过扩散模型将加密后的信息嵌入到载体图像中,利用图像的变换特性,在不影响图像质量的情况下隐藏信息。

5)提取隐藏信息:接收方在接收到带隐藏信息的图像后,通过扩散模型的逆过程,提取隐藏的信息,并解密以获取原始信息。

利用DDIM前后左右流程处理秘密图像获取容器图像。首先以一私钥为条件实现秘密图像的加噪(前向过程);然后以一公钥为条件实现去噪(后向过程),这将产生可在网络中扩散的容器图像。私钥被用来描述秘密图像的内容,而公钥则被用来控制容器图像中的内容,如下图所示,prompt1是私钥,prompt2是公钥。并列的三幅图中,第一幅是秘密图像,第二幅是容器图像,第三幅是揭示图像:

1712058219944_截屏20240402194331.png

通过输入内容,隐藏的秘密图像xsec、带有噪声估计器εθ的预训练条件扩散模型、采样时间步数T,以及作为私钥和公钥的两种不同条件kpri和kpub。输出内容为用于掩盖xsec秘密图像的xcont容器图像:

1712056349315_截屏20240402191158.png

核心代码实现

实验设置:在实验中,我们选择了稳定的Stable Diffusion v1.5作为条件扩散模型,并采用了确定性DDIM采样方法。在此基础上,提出一个基于时间序列数据的随机过程理论框架,用来研究具有任意形状特征的可逆信息隐藏系统中秘密图像变换的问题。秘密图像产生的噪声图和由噪声图产生的容器图都是由50个步骤构成的。在每个过程中,我们分别对随机产生的像素值进行计算,从而获得该过程中待变换部分的信息。为了达到可逆的图像转化效果,我们决定将稳定扩散的导向刻度调整为1。在此基础上,我们给出了一个基于随机梯度下降法的密钥分发方案。关于作为私钥和公钥的特定条件,我们提供了三种选择:prompts(作为提示)、ControlNets条件(depth maps scribbles segmentation maps)以及LoRAs。

数据准备:在实验中,我们共收集了260张图片,并为无载体图像隐写技术设计了一个名为Stego260的提示词。该系统可以帮助研究者从大量图像中快速提取出感兴趣区域信息,从而提高研究效率。该实验把数据集划分为三大类别:人类、动物以及其他常见物品,例如建筑、植物、食品和家具等。这些图像是由不同领域中的专家通过对其进行分析而获取的信息。公开的数据集和谷歌搜索引擎提供了数据集中的图像。实验证明,在这些图像中嵌入一个简单而有效的标记符可以得到很好的效果。为了产生提示密钥,我们采用BLIP来创建私钥,并通过ChatGPT或手动调整来进行语义的修改,从而批量产生公钥。下面的图片展示了如何利用ChatGPT来生成公钥的步骤:

1712059777979_截屏20240402200923.png

核心代码实现如下:

class ODESolve:

    def __init__(self, model, NUM_DDIM_STEPS=50):
        scheduler = DDIMScheduler(beta_start=0.00085, beta_end=0.012, beta_schedule="scaled_linear", clip_sample=False,
                                  set_alpha_to_one=False)
        self.model = model
        self.num_ddim_steps = NUM_DDIM_STEPS
        self.tokenizer = self.model.tokenizer
        self.model.scheduler.set_timesteps(self.num_ddim_steps)
        self.prompt = None
        self.context = None

    def prev_step(self, model_output: Union[torch.FloatTensor, np.ndarray], timestep: int, sample: Union[torch.FloatTensor, np.ndarray]):
        prev_timestep = timestep - self.scheduler.config.num_train_timesteps // self.scheduler.num_inference_steps
        alpha_prod_t = self.scheduler.alphas_cumprod[timestep]
        alpha_prod_t_prev = self.scheduler.alphas_cumprod[prev_timestep] if prev_timestep >= 0 else self.scheduler.final_alpha_cumprod
        beta_prod_t = 1 - alpha_prod_t
        pred_original_sample = (sample - beta_prod_t ** 0.5 * model_output) / alpha_prod_t ** 0.5
        pred_sample_direction = (1 - alpha_prod_t_prev) ** 0.5 * model_output
        prev_sample = alpha_prod_t_prev ** 0.5 * pred_original_sample + pred_sample_direction
        return prev_sample
    
    def next_step(self, model_output: Union[torch.FloatTensor, np.ndarray], timestep: int, sample: Union[torch.FloatTensor, np.ndarray]):
        timestep, next_timestep = min(timestep - self.scheduler.config.num_train_timesteps // self.scheduler.num_inference_steps, 999), timestep
        alpha_prod_t = self.scheduler.alphas_cumprod[timestep] if timestep >= 0 else self.scheduler.final_alpha_cumprod
        alpha_prod_t_next = self.scheduler.alphas_cumprod[next_timestep]
        beta_prod_t = 1 - alpha_prod_t
        next_original_sample = (sample - beta_prod_t ** 0.5 * model_output) / alpha_prod_t ** 0.5
        next_sample_direction = (1 - alpha_prod_t_next) ** 0.5 * model_output
        next_sample = alpha_prod_t_next ** 0.5 * next_original_sample + next_sample_direction
        return next_sample
    
    def get_noise_pred_single(self, latents, t, context):
        noise_pred = self.model.unet(latents, t, context)["sample"]
        return noise_pred

    def get_noise_pred(self, latents, t, is_forward=True, context=None):
        if context is None:
            context = self.context
        guidance_scale = GUIDANCE_SCALE
        uncond_embeddings, cond_embeddings = context.chunk(2)
        noise_pred_uncond = self.model.unet(latents, t, uncond_embeddings)["sample"]
        noise_prediction_text = self.model.unet(latents, t, cond_embeddings)["sample"]
        noise_pred = noise_pred_uncond + guidance_scale * (noise_prediction_text - noise_pred_uncond)
        if is_forward:
            latents = self.next_step(noise_pred, t, latents)
        else:
            latents = self.prev_step(noise_pred, t, latents)
        return latents

    @torch.no_grad()
    def latent2image(self, latents, return_type='np'):
        latents = 1 / 0.18215 * latents.detach()
        image = self.model.vae.decode(latents)['sample']
        if return_type == 'np':
            image = (image / 2 + 0.5).clamp(0, 1)
            image = image.cpu().permute(0, 2, 3, 1).numpy()[0]
            image = (image * 255).astype(np.uint8)
        return image

    @torch.no_grad()
    def image2latent(self, image):
        with torch.no_grad():
            if type(image) is Image:
                image = np.array(image)
            if type(image) is torch.Tensor and image.dim() == 4:
                latents = image
            else:
                image = torch.from_numpy(image).float() / 127.5 - 1
                image = image.permute(2, 0, 1).unsqueeze(0).to(device)
                latents = self.model.vae.encode(image)['latent_dist'].mean
                latents = latents * 0.18215
        return latents

    @torch.no_grad()
    def init_prompt(self, prompt: str):
        uncond_input = self.model.tokenizer(
            [""], padding="max_length", max_length=self.model.tokenizer.model_max_length,
            return_tensors="pt"
        )
        uncond_embeddings = self.model.text_encoder(uncond_input.input_ids.to(self.model.device))[0]
        text_input = self.model.tokenizer(
            [prompt],
            padding="max_length",
            max_length=self.model.tokenizer.model_max_length,
            truncation=True,
            return_tensors="pt",
        )
        text_embeddings = self.model.text_encoder(text_input.input_ids.to(self.model.device))[0]
        self.context = torch.cat([uncond_embeddings, text_embeddings])
        self.prompt = prompt

    @torch.no_grad()
    def get_text_embeddings(self, prompt: str):
        text_input = self.model.tokenizer(
            [prompt],
            padding="max_length",
            max_length=self.model.tokenizer.model_max_length,
            truncation=True,
            return_tensors="pt",
        )
        text_embeddings = self.model.text_encoder(text_input.input_ids.to(self.model.device))[0]
        return text_embeddings

运行ReadMe文件中的以下代码,快速运行代码进行图片加密解密,可以发现,隐写后的图片自然且清晰度高,不易被察觉到隐藏了秘密图像,揭示图像还原度高,经过网络传输后仍然能够很好地被还原:

图片.png

写在最后

基于扩散模型的无载体图像隐写术通过利用图像的变换特性,实现了在不影响图像质量的情况下隐藏信息的功能。该技术可以保障信息的安全传输,并且具有一定的隐蔽性,能够抵抗一定程度的攻击和分析。同时,由于采用无载体图像的形式进行信息隐藏,使得隐藏的信息更加难以检测和识别,提高了信息的安全性。

基于扩散模型的无载体图像隐写术在信息安全领域具有广阔的应用前景。未来可以通过结合深度学习技术,进一步提高隐写术的安全性和隐蔽性,使其在工业领域、军事领域和个人隐私保护等方面发挥更加重要的作用。此外,还可以探索将此技术应用于其他多媒体数据的隐写,如音频、视频等,拓展其在多领域的应用,为信息安全提供更丰富的保障手段。

详细复现过程的项目源码、数据和预训练好的模型可从该文章下方附件获取

【传知科技】关注有礼     公众号、抖音号、视频号

图片.png