图生图(img2img)在已有图片的基础上按提示词改画。strength 决定改动多少。 这篇做
img2img.py:把 A 篇生成的山村图改成雪景,比较 4 档 strength 的效果,并量化每档与原图的差异;参数不合法时先给出明确提示。
先看清成品
- strength 0.25 / 0.5 / 0.75 / 1.0 与原图的平均像素差分别为 9.3 / 20.4 / 43.2 / 66.2。
- 实测 steps=1、strength=0.5 时 diffusers 报 “cannot reshape tensor of 0 elements…”;脚本现在提前检查 steps × strength ≥ 1。
- pytest 2 项通过(其中 1 项真实运行模型)。
合规提醒:sd-turbo 使用 Stability AI Community License:研究、非商业用途,以及年收入低于 100 万美元的个人或组织的商业用途免费;超过的需要企业许可,以模型页面和 stability.ai/license 为准。sd-turbo 仓库不含安全检查器(diffusers 加载时会提示):不要把未经过滤的生成服务公开给他人使用。不生成真实人物的误导性图像,不对他人照片做未经同意的修改;图生图只处理你有权使用的图片。模型从 Hugging Face 官方仓库下载,不使用来路不明的模型文件。
运行说明:本篇在 box 上真实运行了 Stable Diffusion:stabilityai/sd-turbo(revision b261bac6),diffusers 0.41.0,torch 2.14.1(CPU 版),8 核 CPU、无显卡。512×512、1 步生成一张约 4~5 秒。文中的图片都是这次运行生成的。没有测试 GPU,也没有运行 ComfyUI 和 SD 3.5(SD 3.5 需要在 Hugging Face 上同意许可后才能下载,且 CPU 上太慢)。
做完之后你会得到什么?
| 成果 | 说明 |
|---|---|
img2img.py |
图生图和 strength 对比 |
village.png |
输入图(A 篇用种子 42 生成) |
examples/strengths.jpg |
本文生成的对比图 |
test_img2img.py |
2 项测试 |
需要准备哪些环境?
| 项目 | 说明 |
|---|---|
| Python | 3.10 以上 |
| PyTorch | CPU:pip install torch --index-url https://download.pytorch.org/whl/cpu;有 NVIDIA 显卡时按 PyTorch 官网选择 CUDA 版本 |
| diffusers 等 | pip install diffusers transformers accelerate safetensors pillow pytest |
| 磁盘 | sd-turbo 的 fp16 权重约 2.5 GB |
| 内存 | CPU 运行时建议 8 GB 以上(本文机器 15 GB) |
关键事实
| 事实 | 来源 |
|---|---|
图生图时 num_inference_steps × strength 必须 ≥ 1;实际执行 int(num_inference_steps × strength) 步 |
sd-turbo 模型页面 |
官方示例:num_inference_steps=2, strength=0.5, guidance_scale=0.0 |
同上 |
文件结构
75-stable-diffusion-C-src/
├── img2img.py
├── village.png
├── test_img2img.py
└── examples/
└── strengths.jpg
第 1 步:脚本
"""图生图:在你自己的图片(或自己生成的图)上按提示词改画,并比较不同 strength 的效果。
- strength 越大,改动越大、越不像原图
- sd-turbo 实际执行的步数 = int(steps × strength),必须 ≥ 1;否则 diffusers 报一个看不懂的 RuntimeError(调试时实测)
只处理你有权使用的图片;不要用他人照片做换脸或误导性内容。"""
import argparse, sys
import numpy as np, torch
from diffusers import AutoPipelineForImage2Image
from PIL import Image, ImageDraw
MODEL = "stabilityai/sd-turbo"
def check_params(steps, strength):
if not 0 < strength <= 1: raise ValueError(f"strength 必须在 (0, 1] 之间,现在是 {strength}")
if int(steps * strength) < 1:
need = int(np.ceil(1 / strength))
raise ValueError(f"steps × strength = {steps} × {strength} = {steps * strength:g} < 1,实际一步都不会执行;请把 steps 提高到至少 {need}")
def load(model=MODEL):
pipe = AutoPipelineForImage2Image.from_pretrained(model, dtype=torch.float32, variant="fp16"); pipe.set_progress_bar_config(disable=True)
return pipe
def edit(pipe, image, prompt, strength, steps=2, seed=1):
check_params(steps, strength)
img = image.convert("RGB").resize((512, 512))
return pipe(prompt, image=img, num_inference_steps=steps, strength=strength, guidance_scale=0.0, generator=torch.Generator().manual_seed(seed)).images[0]
def change(a, b):
"""与原图的平均像素差(0~255),用来量化“改了多少”。"""
return round(float(np.abs(np.asarray(a.resize((512, 512)), dtype=np.int16) - np.asarray(b, dtype=np.int16)).mean()), 1)
def strip(images, labels, size=256):
out = Image.new("RGB", (size * len(images), size + 22), "white"); d = ImageDraw.Draw(out)
for i, (im, lb) in enumerate(zip(images, labels)):
out.paste(im.resize((size, size)), (i * size, 22)); d.text((i * size + 4, 4), lb, fill="black")
return out
def main(argv=None):
ap = argparse.ArgumentParser(); ap.add_argument("image"); ap.add_argument("prompt"); ap.add_argument("--strengths", default="0.25,0.5,0.75,1.0")
ap.add_argument("--steps", type=int, default=4); ap.add_argument("--seed", type=int, default=1); ap.add_argument("--out", default="strengths.jpg"); a = ap.parse_args(argv)
strengths = [float(s) for s in a.strengths.split(",")]
for s in strengths: check_params(a.steps, s) # 先检查全部参数,避免跑到一半才失败
src = Image.open(a.image).convert("RGB"); pipe = load(); imgs, labels = [src.resize((512, 512))], ["original"]
for s in strengths:
im = edit(pipe, src, a.prompt, s, a.steps, a.seed); imgs.append(im)
labels.append(f"strength {s} (diff {change(src, im)})"); print(labels[-1])
strip(imgs, labels).save(a.out, quality=88); print("→", a.out); return 0
if __name__ == "__main__":
try: sys.exit(main())
except ValueError as e: print("参数错误:", e); sys.exit(2)
第 2 步:测试
import pytest
from PIL import Image
import img2img as I
def test_params():
# 调试时实测:steps=1、strength=0.5 时 diffusers 报 “cannot reshape tensor of 0 elements …”
with pytest.raises(ValueError, match="至少 2"): I.check_params(1, 0.5)
with pytest.raises(ValueError, match="至少 4"): I.check_params(1, 0.25)
with pytest.raises(ValueError, match=r"\(0, 1\]"): I.check_params(4, 0)
I.check_params(2, 0.5); I.check_params(1, 1.0)
@pytest.fixture(scope="module")
def pipe(): return I.load()
def test_strength_order(pipe):
src = Image.open("village.png")
lo, hi = I.edit(pipe, src, "the same mountain village in winter, deep snow, watercolor", 0.25, steps=4), \
I.edit(pipe, src, "the same mountain village in winter, deep snow, watercolor", 1.0, steps=4)
assert I.change(src, lo) < 15 < 40 < I.change(src, hi) # 实测 9.3 和 66.2
第 3 步:运行
$ python img2img.py village.png "the same mountain village in winter, deep snow, watercolor" --steps 1
参数错误: steps × strength = 1 × 0.25 = 0.25 < 1,实际一步都不会执行;请把 steps 提高到至少 4
$ python img2img.py village.png "the same mountain village in winter, deep snow, watercolor"
strength 0.25 (diff 9.3)
strength 0.5 (diff 20.4)
strength 0.75 (diff 43.2)
strength 1.0 (diff 66.2)
→ strengths.jpg

