---
name: comfyui-qwen-image-edit
description: 通过公网网关使用 ComfyUI Qwen-Image-Edit-2511 模型进行高质量图片编辑。覆盖图片上传、工作流提交、队列/任务状态查询、PNG 图片下载的完整流程，支持换装、改背景、物体替换、去元素等。
version: "1.0"
category: image-generation
triggers:
  - 图片编辑
  - 改图
  - 换衣服
  - 改背景
  - P掉物体
  - 物体替换
  - qwen 编辑
  - qwen edit
  - qwen image
  - 去水印
  - 加元素
  - comfyui qwen
  - image edit
---

::: info 下载原始 Skill 文件
```bash
curl https://ai.ospreyai.cn/docs/raw/skills/comfyui-qwen-image-edit.md -o comfyui-qwen-image-edit.md
```
:::

## 对话式接入

本 Skill 文件可被 AI 助手（Claude Code、Cursor、ChatGPT 等）学习，通过自然语言对话完成图片编辑。

在 AI 对话中发送以下指令即可：

```
学习：https://ai.ospreyai.cn/docs/raw/skills/comfyui-qwen-image-edit.md，保存为本地的技能 skills
```

> 更多接入方式和使用示例详见 [API 文档 — AI 助手对话式接入](/api#ai-助手对话式接入)。

---

# ComfyUI Qwen-Image-Edit 图片编辑

## Overview

通过公网网关 `https://ai.ospreyai.cn` 使用 ComfyUI **Qwen-Image-Edit-2511** 模型进行高质量图片编辑。基于 Qwen-VL 视觉语言模型，支持参照编辑（Reference Editing）。

核心特性：
- **Turbo 4 步模式**：Lightning LoRA 加速，~2-6 秒完成（RTX 5090）
- **Native 40 步模式**：更高质量输出，~15-20 秒完成
- **自我编辑**（单图）：同一张图作为输入和参考，描述想要的变化
- **换装/换物**（双图）：图1 = 目标人物，图2 = 参照服装/物品
- **内置 CFGNorm + Switch 节点**：Turbo/Native 切换无需手动修改工作流

所有 API 均需 Bearer Token 鉴权（`Authorization: Bearer sk-xxx`）。

## Quick Start

```bash
export GW="https://ai.ospreyai.cn"
export API_KEY="sk-your-api-key"

# 1. 上传图片
curl -s -H "Authorization: Bearer $API_KEY" -X POST "$GW/api/v1/upload" \
  -F "image=@input.png" -F "type=input" -F "overwrite=true"

# 2. 提交编辑工作流（完整 JSON 见下方 Route A）
curl -s -H "Authorization: Bearer $API_KEY" -X POST "$GW/api/v1/ai/image/generate" \
  -H "Content-Type: application/json" \
  -d '{"prompt":{...}, "extra_data": {}}'

# 3. 查询任务状态
curl -s -H "Authorization: Bearer $API_KEY" "$GW/api/v1/ai/tasks/{prompt_id}"

# 4. 下载生成的图片
curl -s -H "Authorization: Bearer $API_KEY" \
  "$GW/api/v1/ai/image/view/?filename=Qwen_Edit_2511_00001_.png&type=output&subfolder=" \
  -o result.png
```

## Task Routing

| 场景 | 动作 |
|------|------|
| 首次图片编辑 | → Route A: Upload & Generate |
| 需要查看任务是否完成 | → Route B: Check Status |
| 需要获取或下载 PNG | → Route C: Download |
| 需要调优提示词或切换 Turbo/Native | → Route D: Tune Parameters |
| 需要排查服务、节点、模型或文件问题 | → Route E: Troubleshoot |

## Route A: Upload & Generate

### 服务信息

- 网关地址: `https://ai.ospreyai.cn`
- 上传接口: `POST /api/v1/upload`
- 提交接口: `POST /api/v1/ai/image/generate`
- 鉴权方式: `Authorization: Bearer sk-xxx`
- 提交格式: ComfyUI v1 dict 格式 `{"prompt": {...nodes...}, "extra_data": {}}`

### 基本流程

1. 上传输入图片（+ 可选参考图）到 ComfyUI input 目录
2. 提交 Qwen-Image-Edit-2511 工作流
3. 工作流包含 20+ 节点：
   - `LoadImage` × 2 — 加载输入图和参考图
   - `FluxKontextImageScale` — 缩放到 VAE 兼容尺寸
   - `TextEncodeQwenImageEditPlus` × 2 — Qwen-VL 多模态编码（正向/负向）
   - `FluxKontextMultiReferenceLatentMethod` × 2 — 多参考图 latent 方法
   - `UNETLoader` — 加载 Qwen-Image-Edit 主模型
   - `VAELoader` — 加载 VAE 解码器
   - `CLIPLoader` — 加载 Qwen-VL CLIP
   - `VAEEncode` — 编码参考图 latent
   - `KSampler` — 采样去噪（核心节点）
   - `VAEDecode` — VAE 解码
   - `SaveImage` — 保存图片

### Step 1: 上传图片

```bash
# 上传输入图片（必须）
curl -s -H "Authorization: Bearer $API_KEY" -X POST "$GW/api/v1/upload" \
  -F "image=@/path/to/input.png" \
  -F "type=input" \
  -F "overwrite=true"

# 如果换装需要参考图，上传第二张
curl -s -H "Authorization: Bearer $API_KEY" -X POST "$GW/api/v1/upload" \
  -F "image=@/path/to/reference.png" \
  -F "type=input" \
  -F "overwrite=true"
```

**响应：**

```json
{"name": "input.png", "subfolder": "", "type": "input"}
```

> 自我编辑模式只需一张图（图2 = 图1），换装模式需要两张不同的图。

最大文件大小: 50MB。推荐使用 PNG 格式。

### Step 2: 提交编辑工作流

```bash
curl -s -H "Authorization: Bearer $API_KEY" -X POST "$GW/api/v1/ai/image/generate" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt": {
      "41": {"inputs": {"image": "input.png"}, "class_type": "LoadImage"},
      "83": {"inputs": {"image": "input.png"}, "class_type": "LoadImage"},
      "170:160": {"inputs": {"image": ["41", 0]}, "class_type": "FluxKontextImageScale"},
      "170:161": {"inputs": {"unet_name": "qwen_image_edit_2511_bf16.safetensors", "weight_dtype": "default"}, "class_type": "UNETLoader"},
      "170:162": {"inputs": {"clip_name": "qwen_2.5_vl_7b_fp8_scaled.safetensors", "type": "qwen_image", "device": "default"}, "class_type": "CLIPLoader"},
      "170:146": {"inputs": {"vae_name": "qwen_image_vae.safetensors"}, "class_type": "VAELoader"},
      "170:145": {"inputs": {"shift": 3.1, "model": ["170:161", 0]}, "class_type": "ModelSamplingAuraFlow"},
      "170:152": {"inputs": {"strength": 1.0, "model": ["170:145", 0]}, "class_type": "CFGNorm"},
      "170:156": {"inputs": {"pixels": ["170:160", 0], "vae": ["170:146", 0]}, "class_type": "VAEEncode"},
      "170:168": {"inputs": {"value": false}, "class_type": "PrimitiveBoolean"},
      "170:165": {"inputs": {"value": 4}, "class_type": "PrimitiveInt"},
      "170:166": {"inputs": {"value": 40}, "class_type": "PrimitiveInt"},
      "170:154": {"inputs": {"value": 1.0}, "class_type": "PrimitiveFloat"},
      "170:155": {"inputs": {"value": 4.0}, "class_type": "PrimitiveFloat"},
      "170:149": {"inputs": {"prompt": "", "clip": ["170:162", 0], "vae": ["170:146", 0], "image1": ["170:160", 0], "image2": ["83", 0]}, "class_type": "TextEncodeQwenImageEditPlus"},
      "170:147": {"inputs": {"reference_latents_method": "index_timestep_zero", "conditioning": ["170:149", 0]}, "class_type": "FluxKontextMultiReferenceLatentMethod"},
      "170:151": {"inputs": {"prompt": "Remove the jacket, keep the T-shirt underneath", "clip": ["170:162", 0], "vae": ["170:146", 0], "image1": ["170:160", 0], "image2": ["83", 0]}, "class_type": "TextEncodeQwenImageEditPlus"},
      "170:148": {"inputs": {"reference_latents_method": "index_timestep_zero", "conditioning": ["170:151", 0]}, "class_type": "FluxKontextMultiReferenceLatentMethod"},
      "170:153": {"inputs": {"lora_name": "Qwen-Image-Edit-2511-Lightning-4steps-V1.0-bf16.safetensors", "strength_model": 1.0, "model": ["170:152", 0]}, "class_type": "LoraLoaderModelOnly"},
      "170:163": {"inputs": {"switch": ["170:168", 0], "on_false": ["170:152", 0], "on_true": ["170:153", 0]}, "class_type": "ComfySwitchNode"},
      "170:164": {"inputs": {"switch": ["170:168", 0], "on_false": ["170:154", 0], "on_true": ["170:155", 0]}, "class_type": "ComfySwitchNode"},
      "170:167": {"inputs": {"switch": ["170:168", 0], "on_false": ["170:166", 0], "on_true": ["170:165", 0]}, "class_type": "ComfySwitchNode"},
      "170:169": {"inputs": {"seed": 42, "steps": ["170:167", 0], "cfg": ["170:164", 0], "sampler_name": "euler", "scheduler": "simple", "denoise": 1.0, "model": ["170:163", 0], "positive": ["170:148", 0], "negative": ["170:147", 0], "latent_image": ["170:156", 0]}, "class_type": "KSampler"},
      "170:158": {"inputs": {"samples": ["170:169", 0], "vae": ["170:146", 0]}, "class_type": "VAEDecode"},
      "9": {"inputs": {"filename_prefix": "Qwen_Edit_2511", "images": ["170:158", 0]}, "class_type": "SaveImage"}
    },
    "extra_data": {}
  }'
```

**响应：**

```json
{"prompt_id": "a1b2c3d4-...", "number": 200, "node_errors": {}}
```

> `node_errors` 为空对象 `{}` 表示工作流校验通过。如有错误会在此列出。

**使用前需修改：**

| 节点 | 字段 | 修改为 |
|------|------|--------|
| 41 | image | 输入图片文件名（已上传） |
| 83 | image | 参考图片文件名（自我编辑时同 41；换装时用参考图） |
| 170:151 | prompt | 你的英文编辑提示词 |
| 170:168 | value | `true` = Turbo 4步 / `false` = Native 40步 |
| 170:169 | seed | 任意整数（不同值生成不同结果） |

### 节点说明

| 节点 ID | class_type | 功能 |
|---------|-----------|------|
| 41 | LoadImage | 加载输入图片（目标编辑图） |
| 83 | LoadImage | 加载参考图片（换装模式用服装参考） |
| 170:160 | FluxKontextImageScale | 将图片缩放到 VAE 兼容尺寸 |
| 170:161 | UNETLoader | 加载 Qwen-Image-Edit-2511 主模型 |
| 170:162 | CLIPLoader | 加载 Qwen-VL 2.5 7B CLIP 文本编码器 |
| 170:146 | VAELoader | 加载 Qwen Image VAE |
| 170:145 | ModelSamplingAuraFlow | 采样偏移调整（shift=3.1） |
| 170:152 | CFGNorm | CFG 归一化，稳定训练 |
| 170:156 | VAEEncode | 编码缩放后的输入图为 latent |
| 170:151 | TextEncodeQwenImageEditPlus | 正向提示词多模态编码（prompt + image1 + image2） |
| 170:149 | TextEncodeQwenImageEditPlus | 负向提示词多模态编码（空 prompt = 保持原图） |
| 170:148 | FluxKontextMultiReferenceLatentMethod | 正向多参考 latent 方法 |
| 170:147 | FluxKontextMultiReferenceLatentMethod | 负向多参考 latent 方法 |
| 170:153 | LoraLoaderModelOnly | 加载 Lightning 4步 LoRA 加速器 |
| 170:168 | PrimitiveBoolean | Turbo/Native 模式开关 |
| 170:163 | ComfySwitchNode | 模型切换（LoRA / 原生） |
| 170:164 | ComfySwitchNode | CFG 切换（1.0 / 4.0） |
| 170:167 | ComfySwitchNode | Steps 切换（4 / 40） |
| 170:169 | KSampler | 采样去噪核心节点 |
| 170:158 | VAEDecode | VAE 解码 latent → 像素图 |
| 9 | SaveImage | 保存图片为 PNG |

### 工作流连接图

```
LoadImage(41) ──→ FluxKontextImageScale(170:160) ──→ VAEEncode(170:156) ──→ KSampler(170:169)
                                                                                   ↑
LoadImage(83) ──→ TextEncodeQwenImageEditPlus(170:151) ──→ FluxKontextMultiRef(170:148) ──┤
CLIPLoader(170:162) ──→┤                                                                   │
VAELoader(170:146)  ──→┤                                                                   │
                                                                                            │
LoadImage(83) ──→ TextEncodeQwenImageEditPlus(170:149) ──→ FluxKontextMultiRef(170:147) ───┤
                                                                                            │
UNETLoader(170:161) → ModelSamplingAuraFlow(170:145) → CFGNorm(170:152) → Switch(170:163) ─┤
                                                           ↓                                │
                                          LoraLoader(170:153) ──────────────────────────────┘
                                                                                            ↓
                                                                                   VAEDecode(170:158)
                                                                                            ↓
                                                                                     SaveImage(9)
```

### 关键参数

| 节点 | 字段 | 说明 | 示例值 |
|------|------|------|--------|
| 41 | image | 输入图片文件名 | `"input.png"` |
| 83 | image | 参考图片文件名 | `"input.png"`（自我编辑）或 `"ref.png"`（换装） |
| 170:151 | prompt | 编辑提示词（英文） | `"Remove the jacket, keep the T-shirt"` |
| 170:168 | value | Turbo 开关 | `false` = Native 40步, `true` = Turbo 4步 |
| 170:169 | seed | 随机种子 | 42 |
| 9 | filename_prefix | 输出文件名前缀 | `"Qwen_Edit_2511"` |

### 输出图片参数

- **分辨率**: 与输入图片一致
- **格式**: PNG
- **输出路径**: `subfolder=""`（空字符串）, `type=output`

## Route B: Check Status

### 查询队列

```bash
curl -s -H "Authorization: Bearer $API_KEY" "$GW/api/v1/ai/queue"
```

**响应：**

```json
{
  "queue_running": [[200, "a1b2c3d4-...", {...}]],
  "queue_pending": []
}
```

- `queue_running` 不为空 → 任务正在执行
- `queue_pending` 不为空 → 任务排队等待

### 查询任务状态

```bash
curl -s -H "Authorization: Bearer $API_KEY" "$GW/api/v1/ai/tasks/{prompt_id}"
```

**进行中：**

```json
{
  "a1b2c3d4-...": {
    "status": {"status_str": "success", "completed": false},
    "outputs": {}
  }
}
```

**已完成：**

```json
{
  "a1b2c3d4-...": {
    "status": {"status_str": "success", "completed": true},
    "outputs": {
      "9": {
        "images": [
          {"filename": "Qwen_Edit_2511_00001_.png", "subfolder": "", "type": "output"}
        ]
      }
    }
  }
}
```

> Turbo 模式 ~2-6 秒，Native 模式 ~15-20 秒。首次运行需加载 Qwen-VL 模型（约 30 秒），后续快。

## Route C: Download

下载前先从任务状态中获取输出文件信息（`filename`, `subfolder`, `type`），然后通过查看接口下载。

```bash
# 下载 PNG 图片（注意 subfolder 为空字符串）
curl -s -H "Authorization: Bearer $API_KEY" \
  "$GW/api/v1/ai/image/view/?filename=Qwen_Edit_2511_00001_.png&type=output&subfolder=" \
  -o result.png
```

**关键点：**

- 图片输出的 `subfolder` 为空字符串
- 图片输出的 `type` 为 `output`
- 下载的是 PNG 格式，可直接查看

### Python 调用示例

```python
import requests
import time
import os

GW = os.getenv("GW", "https://ai.ospreyai.cn")
API_KEY = os.getenv("API_KEY", "")
headers = {"Authorization": f"Bearer {API_KEY}"}

# 1. 上传图片
def upload(path):
    with open(path, "rb") as f:
        resp = requests.post(f"{GW}/api/v1/upload", headers=headers,
                             files={"image": (os.path.basename(path), f.read(), "image/png")},
                             data={"overwrite": "true", "type": "input"})
        return os.path.basename(path)

input_name = upload("input.png")
ref_name = upload("ref.png")  # 自我编辑时用 input.png
print(f"Uploaded: {input_name}, {ref_name}")

# 2. 提交编辑工作流
prompt_text = "Remove the jacket, keep the T-shirt underneath"
nodes = {
    "41": {"inputs": {"image": input_name}, "class_type": "LoadImage"},
    "83": {"inputs": {"image": ref_name}, "class_type": "LoadImage"},
    # ... 完整工作流节点，替换 170:151 的 prompt 和 41/83 的 image
}

resp = requests.post(f"{GW}/api/v1/ai/image/generate",
                     json={"prompt": nodes, "extra_data": {}},
                     headers={**headers, "Content-Type": "application/json"})
prompt_id = resp.json()["prompt_id"]
print(f"Task submitted: {prompt_id}")

# 3. 轮询任务状态
while True:
    resp = requests.get(f"{GW}/api/v1/ai/tasks/{prompt_id}", headers=headers)
    data = resp.json()
    task = data.get(prompt_id, {})
    if task.get("status", {}).get("completed"):
        print("Task completed!")
        break
    time.sleep(2)

# 4. 下载图片
outputs = task.get("outputs", {}).get("9", {})
image_info = outputs.get("images", [{}])[0]
resp = requests.get(f"{GW}/api/v1/ai/image/view/", headers=headers,
                    params={"filename": image_info["filename"],
                            "subfolder": image_info.get("subfolder", ""),
                            "type": image_info.get("type", "output")})
with open("result.png", "wb") as f:
    f.write(resp.content)
print(f"Downloaded: result.png ({len(resp.content)} bytes)")
```

## Route D: Tune Parameters

| 参数 | 节点 | 建议 |
|------|------|------|
| 编辑提示词 | 170:151 prompt | 英文效果更稳定；用 image 1/image 2 指代两张图片 |
| 负向提示词 | 170:149 prompt | 保持空字符串 `""` = 尽量保持原图不变 |
| Turbo/Native | 170:168 value | `false` = Native 40步（质量更好）; `true` = Turbo 4步（速度更快） |
| 随机种子 | 170:169 seed | 不同值生成不同结果；相同种子可复现 |
| 采样偏移 | 170:145 shift | 默认 3.1；增大更锐利，减小更平滑 |
| 输出前缀 | 9 filename_prefix | 自定义输出文件名前缀 |

### 提示词策略

**自我编辑（单图，图2 = 图1）：**

```text
Remove the jacket, keep the T-shirt underneath
```

```text
Change the background to a sunny beach, keep the person unchanged
```

```text
Replace the red wine glass with a bouquet of flowers
```

```text
Add a red scarf around the neck, keep everything else identical
```

```text
Turn the daytime scene into a night scene with stars
```

**换装（双图）：**

```text
Have the person in image 1 wear the clothes from image 2
```

```text
Apply the makeup style from image 2 to the face in image 1
```

```text
Replace the outfit of the person in image 1 with the outfit in image 2, keep the same pose
```

### Turbo vs Native

| 模式 | 170:168 | 步数 | CFG | 速度 | 质量 | 适用场景 |
|------|---------|------|-----|------|------|---------|
| Turbo (Lightning LoRA) | `true` | 4 | 1.0 | ~2-6s | 好 | 快速预览、简单编辑 |
| Native | `false` | 40 | 4.0 | ~15-20s | 更好 | 精细编辑、最终输出 |

## Route E: Troubleshoot

| 问题 | 排查方法 |
|------|---------|
| 上传图片失败 | 检查文件大小（≤50MB）、格式（推荐 PNG）、Bearer Token 是否有效 |
| 500 on generate | API 格式错误，确认用 v1 dict 格式 `{"prompt": {...}, "extra_data": {}}`，不是数组格式 |
| Bad linked input | 节点连接格式问题，确认用 `["node_id", slot_index]` 格式，如 `["41", 0]` |
| 504 timeout | 首次运行需加载 Qwen-VL 模型（约 30s），后续快；如持续超时检查 GPU 状态 |
| 工作流提交报 `value_not_in_list` | 模型文件名不正确，检查 UNet/CLIP/VAE/LoRA 名称是否与服务器一致 |
| `node_errors` 非空 | 图片文件未上传成功或节点连接有误 |
| 生成结果与原图完全一样 | 提示词可能未被正确编码；检查 170:151 prompt 和 image1/image2 连接 |
| 生成结果质量差 | 尝试切换到 Native 模式（170:168 = false）；使用更详细的英文提示词 |
| 下载图片返回空 | 检查 `subfolder` 是否为空字符串 |
| 401 鉴权失败 | 检查 Bearer Token 是否有效：`curl -H "Authorization: Bearer sk-xxx" $GW/api/v1/ai/queue` |
| 429 请求被限流 | AI 接口 10次/分/IP，稍后重试 |
| 503 后端服务不可用 | 检查内网 ComfyUI 服务是否运行 |

### 模型文件清单

| 类型 | 文件 | 用途 |
|------|------|------|
| UNet | `qwen_image_edit_2511_bf16.safetensors` | Qwen-Image-Edit 主模型 |
| CLIP | `qwen_2.5_vl_7b_fp8_scaled.safetensors` | Qwen-VL 多模态文本编码器 |
| VAE | `qwen_image_vae.safetensors` | Qwen Image VAE 解码器 |
| LoRA | `Qwen-Image-Edit-2511-Lightning-4steps-V1.0-bf16.safetensors` | Turbo 4步加速器 |

### 技术细节

- **Qwen-VL 多模态编码**：`TextEncodeQwenImageEditPlus` 同时接收 prompt + image1 + image2，实现图文联合理解
- **FluxKontextMultiReferenceLatentMethod**：用于多参考图的 latent 方法，`index_timestep_zero` 策略在 timestep=0 时注入参考信息
- **CFGNorm**：CFG 归一化层，稳定 Flux 架构训练
- **ComfySwitchNode**：通过 `PrimitiveBoolean` 控制 Turbo/Native 三路切换（模型、CFG、步数）
- **子图前缀 `170:`**：避免与顶层节点 ID（41, 83, 9）冲突
- **v1 dict 格式**：ComfyUI v0.20.1 使用 `{"prompt": {...}, "extra_data": {}}` 而非数组格式

### 内网直连 vs 公网网关

| | 内网直连 | 公网网关 |
|---|---|---|
| 地址 | `192.168.1.236:8188` | `ai.ospreyai.cn` |
| 鉴权 | 无 | `Authorization: Bearer sk-xxx` |
| 上传路径 | `/upload/image` | `/api/v1/upload` |
| 提交路径 | `/prompt` | `/api/v1/ai/image/generate` |
| 任务查询 | `/history/{id}` | `/api/v1/ai/tasks/{id}` |
| 队列查询 | `/queue` | `/api/v1/ai/queue` |
| 下载路径 | `/view?filename=...` | `/api/v1/ai/image/view/?filename=...&type=output&subfolder=` |
| 限流 | 无 | 10次/分/IP |
| API 格式 | 数组 `[...]` | v1 dict `{"prompt": {...}, "extra_data": {}}` |

## Verification Checklist

- [ ] 输入图片上传成功，返回 `name` 值
- [ ] 工作流提交成功，`node_errors` 为空
- [ ] 任务在 `/api/v1/ai/queue` 中执行完成
- [ ] 任务状态 `completed: true`，outputs 包含 PNG 文件信息
- [ ] 成功下载 PNG 文件（注意 `subfolder` 为空字符串）
- [ ] 图片可正常查看且编辑效果符合预期
