Appearance
下载原始 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 助手对话式接入。
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.pngTask 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": {}}
基本流程
- 上传输入图片(+ 可选参考图)到 ComfyUI input 目录
- 提交 Qwen-Image-Edit-2511 工作流
- 工作流包含 20+ 节点:
LoadImage× 2 — 加载输入图和参考图FluxKontextImageScale— 缩放到 VAE 兼容尺寸TextEncodeQwenImageEditPlus× 2 — Qwen-VL 多模态编码(正向/负向)FluxKontextMultiReferenceLatentMethod× 2 — 多参考图 latent 方法UNETLoader— 加载 Qwen-Image-Edit 主模型VAELoader— 加载 VAE 解码器CLIPLoader— 加载 Qwen-VL CLIPVAEEncode— 编码参考图 latentKSampler— 采样去噪(核心节点)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 underneathtext
Change the background to a sunny beach, keep the person unchangedtext
Replace the red wine glass with a bouquet of flowerstext
Add a red scarf around the neck, keep everything else identicaltext
Turn the daytime scene into a night scene with stars换装(双图):
text
Have the person in image 1 wear the clothes from image 2text
Apply the makeup style from image 2 to the face in image 1text
Replace the outfit of the person in image 1 with the outfit in image 2, keep the same poseTurbo 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为空字符串) - [ ] 图片可正常查看且编辑效果符合预期