$ python -m pytest -q
2 passed
怎么检查结果?
- strength 越大,差异值越大,画面越不像原图(本例从“原图加一层薄雪”到“完全重新构图的雪山村”)。
- 想保留构图:0.25~0.5;想换风格:0.5~0.75;1.0 基本等于重新生成。
- 所有 strength 都在开始前检查一遍,不会跑到一半才失败。
调试示例
steps × strength < 1 时的报错看不懂。 我先用 steps=1、strength=0.5 运行(沿用文生图的 1 步),diffusers 报:
RuntimeError cannot reshape tensor of 0 elements into shape [0, -1, 1, 512] because the unspecified dimension size -1 can be any value and is ambiguous
从这句报错完全看不出是参数问题。原因是实际执行的步数是 int(1 × 0.5) = 0,模型页面中也写明了 steps × strength 必须 ≥ 1。改成 steps=2 后正常。脚本现在在加载模型前检查所有 strength,并直接给出需要的最少步数。
常见问题
| 现象 | 原因 | 处理 |
|---|---|---|
| RuntimeError: cannot reshape tensor of 0 elements | steps × strength < 1 | 提高 steps |
| 改动太大 | strength 太高 | 降到 0.25~0.5 |
| 输入图被拉伸 | 原图不是正方形 | 先裁剪成正方形再输入 |
模板规则
- steps × strength ≥ 1。
- 用 strength 控制改动程度。
- 只处理你有权使用的图片。
项目资源
| 用途 | 地址 |
|---|---|
| sd-turbo 模型页面(官方) | https://huggingface.co/stabilityai/sd-turbo |
| sd-turbo 许可证 | https://huggingface.co/stabilityai/sd-turbo/blob/main/LICENSE.md |
| Stability AI 许可说明 | https://stability.ai/license |
| diffusers 文档 | https://huggingface.co/docs/diffusers/index |
| diffusers GitHub | https://github.com/huggingface/diffusers |
strength 是一个旋钮:从“轻微修改”到“重新画一张”。