ComfyUI-QwenImageLayered
ComfyUI custom nodes implementing DiffSynth V2 call interface for Qwen-Image-Layered-Control-V2 with layered image decomposition and brush control support. (Description by CC)
ComfyUI-QwenImageLayered
当前状态:DiffSynth V2 原型已跑通,但它的
context_image是软条件引导,不是 ComfyUI 原生意义上的硬遮罩 / inpaint mask。它适合验证官方 V2 调用链和模型兼容性,不建议作为“精确按遮罩分层提取”的最终方案。
当前结论
本仓库当前实现的是 DiffSynth 官方 V2 调用方式的 ComfyUI 节点原型。经过本地 workflow 验证:
v1_model可以复用 ComfyUI 单文件模型:models/diffusion_models/qwen_image_layered_control_bf16.safetensors。v2_lora可以复用 ComfyUI 单文件 LoRA:models/loras/Qwen-Image-Layered-Control-V2.safetensors。- ComfyUI 的
qwen_2.5_vl_7b_fp8_scaled.safetensors不能直接作为 DiffSynth text encoder 使用,需要官方Qwen/Qwen-Image/text_encoder/model*.safetensors分片缓存。 - ComfyUI 的
qwen_image_layered_vae.safetensors不能直接作为 DiffSynth Layered VAE 使用,需要官方Qwen/Qwen-Image-Layered/vae/diffusion_pytorch_model.safetensors。 context_image只能影响生成方向,不能保证严格保留 / 提取 mask 区域;如果输出表现为裁掉、重绘或偏离画笔区域,这是该路线的已知限制。
因此,当前代码建议作为 DiffSynth V2 prototype 提交保留;下一阶段应开新分支,回到原生 ComfyUI workflow 链路上扩展遮罩能力。
面向 Qwen-Image-Layered-Control-V2 的 ComfyUI 自定义节点,按 DiffSynth 官方 V2 调用方式实现:
pipe.load_lora(pipe.dit, ...)
pipe(
prompt=...,
layer_input_image=input_image,
layer_num=0,
context_image=brush_context,
num_inference_steps=10,
cfg_scale=1.0,
)
当前设计
当前 DiffSynth 原型不改造 ComfyUI 原生 LoadV2LoRA / KSampler 链路。V2 的画笔控制来自 DiffSynth 的 context_image 参数,原生 Qwen layered workflow 目前没有等价输入,所以这里直接走 DiffSynth QwenImagePipeline。后续原生 mask 方案会在独立分支中验证,不混入这个原型节点。
节点
QwenImage Layered V2 Loader
加载 V1 主模型、官方 Qwen text encoder、官方 Layered VAE,并在 V1 上加载 V2 LoRA。
模型选择方式和 ComfyUI 原生加载节点一致:节点通过 folder_paths 从 ComfyUI/models 下索引文件,UI 里只显示相对文件名,不需要填写 /opt/comfyui/models/... 或 E:\AI\ComfyUI_Docker\models\... 绝对路径。
默认模型配置:
| 参数 | 默认值 |
| --- | --- |
| v1_model | qwen_image_layered_control_bf16.safetensors |
| v2_lora | Qwen-Image-Layered-Control-V2.safetensors |
| allow_official_download | False |
| download_source | modelscope |
v1_model 和 v2_lora 会解析为本地文件路径并传给 DiffSynth 的 ModelConfig(path=...),不会触发这些权重的重复下载。
注意:ComfyUI 常用的 qwen_2.5_vl_7b_fp8_scaled.safetensors 是 ComfyUI FP8 重打包 text encoder,DiffSynth 官方 QwenImagePipeline 不能识别这个文件。text encoder 需要使用官方 Qwen/Qwen-Image 的 text_encoder/model*.safetensors 分片。默认 allow_official_download=False,缺少官方 text encoder/tokenizer 缓存时会直接报错,不会静默下载;需要自动补齐时再打开这个开关。
同理,ComfyUI 的 qwen_image_layered_vae.safetensors 和 DiffSynth 官方 VAE 参数命名不一致,不能直接用于 QwenImagePipeline。Layered VAE 使用官方 Qwen/Qwen-Image-Layered/vae/diffusion_pytorch_model.safetensors 缓存。
QwenImage Layered V2 Brush Context
把 ComfyUI 的 MASK 编码成 V2 需要的 RGBA context_image:
| 输入 | 作用 |
| --- | --- |
| target_mask | 红色通道,表示要提取的区域 |
| remove_mask | 绿色通道,表示要排除的区域 |
| 两个 mask 重叠 | 黄色区域,表示提取被遮挡的后层 |
输出是 IMAGE,可以接到 QwenImage Layered V2 Decompose 的 context_image,也可以接 PreviewImage 检查画笔上下文。
QwenImage Layered V2 Decompose
执行单层提取。
推荐 V2 画笔控制参数:
| 参数 | 建议 |
| --- | --- |
| steps | 10 起步,遮挡复杂时增加 |
| cfg_scale | 有画笔时优先 1.0 |
| use_input_size | 默认 True,使用输入图当前尺寸并保持比例 |
| width / height | use_input_size=False 时才作为手动输出尺寸 |
| layer_num | 通常为 0 |
该节点支持两种连接方式:
- 接
context_image:使用QwenImage Layered V2 Brush Context生成好的 RGBA 上下文。 - 直接接
target_mask/remove_mask:节点内部临时生成context_image。
提示词建议描述原图整体内容,不要写 mask、cut out、isolated layer 这类裁切指令。画笔区域由 context_image 控制:红色表示要提取的区域,绿色表示要排除的区域。如果输出像是把你画的区域挖掉了,先预览 Brush Context 输出,确认目标区域是否为红色;如果红色在反方向,切换 invert_target。
推荐 workflow
Load Image
├─ IMAGE ───────────────────────────────┐
└─ MASK -> Grow/Blur Mask -> target_mask │
▼
QwenImage Layered V2 Brush Context -> context_image
▼
QwenImage Layered V2 Loader -> pipeline -> QwenImage Layered V2 Decompose -> Preview/Save
如果需要绿色排除区域,再接第二路 mask 到 remove_mask。
安装依赖
需要在 ComfyUI 使用的 Python 环境中安装 DiffSynth-Studio:
git clone https://github.com/modelscope/DiffSynth-Studio.git
cd DiffSynth-Studio
pip install -e .
requirements.txt 仅保留运行时 Python 包提示,实际 Docker 环境建议在镜像或容器内安装 DiffSynth。
模型文件
当前节点优先使用 ComfyUI 本地模型索引。文件应放在:
ComfyUI/models/diffusion_models/qwen_image_layered_control_bf16.safetensors
ComfyUI/models/loras/Qwen-Image-Layered-Control-V2.safetensors
DiffSynth 仍需要官方 Qwen/Qwen-Image 的 text encoder/tokenizer,以及官方 Qwen/Qwen-Image-Layered 的 VAE 缓存:
ComfyUI/models/Qwen/Qwen-Image/text_encoder/model*.safetensors
ComfyUI/models/Qwen/Qwen-Image/tokenizer/*
ComfyUI/models/Qwen/Qwen-Image-Layered/vae/diffusion_pytorch_model.safetensors
如果 allow_official_download=True,DiffSynth 会按 download_source 自动下载缺失的官方 text encoder/tokenizer/VAE。默认缓存路径可通过环境变量控制:
DIFFSYNTH_MODEL_BASE_PATH=/path/to/models
如果不希望 DiffSynth 查询远程,可在模型齐全后设置:
DIFFSYNTH_SKIP_DOWNLOAD=True
与原生 ComfyUI workflow 的区别
image_qwen_image_layered_control_v2.json 使用的是:
UNETLoader -> LoraLoaderModelOnly -> ModelSamplingAuraFlow -> KSampler
这条链可以加载 V2 LoRA,并且已经验证它在比例、原生采样行为、ComfyUI 模型复用上更贴近当前目标;缺口是没有 context_image 或等价 mask 输入。DiffSynth 路线保留为官方 V2 行为参考,但不再作为精确遮罩分层的主推进方向。
下一阶段:原生 workflow 扩展计划
建议在提交当前原型后,从当前基线开新分支,例如:
git checkout -b feature/native-qwen-mask
分支目标:
- 保留原生链路:
UNETLoader -> LoraLoaderModelOnly -> ModelSamplingAuraFlow -> KSampler。 - 复用已有模型文件,不引入 DiffSynth 官方分片作为强依赖。
- 在原生 workflow 上补充 mask / control 能力,优先保持输入图比例、V2 LoRA 行为和现有 ComfyUI 节点习惯。
- 不把 mask 逻辑塞进
LoadV2LoRA。LoRA loader 只负责加载权重;mask 应该进入采样条件、model patch、conditioning 或 control 分支。
计划步骤:
- 锁定
image_qwen_image_layered_control_v2.1.json作为原生 baseline,记录同一输入图、seed、steps、cfg 下的输出行为。 - 梳理原生 Qwen 节点链路中可插入条件的位置:
MODELpatch、CONDITIONING、latent mask、control branch 或 sampler wrapper。 - 参考
image_qwen_image_controlnet_patch.json的QwenImageDiffsynthControlnetmask 输入,以及image_qwen_image_instantx_inpainting_controlnet.json中Grow and Blur Mask/ 局部重绘节点的 mask 预处理方式。 - 实现最小可验证节点,暂定方向为
QwenImageLayeredNativeMaskPatch或QwenImageLayeredNativeMaskCondition,具体输入输出以原生接口验证结果为准。 - 用同一组 workflow 验证:输出比例保持、mask 区域不被裁切、无 mask 时与原生 baseline 行为一致、V2 LoRA 正常生效。
退出条件:
- 如果原生 Qwen 采样链没有可用的 mask 注入点,则不继续堆复杂兼容层,改评估 ControlNet / inpainting patch 方案。
- 如果官方后续提供原生 V2 brush control 节点,则优先适配官方接口。
提交前建议
当前阶段建议把本仓库提交为 DiffSynth V2 原型基线,提交信息可使用:
feat: add DiffSynth Qwen layered v2 mask prototype
提交前需要避免把运行缓存纳入版本库,例如 __pycache__/。如果仓库还没有 .gitignore,建议先补充 Python 缓存忽略规则。