API 开发 #

API 概述 #

Stable Diffusion 提供多种 API 接口,支持程序化调用和自动化生成。

text
┌─────────────────────────────────────────────────────────────┐
│                      API 方式对比                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  方式            特点              适用场景                  │
│  ─────────────────────────────────────────────────────────  │
│  WebUI API      RESTful 接口     简单集成                   │
│  ComfyUI API    工作流驱动       复杂工作流                 │
│  Diffusers      Python 库        深度定制                   │
│  SD.Next API    增强版 API        高级功能                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

WebUI API #

启用 API #

text
启动 WebUI API:

启动参数:
webui-user.bat
set COMMANDLINE_ARGS=--api --listen

参数说明:
├── --api: 启用 API
├── --listen: 允许外部访问
├── --port 7860: 指定端口
└── --api-auth user:pass: API 认证

验证 API:
访问 http://127.0.0.1:7860/docs
查看 Swagger 文档

API 端点 #

text
主要 API 端点:

文生图:
POST /sdapi/v1/txt2img

图生图:
POST /sdapi/v1/img2img

局部重绘:
POST /sdapi/v1/img2img (带 mask)

模型列表:
GET /sdapi/v1/sd-models

采样器列表:
GET /sdapi/v1/samplers

进度查询:
GET /sdapi/v1/progress

中断生成:
POST /sdapi/v1/interrupt

选项设置:
POST /sdapi/v1/options

txt2img 请求 #

text
Python 请求示例:

import requests
import base64
import json

url = "http://127.0.0.1:7860"

payload = {
    "prompt": "a beautiful girl, anime style, masterpiece",
    "negative_prompt": "ugly, low quality, blurry",
    "steps": 25,
    "cfg_scale": 7,
    "width": 512,
    "height": 768,
    "sampler_name": "DPM++ 2M Karras",
    "seed": -1,
    "batch_size": 1,
    "n_iter": 1,
}

response = requests.post(
    url=f"{url}/sdapi/v1/txt2img",
    json=payload
)

result = response.json()

# 保存图像
for i, img_data in enumerate(result["images"]):
    img_bytes = base64.b64decode(img_data)
    with open(f"output_{i}.png", "wb") as f:
        f.write(img_bytes)

img2img 请求 #

text
img2img 请求示例:

import requests
import base64

url = "http://127.0.0.1:7860"

# 读取输入图像
with open("input.png", "rb") as f:
    img_base64 = base64.b64encode(f.read()).decode()

payload = {
    "prompt": "anime style, detailed",
    "negative_prompt": "ugly, low quality",
    "init_images": [img_base64],
    "denoising_strength": 0.5,
    "steps": 25,
    "cfg_scale": 7,
    "width": 512,
    "height": 768,
    "sampler_name": "DPM++ 2M Karras",
}

response = requests.post(
    url=f"{url}/sdapi/v1/img2img",
    json=payload
)

result = response.json()

完整参数列表 #

text
txt2img 完整参数:

{
    "prompt": "提示词",
    "negative_prompt": "负向提示词",
    "steps": 25,                    // 采样步数
    "cfg_scale": 7,                 // CFG Scale
    "width": 512,                   // 宽度
    "height": 512,                  // 高度
    "sampler_name": "Euler a",      // 采样器
    "seed": -1,                     // 种子
    "subseed": -1,                  // 子种子
    "subseed_strength": 0,          // 子种子强度
    "batch_size": 1,                // 批次大小
    "n_iter": 1,                    // 迭代次数
    "restore_faces": false,         // 面部修复
    "tiling": false,                // 平铺
    "eta": 0,                       // Eta
    "s_churn": 0,                   // Sigma churn
    "s_tmax": 0,                    // Sigma tmax
    "s_tmin": 0,                    // Sigma tmin
    "s_noise": 1,                   // Sigma noise
    "override_settings": {},        // 覆盖设置
    "script_args": [],              // 脚本参数
    "alwayson_scripts": {}          // 始终运行的脚本
}

使用 LoRA #

text
API 中使用 LoRA:

方法一:提示词语法
payload = {
    "prompt": "<lora:style:0.8>, a beautiful girl",
    ...
}

方法二:alwayson_scripts
payload = {
    "prompt": "a beautiful girl",
    "alwayson_scripts": {
        "LoRA": {
            "args": [
                {"lora_name": "style.safetensors", "weight": 0.8}
            ]
        }
    }
}

使用 ControlNet #

text
API 中使用 ControlNet:

import base64

