适用场景
需要一次性解决"出图"和"改图"两类需求的人:先用文生图批量产出素材,再对其中某几张做局部替换、换背景、改风格。Qwen-Image-2.1 把生成与编辑放在同一套调用方式和相近的提示词逻辑里,适合把"生图 → 改图 → 统一风格"串成一条流水线。如果你受够了在三个工具之间来回导图、每次改完风格就跑偏,这套流程值得试。
环境与前置条件
操作系统:Linux(Ubuntu 22.04 一类)、macOS、Windows + WSL2 均可。只走云端 API 的话,系统几乎无所谓,能跑 Python 就行。
运行时:Python 3.9 及以上,建议用虚拟环境隔离依赖。需要 pip 能正常联网拉包。
账号与密钥:在所选平台开通图像生成服务并创建 API Key。模型 ID、调用地址、支持的图片尺寸与并发上限,请以官方文档当前版本为准,不同平台可能略有差异,本文把这几项都放在环境变量里,方便随时替换。
云端 API 路线:磁盘预留几 GB 放素材和产物即可,无显卡要求。
本地部署路线(可选):权重体积通常在几十 GB 量级,建议磁盘预留 60–100 GB;显存 24 GB 及以上体验较顺,显存紧张时用 CPU offload、量化或降低分辨率救急,具体门槛以官方文档当前版本为准。
分步骤部署
下面按"先跑通云端 API,再按需转本地"的顺序推进。云端路线足够覆盖文生图、局部重绘和风格统一。
步骤 1:建目录与虚拟环境
```bash
mkdir -p ~/qwen-image-demo/{in,out,masks}
cd ~/qwen-image-demo
python3 -m venv .venv
source .venv/bin/activate
pip install -U pip
pip install -U openai python-dotenv requests pillow
```
这步在做什么:把项目目录、素材目录(in)、产物目录(out)、掩码目录(masks)先分好,后面脚本按固定路径读写,不容易乱。看到 Successfully installed ... 一堆包名即为成功。
步骤 2:配置密钥与模型 ID
在项目根目录新建 .env:
```ini
IMAGE_API_KEY=你的密钥
IMAGE_API_BASE=服务商提供的接口地址
IMAGE_MODEL=控制台里显示的模型 ID
```
.env 不要提交到 Git,把它写进 .gitignore:
```bash
echo ".env" >> .gitignore
echo ".venv/" >> .gitignore
```
验证变量能被读到:
```bash
python -c "import os; from dotenv import load_dotenv; load_dotenv(); print(bool(os.environ.get('IMAGE_API_KEY')), os.environ.get('IMAGE_MODEL'))"
```
输出 True 你的模型ID 就对了。
步骤 3:先跑通最小文生图
新建 imgutil.py,把"拿到结果并存盘"这段公共逻辑抽出来,两条路线都会用到:
```python
imgutil.py
import base64, pathlib, requests
def save_result(item, path):
"""兼容两种返回:base64 内联数据 / 临时 URL。"""
path = pathlib.Path(path)
path.parent.mkdir(parents=True, exist_ok=True)
if getattr(item, "b64_json", None):
path.write_bytes(base64.b64decode(item.b64_json))
elif getattr(item, "url", None):
r = requests.get(item.url, timeout=120)
r.raise_for_status()
path.write_bytes(r.content)
else:
raise RuntimeError(f"无法识别的返回结构: {item}")
return path
```
再写 gen_txt2img.py:
```python
gen_txt2img.py
import os
from dotenv import load_dotenv
from openai import OpenAI
from imgutil import save_result
load_dotenv()
client = OpenAI(api_key=os.environ["IMAGE_API_KEY"],
base_url=os.environ["IMAGE_API_BASE"])
PROMPT = "一只橘猫坐在木质窗台上,下午的侧逆光,浅景深,胶片质感"
resp = client.images.generate(
model=os.environ["IMAGE_MODEL"],
prompt=PROMPT,
size="1024x1024", # 若平台要求写成 1024*1024,只改这一处;不接受该参数就删掉这行
n=1,
)
out = save_result(resp.data[0], "out/txt2img.png")
print("saved:", out, out.stat().st_size, "bytes")
```
```bash
python gen_txt2img.py
```
出现 saved: out/txt2img.png 数字 bytes 表示成功。
步骤 4:指令改图(整图编辑)
先准备一张原图放进 in/source.png,然后:
```python
edit_image.py
import os
from dotenv import load_dotenv
from openai import OpenAI
from imgutil import save_result
load_dotenv()
client = OpenAI(api_key=os.environ["IMAGE_API_KEY"],
base_url=os.environ["IMAGE_API_BASE"])
with open("in/source.png", "rb") as src:
resp = client.images.edit(
model=os.environ["IMAGE_MODEL"],
image=src,
prompt="把背景换成浅灰色摄影棚背景,人物位置、姿态和服装保持不变,光线方向保持一致",
size="1024x1024",
)
out = save_result(resp.data[0], "out/edit_bg.png")
print("saved:", out)
```
指令改图的提示词按"动词 + 目标 + 保持不变项"三段写,比"弄得好看点"这种描述靠谱得多。有些平台的编辑接口要求传图片 URL 或 base64,字段名可能有差异,照官方文档把上面这一段请求体改掉即可,其余代码不用动。
步骤 5:局部重绘(掩码)
局部重绘的本质是:告诉模型"只有这块区域可以动"。先用 Pillow 画掩码:
```python
make_mask.py
from PIL import Image, ImageDraw
W = H = 1024
BOX = (560, 120, 980, 520) # 左上x, 左上y, 右下x, 右下y
mask = Image.new("RGB", (W, H), "black")
ImageDraw.Draw(mask).rectangle(BOX, fill="white")
mask.save("masks/window.png")
print("mask saved")
```
然后在 edit_image.py 里把掩码一起传进去:
```python
with open("in/source.png", "rb") as src, open("masks/window.png", "rb") as msk:
resp = client.images.edit(
model=os.environ["IMAGE_MODEL"],
image=src,
mask=msk,
prompt="把这片区域里的天空改成黄昏色调,其余区域不要改动",
size="1024x1024",
)
```
注意:不同平台对掩码的黑白约定可能相反(白色表示重绘区,或黑色表示重绘区)。如果发现改错了地方,把掩码反相再用:
```bash
python -c "from PIL import Image, ImageOps; ImageOps.invert(Image.open('masks/window.png').convert('RGB')).save('masks/window_inv.png')"
```
步骤 6:风格统一与批量出图
风格统一的关键不是"每张都写得一样",而是把风格抽成一段固定前缀,主体部分再替换。四段式模板:风格 + 主体 + 镜头/光线 + 排除项。
```python
batch.py
import os, json, time
from dotenv import load_dotenv
from openai import OpenAI
from imgutil import save_result
load_dotenv()
client = OpenAI(api_key=os.environ["IMAGE_API_KEY"],
base_url=os.environ["IMAGE_API_BASE"])
STYLE = "柔和的商业插画风格,低饱和莫兰迪色系,简洁构图,大面积留白,柔和顶光"
SUBJECTS = ["一杯手冲咖啡", "一台打开的笔记本电脑", "一盆龟背竹"]
for i, s in enumerate(SUBJECTS, 1):
prompt = f"{STYLE}。主体:{s}。画面中不出现文字与水印。"
t0 = time.time()
try:
resp = client.images.generate(model=os.environ["IMAGE_MODEL"],
prompt=prompt, size="1024x1024", n=1)
p = save_result(resp.data[0], f"out/style_{i:02d}.png")
log = {"i": i, "prompt": prompt, "path": str(p), "ok": True,
"sec": round(time.time() - t0, 1)}
except Exception as e:
log = {"i": i, "prompt": prompt, "ok": False, "err": str(e),
"sec": round(time.time() - t0, 1)}
print(json.dumps(log, ensure_ascii=False))
with open("out/run.log.jsonl", "a", encoding="utf-8") as f:
f.write(json.dumps(log, ensure_ascii=False) + "\n")
time.sleep(1) # 降低触发限流的概率
```
提示词实操的三个具体技巧:
1. 风格前缀固定、只换主体:把 STYLE 写成一个常量,三张图共用,风格漂移会明显减少。
2. 删掉模糊形容词:把"高级感"换成"大面积留白 + 柔和顶光 + 低饱和",可执行性完全不同。
3. 明确写出不要什么:正例是"画面中不出现文字与水印",反例是"不要难看"。否定描述同样要具体。
步骤 7:本地部署(可选)
如果想脱离网络调用,按官方模型页给出的加载示例走,通用流程如下:
```bash
pip install -U torch diffusers transformers accelerate safetensors pillow
```
torch 的 CUDA 版本需与显卡驱动匹配,安装命令以 PyTorch 官方页面给出的方式为准。
```python
local_gen.py —— 类名与参数以官方模型页示例为准
import torch
from diffusers import DiffusionPipeline
MODEL_DIR = "./models/qwen-image" # 本地权重目录
pipe = DiffusionPipeline.from_pretrained(MODEL_DIR, torch_dtype=torch.bfloat16)
pipe.enable_model_cpu_offload() # 显存紧张时开启,会慢一些但跑得动
image = pipe(prompt="一只橘猫坐在木质窗台上,侧逆光,胶片质感",
num_inference_steps=30).images[0]
image.save("out/local.png")
print("saved: out/local.png")
```
编辑能力通常对应另一套权重与 pipeline 类,按官方模型页的示例加载对应目录即可。
验证部署是否成功
文生图验证:
```bash
python gen_txt2img.py
python -c "from PIL import Image; im=Image.open('out/txt2img.png'); print(im.size, im.mode)"
```
预期输出形如 (1024, 1024) RGB(或平台默认尺寸),文件大小一般在 100 KB 以上。文件能打开、尺寸非 0 即可认为通了。
编辑验证:确认只改了该改的地方。
```bash
python make_mask.py && python edit_image.py
python - <<'PY'
from PIL import Image, ImageChops
a = Image.open("in/source.png").convert("RGB")
b = Image.open("out/edit_bg.png").convert("RGB")
print("尺寸一致:", a.size == b.size)
print("变化区域:", ImageChops.difference(a, b).getbbox())
PY
```
预期:尺寸一致: True;变化区域 返回的矩形大致落在掩码框内。如果返回 None,说明两张图完全相同,编辑没生效。
批量验证:
```bash
ls -lh out/style_*.png && wc -l out/run.log.jsonl
```
预期三张图都在,日志行数与出图数量一致,且每行 "ok": true。
常见报错与解决
1. ModuleNotFoundError: No module named 'openai'
原因:不在虚拟环境里跑,或者依赖没装成功。
解决:
```bash
source .venv/bin/activate && pip install -U openai python-dotenv requests pillow
```
2. KeyError: 'IMAGE_API_KEY'
原因:.env 没被加载,或变量名与代码里写的不一致。
解决:
```bash
cd ~/qwen-image-demo && python -c "import os; from dotenv import load_dotenv; load_dotenv(); print(bool(os.environ.get('IMAGE_API_KEY')))"
```
输出必须是 True。为 False 就检查 .env 是否在项目根目录、键名有没有拼错。
3. openai.AuthenticationError: Error code: 401 - invalid_api_key
原因:密钥复制不全、首尾带空格或被引号包进去,也可能已失效。
解决:
```bash
python -c "import os; from dotenv import load_dotenv; load_dotenv(); k=os.environ['IMAGE_API_KEY'].strip(); print(repr(k[:4]), len(k))"
```
确认长度正常、没有多余空白,必要时在控制台重新生成密钥再写回 .env。
4. openai.BadRequestError: Error code: 400 - InvalidParameter: size
原因:尺寸写法或取值不在平台支持范围内(有的要求 1024*1024,有的只接受特定几档)。
解决:先删掉 size 参数用默认值跑通,再对照官方文档填支持的尺寸:
```bash
python -c "import os; from dotenv import load_dotenv; from openai import OpenAI; load_dotenv(); c=OpenAI(api_key=os.environ['IMAGE_API_KEY'], base_url=os.environ['IMAGE_API_BASE']); print([m.id for m in c.models.list()][:20])"
```
5. openai.NotFoundError: Error code: 404 - model not found
原因:模型 ID 写错,或该模型在当前账号下尚未开通。
解决:在控制台复制准确的模型 ID 覆盖 .env 里的值,确认开通状态后重跑:
```bash
grep IMAGE_MODEL .env
```
6. torch.cuda.OutOfMemoryError: CUDA out of memory
原因:本地部署时显存不够。
解决:开启分层卸载,或先降分辨率试跑。
```bash
PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True python local_gen.py
```
同时在脚本里加上 pipe.enable_model_cpu_offload(),仍不够就换成 pipe.enable_sequential_cpu_offload()。
7. 下载结果图时报 requests.exceptions.HTTPError: 403 Client Error
原因:返回的临时链接已过期,或需要鉴权头。
解决:拿到 URL 后立即下载落盘,别把 URL 存下来隔天再用。imgutil.py 里的写法已经是即时下载,只要不改就没事。
后续维护
备份:定期把 .env、提示词模板、masks/ 和 out/ 一起归档。密钥单独存放,不要跟产物混在一个压缩包里往外发。产物建议按日期分目录,例如 out/2025-06-01/,回溯时不用翻整个目录。
升级:把当前依赖版本冻结下来,升级前先对比:
```bash
pip freeze > requirements.txt
pip install -U openai && python gen_txt2img.py && python edit_image.py
```
升级 SDK、模型或权重版本后,先用上面两条命令跑一遍核心链路,确认产物正常再切到正式流程。模型 ID 变更时只改 .env,代码无需改动,这也是前面把它做成变量的原因。
日志与监控:out/run.log.jsonl 已经记录了每次调用的提示词、产物路径、耗时和成败。可以再加几个统计:
```bash
grep -c '"ok": false' out/run.log.jsonl
awk -F'"sec": ' '{print $2}' out/run.log.jsonl | cut -d, -f1 | sort -n | tail -3
```
第一条看失败次数,第二条看最慢的几次耗时。失败率突然升高,通常指向限流、密钥额度或接口变更,先查这三项。
省钱省时间:先用小尺寸快速试构图,定稿后再出大图;相同提示词与参数的产物落盘缓存,重复请求直接读本地文件;批量任务之间留一秒间隔,减少触发限流的概率。
成本与额度:计费方式、免费额度和并发限制请以官方页面为准,别按经验值估算。
