广告位 · 页头(后台-模块管理-新建模块后替换此区域)

如何用ControlNet把二维码藏进艺术画作?

想把二维码藏进一张完整的插画里,很多人会先想到降低二维码透明度后叠在画面上,但这种方式生成的结果往往要么扫不出来,要么看起来像一块突兀的码。ControlNet 的二维码控制模型走的是另一条路:它不直接覆盖二维码,而是把二维码图像作为扩散模型的结构条件,让模型在去噪的每一步都参考码点的明暗布局,同时用文本提示词控制画面内容、风格和颜色。最终图像是一张从纹理到构图都被重新组织过的画作,而二维码信息则被编码在画面的明暗关系里。这里的重点不是叠加,而是让二维码模块成为画面生成的一部分。

如何用ControlNet把二维码藏进艺术画作?

一、控制原理:为什么能同时像画又像码

二维码识别的核心依赖三个定位角,以及数据区中黑白模块之间的对比。只要这些明暗关系在缩放和拍摄后仍能保持足够差异,解码器就可以还原信息。ControlNet 的 qrcode_monster 模型在训练时学习到一种映射:输入一张二维码灰度图,输出与主模型中间特征同尺度的残差控制信号。生成过程中,条件图像会经过与编码器对应的控制分支,把每一层的特征加到 UNet 的对应层,使画面中的高亮区域倾向落在二维码白色模块附近,阴影区域倾向落在黑色模块附近。

这样做的好处是,二维码不再以硬边方块的形式存在。模型可以用云层、水流、建筑窗户、服饰纹理等自然形状去表达这些明暗差异。因此生成图的第一眼观感是艺术画作,但如果把图片缩小、转成灰度并加强对比度,二维码的模块结构会重新显现。与简单叠加相比,这种融合方式能保留更多细节,同时降低贴图感。

不过控制信号并不是越强越好。当控制权重设置过高,画面会被迫保留明显的二维码网格,艺术感下降;权重过低,码点又容易被提示词内容冲散,导致扫描失败。后文会围绕这个平衡点展开。

二、准备模型与单张生成流程

在 Python 环境中,需要安装 diffusers、transformers、accelerate、opencv-python、qrcode 和 Pillow。基础扩散模型可以选择 Stable Diffusion 1.5 系列,写实或二次元画风均可。ControlNet 使用 monster-labs 发布的 control_v1p_sd15_qrcode_monster,它专门针对二维码结构做了训练。实际部署时,如果使用 diffusers,建议将 ControlNetModel 和 StableDiffusionControlNetPipeline 组合加载,并切换到 UniPCMultistepScheduler 以提高采样效率。

import torch
import cv2
import qrcode
from PIL import Image
from diffusers import StableDiffusionControlNetPipeline, ControlNetModel, UniPCMultistepScheduler
# 加载 ControlNet 和 Stable Diffusion 1.5
controlnet = ControlNetModel.from_pretrained(
    "monster-labs/control_v1p_sd15_qrcode_monster",
    torch_dtype=torch.float16
)
pipe = StableDiffusionControlNetPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    controlnet=controlnet,
    torch_dtype=torch.float16,
    safety_checker=None
)
pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
pipe.enable_model_cpu_offload()
# 生成二维码灰度图
qr = qrcode.QRCode(
    version=5,
    error_correction=qrcode.constants.ERROR_CORRECT_H,
    box_size=16,
    border=4
)
qr.add_data("https://ipipp.com")
qr.make(fit=True)
qr_img = qr.make_image(fill_color="black", back_color="white").convert("L")
control_image = qr_img.resize((512, 512))

这里的二维码不是直接输出给用户看的尺寸,而是作为控制条件。使用高容错级别 ERROR_CORRECT_H 可以允许最多约 30% 的码点被破坏,这对后续艺术化处理非常重要。二维码最终会被缩放到 512×512 或 768×768,与扩散模型的潜在空间尺寸对应。如果二维码有较多数据,version 要相应调大,否则信息会因模块过密而难以融入画面。

接下来调用管线。提示词要描述完整场景,例如山间晨雾、赛博朋克街道、水彩花卉等,同时避免使用大量细碎文字或噪点描述,因为这些内容容易与二维码模块冲突。负面提示词可以加入 low quality、blurry、text、watermark 等,减少干扰。

prompt = "a beautiful mountain lake with soft morning fog, high detail, ink painting style"
negative_prompt = "low quality, blurry, text, watermark, deformed"
image = pipe(
    prompt=prompt,
    negative_prompt=negative_prompt,
    image=control_image,
    controlnet_conditioning_scale=0.95,
    guidance_scale=7.5,
    num_inference_steps=30,
    width=512,
    height=512
).images[0]
image.save("art_qrcode.png")