# 读取 ControlNet 图像
with open("control_image.png", "rb") as f:
    control_img = base64.b64encode(f.read()).decode()

payload = {
    "prompt": "a beautiful girl",
    "alwayson_scripts": {
        "controlnet": {
            "args": [
                {
                    "enabled": True,
                    "module": "canny",
                    "model": "control_canny",
                    "weight": 1.0,
                    "image": control_img,
                    "guidance_start": 0.0,
                    "guidance_end": 1.0,
                }
            ]
        }
    }
}

ComfyUI API #

启用 API #

text
启动 ComfyUI API:

python main.py --listen

默认端口: 8188
API 文档: http://127.0.0.1:8188/docs

API 端点 #

text
ComfyUI API 端点:

提交任务:
POST /prompt

查询历史:
GET /history/{prompt_id}

获取图像:
GET /view

WebSocket:
WS /ws

上传图像:
POST /upload/image

获取对象信息:
GET /object_info

提交工作流 #

text
ComfyUI API 调用:

import requests
import json

url = "http://127.0.0.1:8188"

# 加载工作流
with open("workflow.json") as f:
    workflow = json.load(f)

# 提交任务
response = requests.post(
    url=f"{url}/prompt",
    json={"prompt": workflow}
)

prompt_id = response.json()["prompt_id"]

# 查询状态
import time
while True:
    history = requests.get(
        url=f"{url}/history/{prompt_id}"
    ).json()
    
    if prompt_id in history:
        print("完成")
        break
    
    time.sleep(1)

WebSocket 监听 #

text
WebSocket 实时监听:

import websocket
import json

def on_message(ws, message):
    data = json.loads(message)
    
    if data["type"] == "executing":
        print(f"执行中: {data['data']['node']}")
    
    elif data["type"] == "progress":
        print(f"进度: {data['data']['value']}/{data['data']['max']}")
    
    elif data["type"] == "executed":
        print("完成")
        images = data["data"]["output"]["images"]
        for img in images:
            print(f"图像: {img['filename']}")

ws = websocket.WebSocketApp(
    "ws://127.0.0.1:8188/ws",
    on_message=on_message
)
ws.run_forever()

Diffusers 库 #

安装和使用 #

text
安装 Diffusers:

pip install diffusers transformers accelerate

基础使用:

from diffusers import StableDiffusionPipeline
import torch

# 加载模型
pipe = StableDiffusionPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    torch_dtype=torch.float16
)
pipe.to("cuda")

# 生成图像
image = pipe(
    prompt="a beautiful girl, anime style",
    negative_prompt="ugly, low quality",
    num_inference_steps=25,
    guidance_scale=7,
    width=512,
    height=768,
).images[0]

image.save("output.png")

使用 LoRA #

text
Diffusers 使用 LoRA:

from diffusers import StableDiffusionPipeline
import torch

pipe = StableDiffusionPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    torch_dtype=torch.float16
)
pipe.to("cuda")

# 加载 LoRA
pipe.load_lora_weights("path/to/lora")

# 生成
image = pipe(
    prompt="a beautiful girl, anime style",
    cross_attention_kwargs={"scale": 0.8},  # LoRA 权重
).images[0]

使用 ControlNet #

text
Diffusers 使用 ControlNet:

from diffusers import StableDiffusionControlNetPipeline, ControlNetModel
from diffusers.utils import load_image
import torch

# 加载 ControlNet
controlnet = ControlNetModel.from_pretrained(
    "lllyasviel/sd-controlnet-canny",
    torch_dtype=torch.float16
)

# 加载管道
pipe = StableDiffusionControlNetPipeline.from_pretrained(
    "runwayml/stable-diffusion-v1-5",
    controlnet=controlnet,
    torch_dtype=torch.float16
)
pipe.to("cuda")

# 加载控制图像
control_image = load_image("control.png")

# 生成
image = pipe(
    prompt="a beautiful girl",
    image=control_image,
).images[0]

实用工具类 #

封装 API 客户端 #

text
Stable Diffusion API 客户端类:

import requests
import base64
import time
from typing import List, Optional

class SDWebUIClient:
    def __init__(self, url: str = "http://127.0.0.1:7860"):
        self.url = url
    
    def txt2img(
        self,
        prompt: str,
        negative_prompt: str = "",
        steps: int = 25,
        cfg_scale: float = 7,
        width: int = 512,
        height: int = 512,
        sampler: str = "DPM++ 2M Karras",
        seed: int = -1,
        batch_size: int = 1,
    ) -> List[bytes]:
        
        payload = {
            "prompt": prompt,
            "negative_prompt": negative_prompt,
            "steps": steps,
            "cfg_scale": cfg_scale,
            "width": width,
            "height": height,
            "sampler_name": sampler,
            "seed": seed,
            "batch_size": batch_size,
        }
        
        response = requests.post(
            f"{self.url}/sdapi/v1/txt2img",
            json=payload
        )
        
        images = []
        for img_data in response.json()["images"]:
            images.append(base64.b64decode(img_data))
        
        return images
    
    def img2img(
        self,
        prompt: str,
        init_image: bytes,
        denoising_strength: float = 0.5,
        **kwargs
    ) -> List[bytes]:
        
        img_base64 = base64.b64encode(init_image).decode()
        
        payload = {
            "prompt": prompt,
            "init_images": [img_base64],
            "denoising_strength": denoising_strength,
            **kwargs
        }
        
        response = requests.post(
            f"{self.url}/sdapi/v1/img2img",
            json=payload
        )
        
        images = []
        for img_data in response.json()["images"]:
            images.append(base64.b64decode(img_data))
        
        return images
    
    def get_progress(self) -> dict:
        response = requests.get(f"{self.url}/sdapi/v1/progress")
        return response.json()
    
    def interrupt(self):
        requests.post(f"{self.url}/sdapi/v1/interrupt")

# 使用示例
client = SDWebUIClient()

images = client.txt2img(
    prompt="a beautiful girl, anime style",
    width=512,
    height=768,
)

for i, img in enumerate(images):
    with open(f"output_{i}.png", "wb") as f:
        f.write(img)

批量生成工具 #

text
批量生成脚本:

import os
from typing import List
import time

def batch_generate(
    client: SDWebUIClient,
    prompts: List[str],
    output_dir: str = "outputs",
    **kwargs
):
    os.makedirs(output_dir, exist_ok=True)
    
    for i, prompt in enumerate(prompts):
        print(f"生成 {i+1}/{len(prompts)}: {prompt[:50]}...")
        
        images = client.txt2img(prompt=prompt, **kwargs)
        
        for j, img in enumerate(images):
            filename = f"{output_dir}/img_{i}_{j}.png"
            with open(filename, "wb") as f:
                f.write(img)
        
        print(f"保存到 {filename}")

# 使用示例
prompts = [
    "a beautiful girl with long hair",
    "a handsome man in suit",
    "a cute cat sitting on a chair",
]

batch_generate(client, prompts, output_dir="batch_outputs")

最佳实践 #

错误处理 #

text
完善的错误处理:

import requests
from requests.exceptions import RequestException
import time

def generate_with_retry(
    client: SDWebUIClient,
    prompt: str,
    max_retries: int = 3,
    retry_delay: float = 5.0,
    **kwargs
):
    for attempt in range(max_retries):
        try:
            return client.txt2img(prompt=prompt, **kwargs)
        except RequestException as e:
            print(f"请求失败 (尝试 {attempt + 1}/{max_retries}): {e}")
            if attempt < max_retries - 1:
                time.sleep(retry_delay)
            else:
                raise

并发控制 #

text
并发生成:

import concurrent.futures
from typing import List

def concurrent_generate(
    client: SDWebUIClient,
    prompts: List[str],
    max_workers: int = 2,
    **kwargs
):
    results = []
    
    with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {
            executor.submit(client.txt2img, prompt=prompt, **kwargs): prompt
            for prompt in prompts
        }
        
        for future in concurrent.futures.as_completed(futures):
            prompt = futures[future]
            try:
                images = future.result()
                results.append((prompt, images))
            except Exception as e:
                print(f"生成失败: {prompt}, 错误: {e}")
    
    return results

进度监控 #

text
进度监控:

import time

def generate_with_progress(client: SDWebUIClient, prompt: str, **kwargs):
    import threading
    
    result = None
    exception = None
    
    def generate():
        nonlocal result, exception
        try:
            result = client.txt2img(prompt=prompt, **kwargs)
        except Exception as e:
            exception = e
    
    thread = threading.Thread(target=generate)
    thread.start()
    
    while thread.is_alive():
        progress = client.get_progress()
        print(f"\r进度: {progress['progress']:.1%} | ETA: {progress['eta_relative']:.1f}s", end="")
        time.sleep(0.5)
    
    thread.join()
    
    if exception:
        raise exception
    
    return result

下一步 #

掌握 API 开发后,你可以:

  • 构建自动化图像生成系统
  • 集成到现有应用中
  • 开发图像生成服务
  • 创建批量处理工具

继续探索:

最后更新:2026-04-05