生成后不要立刻结束,最好用 OpenCV 或 pyzbar 做一次扫描验证。由于艺术二维码的模块已经融入纹理,直接扫描原图有可能失败,通常需要将图片缩小、灰度化并增加对比度后再测试。这个过程能从视觉上快速判断哪些区域破坏了二维码结构,为后续调参提供依据。

三、关键参数与批量筛选方案

控制强度 controlnet_conditioning_scale 是最影响结果的参数。把它设到 1.1 以上,二维码的模块边界会更清晰,但画面会显得生硬;设到 0.7 以下,画面自由度提高,但码点容易被画作内容淹没。推荐从 0.85 到 1.0 之间开始,不同基础模型和提示词需要微调。guidance_scale 控制文本提示词的约束力,过高的文本引导会削弱二维码结构,一般保持在 6.5 到 8.5 较为稳妥。

另一个容易被忽略的预处理是控制图像的模糊程度。直接使用锐利的二维码会让 ControlNet 倾向保留完整格线。可以在送入管线前对 control_image 做一次轻微高斯模糊,或者先缩小再放大,这样能在保留模块分布的同时弱化边缘。示例代码如下:

import numpy as np
control_array = np.array(control_image)
blurred = cv2.GaussianBlur(control_array, (5, 5), 0)
control_image = Image.fromarray(blurred)

批量生成时,建议固定二维码内容,对每张候选图执行二维码检测。检测器可以用 OpenCV 的 QRCodeDetector。对于通过检测的结果,计算解码文本是否与目标一致;如果一致,保留图片并记录控制权重、种子和检测分数。下面是一个批量筛选框架,实际使用时可以替换提示词和种子范围。

import random
import numpy as np
detector = cv2.QRCodeDetector()
best_score = 0
best_info = None
for seed in range(6):
    generator = torch.Generator(device="cuda").manual_seed(seed)
    result = pipe(
        prompt=prompt,
        negative_prompt=negative_prompt,
        image=control_image,
        controlnet_conditioning_scale=0.9 + random.uniform(-0.05, 0.08),
        guidance_scale=7.5,
        num_inference_steps=28,
        generator=generator
    ).images[0]
    gray = cv2.cvtColor(np.array(result), cv2.COLOR_RGB2GRAY)
    data, points, _ = detector.detectAndDecode(gray)
    if data == "https://ipipp.com":
        # 记录解码分数并保留候选
        print(f"seed {seed} 可扫描")
        if len(points) > best_score:
            best_score = len(points)
            best_info = (seed, result)

上面代码中的 np.array(result) 用于将 PIL 图像转成 OpenCV 需要的数组。批量筛选的核心思想不是追求每张都能直接识别,而是利用检测器自动淘汰不可扫的候选,再人工挑选艺术效果最好的图片。扫描验证时,可以尝试多尺度图像,有的图在 600 像素宽时扫不到,但缩到 300 像素宽时反而可以识别,因为缩小会削弱细节纹理,让模块对比更突出。

四、提升画面质量与扫描率的实用技巧

艺术二维码最常见的失败原因是三个定位角被画面元素破坏。定位角一旦缺失,即使数据区完整,解码器也无法建立坐标系。可以在生成时通过提示词控制边缘区域,或对二维码做局部增强:在 control_image 中将三个定位角附近做轻微锐化,使模型更关注这些区域。也可以使用 ControlNet 的权重分段控制,让控制信号在去噪前期更强,后期逐步减弱,从而在保持结构的同时提升纹理自然度。

不同基础模型对二维码控制模型的响应不同。写实类模型容易把二维码模块误认为窗户、砖块等硬质结构,适合生成建筑、街景;二次元模型更容易将明暗融入色块和线条,适合插画、动漫风。不要只依赖一个模型,同一个提示词在多模型下可能完全不同。建议先固定控制参数,批量测试基础模型的表现,再根据画面风格反向调整提示词。

二维码容错级别也值得单独调整。ERROR_CORRECT_H 允许更多模块损坏,但也会让二维码密度增大,因为纠错码占用了更多模块。如果画面风格比较简洁,可以尝试 ERROR_CORRECT_M 或 Q,它们在保证一定容错的同时减少总模块数,让控制条件更宽松。最终目标是让二维码信息在视觉上足够隐蔽,同时在手机摄像头下保持稳定识别。

最后需要注意使用场景。将二维码隐藏进画作后,如果用于活动海报、支付入口或个人作品展示,应保证用户能明确意识到这是二维码,以免产生误导。不要使用该技术伪造支付二维码或诱导扫码,避免安全风险。

广告位 · 文章底部(后台-模块管理替换此区域)