基于Stable Diffusion WebUI与FastAPI的二次元角色图像生成服务实践
2026/8/31 4:19:37 网站建设 项目流程

在二次元项目里,“捕获一只纱雾酱”听起来像一句玩笑话,但落到真实开发场景中,它其实是一个非常具体的需求:给一个角色名和一套特征描述,稳定生成一批形象统一、背景干净、可以直接用于头像或素材的二次元角色图片。很多人第一反应是去图片站批量下载,但那样既容易踩版权问题,也很难产出符合自己需求的素材。更可控的思路,是搭建一套本地 AI 绘画生成服务,用提示词约束角色特征,再通过 HTTP 接口把出图能力暴露给业务方。

这篇文章会从 Stable Diffusion WebUI 的安装开始,先跑通“提示词 -> 图片”的最小链路,然后用 FastAPI 封装一个角色形象生成服务。这样做的好处是,角色特征可以被固化成模板,生成参数可以被记录和复现,出图能力也能被其他系统调用,而不是一直停留在 WebUI 的调试页面里。

需要先说明一点:文中的“纱雾酱”只是示例角色名,用来演示如何通过提示词稳定描述一个虚拟角色。它不代表任何已存在的作品角色。实际项目中,建议替换成你自己的原创角色名,并在使用前确认提示词、底模和参考素材的授权范围。

1. 先理解“捕获一只纱雾酱”到底是在解决什么问题

1.1 角色形象生成的核心链路

如果要给一个角色生成图片,传统做法是找画师约稿,成本高、周期长。另一种做法是使用生成式模型,通过文字描述生成图片。这时“捕获”就不是爬取或下载,而是把“角色设定”转换成模型能理解的提示词,再让模型输出图片。

这条核心链路可以拆成四步:

  1. 定义角色特征:发型、瞳色、服装、表情、画风。
  2. 把特征翻译成提示词:英文标签、自然语言描述、负面标签。
  3. 模型推理:底模、采样器、步数、CFG 等参数共同决定输出效果。
  4. 筛选与复现:固定 seed 和参数,让同一角色能够反复生成。

很多初学者只关注第 2 步,认为“提示词写得好就能出好图”,但实际项目中,第 1 步和第 4 步才是最容易出问题的。角色特征没有固化,模型就像被问了一个模糊问题;不记录 seed,下次生成同一个角色时可能变成另一个人。

1.2 为什么不能直接爬图或手工一张张画

爬图的最大问题是质量不可控和版权风险。从图站批量下载的图片,风格不统一、清晰度参差不齐,而且大部分作品有明确版权声明,直接抓取后用于项目素材很容易引发纠纷。

手工一张张画的问题则是效率。就算只做头像,一个角色往往需要多角度、多表情、多场景的素材。在还没有确认角色最终风格之前,大规模手工绘制的时间成本太高。

AI 绘画解决的是“快速试探风格”和“批量生成基础素材”这两个问题。它更适合作为角色视觉探索的加速器:先快速生成大量候选图,找到喜欢的风格,再围绕固定的参数体系做批量产出。

1.3 为什么需要把生成能力封装成 HTTP 接口

Stable Diffusion WebUI 自带网页界面和 API,为什么还要用 FastAPI 再封装一层?

因为 WebUI 的 API 适合调试,不适合直接暴露给业务方使用。它没有参数白名单,没有调用频率限制,没有任务记录,没有图片落库逻辑。一旦上游系统把请求直接打到 WebUI,业务方可能会传一个不合理的分辨率或超长提示词,把显卡资源耗尽。

通过 FastAPI 加一层薄封装,可以做三件事:

  • 参数校验:限制分辨率、步数、CFG 的合理范围。
  • 任务记录:每次请求都记录 prompt、seed、参数和输出图片路径。
  • 访问控制:对外只暴露一个精简接口,内部再转发给 WebUI API。

这样 WebUI 变成纯粹的推理引擎,业务方只关心“提交参数、拿到图片”这一个动作。

2. 环境准备:把 Stable Diffusion WebUI 装好并打开 API

2.1 环境要求与版本确认

做 AI 绘画,显卡是最重要的硬件条件。Stable Diffusion WebUI 对显存比较敏感,学习环境至少要有 6 GB 显存,推荐 8 GB 以上。如果只有 CPU,也能生成,但速度会非常慢,不适合反复调参。

软件环境建议如下:

软件版本或说明
操作系统Windows 10/11、Ubuntu 20.04 以上、macOS
Python3.10 或 3.11,WebUI 启动脚本会创建独立虚拟环境
Git用于拉取 WebUI 源码
NVIDIA 驱动支持 CUDA 的显卡驱动,或使用 CPU 模式
Stable Diffusion WebUIAutomatic1111 版本,持续更新中

这里要注意:WebUI 项目更新很快,部署前先确认当前版本的安装文档和依赖要求。下面命令中的仓库地址如果因为网络原因无法访问,需要先确认自己的网络策略是否允许访问外部代码仓库;不要在无法访问的情况下反复尝试不完整的镜像或离线包。

2.2 安装 WebUI 并启动

在 Linux 或 macOS 下,可以通过 Git 拉取 WebUI 仓库:

git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git cd stable-diffusion-webui

然后启动:

./webui.sh --api

Windows 用户运行webui-user.bat。如果要携带--api参数,建议直接在webui-user.bat中修改启动参数,把COMMANDLINE_ARGS改成:

set COMMANDLINE_ARGS=--api

--api是必须的,因为它会开启 WebUI 的 HTTP API 服务。没有这个参数,后面 FastAPI 无法调用 WebUI 的出图接口。

首次启动时 WebUI 会创建 Python 虚拟环境并下载依赖,耗时取决于网络和机器性能,可能需要十几分钟。启动成功后终端会出现类似日志:

Running on local URL: http://127.0.0.1:7860

此时不要关掉终端,WebUI 进程需要保持运行。

2.3 检查模型是否加载成功

WebUI 启动后,默认会从models/Stable-diffusion目录读取模型。模型文件的常见格式是.safetensors.ckpt

把下载好的模型文件放入:

stable-diffusion-webui/models/Stable-diffusion/

文件名建议使用英文和下划线,不要包含空格、中文和特殊符号,否则在调用 API 时容易出现解析问题。放置完成后,在 WebUI 页面左侧模型下拉框中选择目标模型,或重启 WebUI。

命令行方式也可以验证模型是否被识别:

curl -s http://127.0.0.1:7860/sdapi/v1/sd-models

正常情况下会返回一个 JSON 数组,里面包含模型名称和文件名信息。如果返回空数组,说明模型文件没有被识别,需要检查目录路径和文件权限。

2.4 确认 API 端口可用

WebUI 的 API 默认监听127.0.0.1:7860。先用浏览器打开http://127.0.0.1:7860,确认页面能访问。

接着验证 API 本身:

curl -s http://127.0.0.1:7860/sdapi/v1/txt2img \ -H "Content-Type: application/json" \ -d '{"prompt":"1girl","steps":8}'

这一步会真的生成图片,所以第一次执行需要等待几秒到几十秒。如果返回的 JSON 中包含images字段,说明 API 已经可用。

注意:第一次调用 API 时,WebUI 需要把模型加载到显存,速度会比后续请求慢很多。这不是故障,耐心等待即可。

3. 提示词设计:用一段文本描述“纱雾酱”的长相

3.1 提示词的分层结构

角色形象的提示词不应该是一条长句,而应该按层级组织。我推荐分成四个部分:

  • 画质词:masterpiece、best quality、highres,控制整体完成度。
  • 内容词:1girl、solo,控制画面主体和人数。
  • 角色特征词:发型、瞳色、服装、表情,控制“像不像”。
  • 环境词:背景、光线、构图,控制画面氛围。

把提示词分层有两个好处。第一,后续想更换场景时,只需要替换环境词,角色特征词保持不动。第二,排查“为什么不像”时,可以按层级逐步删减,找到影响最大的标签。

3.2 正面提示词示例

下面是一个针对“纱雾酱”的正面提示词示例:

masterpiece, best quality, highres, 1girl, solo, pink hair, long hair, braided hair, blue eyes, shy, gentle smile, school uniform, soft lighting, clean background

解释一下关键部分:

  • 1girl表示画面中有一个女性角色。
  • solo表示单人,避免模型自动加入其他角色。
  • pink hair, long hair, braided hair定义发型和发色。
  • blue eyes定义瞳色。
  • shy, gentle smile定义表情。
  • school uniform定义服装。
  • soft lighting, clean background定义背景和光线。

这里有个容易误会的点:如果你使用的底模是在 Danbooru 这类标签体系上训练出来的,那么“纱雾酱”这四个字只是一个信息锚点,模型不一定认识。真正决定长相的是后面那串英文特征标签。所以不要以为写了角色名就等于定义了角色。

3.3 负面提示词和采样参数

负面提示词的作用是告诉模型“不要出现什么”。针对二次元角色图片,常用负面提示词如下:

lowres, bad anatomy, bad hands, extra fingers, extra limbs, missing fingers, blurry, jpeg artifacts, watermark, text, signature

把参数设置为:

  • steps:25
  • Sampler:Euler a 或 DPM++ 2M Karras
  • CFG Scale:7
  • Width/Height:512 x 512

这套参数接近大多数模型的默认推荐区间,适合第一次试水。先不要贪图高分辩率,512 x 512 在大多数显卡上都能顺利出图。

3.4 用 WebUI 完成第一次生成并检查效果

在 WebUI 页面中填入上述提示词,点击 Generate,等待图片生成。观察第一张图时,重点关注三件事:

  1. 角色是否完整:有没有多手指、断手臂、面部畸形。
  2. 特征是否稳定:发色、发型、瞳色是否符合预期。
  3. 背景是否干净:是否出现多余文字、人物、水印。

第一张不理想很正常。AI 绘画是概率生成,不可能第一次就完美。此时先不要继续随机点生成,而是把当前的 seed 记录下来,再微调提示词。关于 seed 的作用,后面会专门讲。

4. 用 FastAPI 把生成流程封装成角色生成服务

4.1 服务化要解决哪些问题

WebUI 已经提供了网页和 API,为什么业务方不能直接用?因为 WebUI API 的参数没有限制。业务方如果传一个steps=200width=1024,height=1024,batch_size=8的请求,一张显卡可能直接爆显存,其他人都无法使用。

服务化的目标不是重新实现生成逻辑,而是在 WebUI 前面加一道受控的门。FastAPI 在这件事上很合适:它自带参数校验,能生成接口文档,异步支持也足够应对图片生成这种长时间任务。

4.2 项目结构和依赖

创建项目目录:

sawamu_service/ ├── app.py ├── requirements.txt └── sd_client.py

requirements.txt内容:

fastapi==0.115.* uvicorn[standard]==0.30.* requests==2.32.* pydantic==2.*

安装依赖:

pip install -r requirements.txt

4.3 WebUI API 客户端实现

新建sd_client.py,封装对 WebUI API 的调用:

import json import requests SD_WEBUI_URL = "http://127.0.0.1:7860" def generate_image(params: dict) -> dict: payload = { "prompt": params["prompt"], "negative_prompt": params.get("negative_prompt", "lowres, bad anatomy, bad hands, extra fingers, blurry, watermark, text"), "width": params.get("width", 512), "height": params.get("height", 512), "steps": params.get("steps", 25), "cfg_scale": params.get("cfg_scale", 7.0), "sampler_name": params.get("sampler_name", "Euler a"), "seed": params.get("seed", -1), } resp = requests.post( f"{SD_WEBUI_URL}/sdapi/v1/txt2img", json=payload, timeout=180, ) resp.raise_for_status() data = resp.json() images = data.get("images") or [] if not images: raise RuntimeError("WebUI returned empty images") info = data.get("info", "") seed = extract_seed(info) return { "image_base64": images[0], "seed": seed, } def extract_seed(info: str): if not info: return None try: parsed = json.loads(info) return parsed.get("seed") except (ValueError, TypeError): return None

这段代码解决的问题是:把 WebUI 返回的复杂数据整理成我们关心的两个字段:图片 base64 和 seed。info字段是 WebUI 返回的 JSON 字符串,里面包含实际使用的 seed。手动解析它,比直接信任请求参数里的 seed 更准确。

4.4 FastAPI 接口与参数校验

新建app.py

from typing import Optional from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field from sd_client import generate_image app = FastAPI(title="Character Image Generation Service") class GenerateRequest(BaseModel): prompt: str = Field(..., min_length=1, description="正向提示词") negative_prompt: Optional[str] = Field( default="lowres, bad anatomy, bad hands, extra fingers, blurry, watermark, text", description="反向提示词", ) width: int = Field(default=512, ge=64, le=768, description="图片宽度") height: int = Field(default=512, ge=64, le=768, description="图片高度") steps: int = Field(default=25, ge=10, le=80, description="采样步数") cfg_scale: float = Field(default=7.0, ge=1.0, le=15.0, description="提示词权重") sampler_name: str = Field(default="Euler a", description="采样器名称") seed: Optional[int] = Field(default=-1, description="-1 表示随机种子") @app.post("/generate") async def generate(req: GenerateRequest): try: result = generate_image(req.dict()) except Exception as exc: raise HTTPException(status_code=502, detail=str(exc)) return {"code": 0, "data": result} @app.get("/health") async def health(): return {"status": "ok"}

这里最关键的是 Pydantic 的Field约束。widthheightstepscfg_scale都有了上下限。这样业务方传错参数时,FastAPI 会直接返回 422,而不是把请求打到 WebUI 上浪费显卡资源。

启动服务:

uvicorn app:app --host 0.0.0.0 --port 8000

浏览器打开http://127.0.0.1:8000/docs,能看到自动生成的接口文档,这是 FastAPI 的额外收益。

4.5 用 curl 验证接口并落盘图片

执行以下命令:

curl -s -X POST http://127.0.0.1:8000/generate \ -H "Content-Type: application/json" \ -d '{ "prompt": "masterpiece, best quality, 1girl, solo, pink hair, long hair, blue eyes, shy, school uniform, soft lighting, clean background", "negative_prompt": "lowres, bad anatomy, bad hands, extra fingers, blurry, watermark, text", "steps": 25, "cfg_scale": 7.0, "width": 512, "height": 512, "seed": 10001 }' \ -o result.json

查看返回内容前 300 个字符:

head -c 300 result.json

可以看到image_base64字段,它是图片的 base64 编码。把它解码保存为本地 PNG:

python -c "import base64,json; d=json.load(open('result.json')); open('sawamu_001.png','wb').write(base64.b64decode(d['data']['image_base64']))"

如果是 Windows,也可以写一个简单的 Python 脚本保存图片。建议在服务端接口中直接实现“保存图片到本地”的逻辑,这样每次生成都有文件落在磁盘里,方便后续查看。

可以把落盘逻辑加在 FastAPI 接口中,比如用 UUID 作为文件名:

import base64 import uuid from pathlib import Path OUTPUT_DIR = Path("outputs") OUTPUT_DIR.mkdir(exist_ok=True) @app.post("/generate") async def generate(req: GenerateRequest): try: result = generate_image(req.dict()) except Exception as exc: raise HTTPException(status_code=502, detail=str(exc)) filename = f"{uuid.uuid4().hex}.png" image_bytes = base64.b64decode(result["image_base64"]) (OUTPUT_DIR / filename).write_bytes(image_bytes) return { "code": 0, "data": { "filename": filename, "seed": result["seed"], "params": req.dict(), }, }

这样做的好处是返回给业务方的不再是超长 base64 字符串,而是一个可以在服务器上访问的文件名。真正生产项目中,还需要配置 Nginx 或对象存储来暴露图片,避免把 FastAPI 服务当成静态文件服务器。

5. 生成参数详解:同一角色为什么效果差异那么大

5.1 参数速查表

很多初学者在 WebUI 里生成图片,只需要点击 Generate,完全不关注参数。一旦开始用 API 批量生成,参数就成了决定成功率和成本的关键。

下面这张表总结了核心参数的作用和推荐区间:

参数作用推荐区间调大效果调小效果
steps采样步数20-30细节更丰富,但过步数会导致画面发灰或过拟合速度更快,但细节不足
cfg_scale提示词权重5-9更贴近提示词,但容易过饱和或图形畸变画面更自由,但可能偏离提示词
width/height输出分辨率512 或 576 附近显存占用增大,出图变慢图像模糊,细节丢失
seed随机种子正整数或 -1固定 seed 可复现同一构图-1 时每次随机
sampler_name采样器Euler a / DPM++ 2M Karras不同采样器风格和速度不同无明确好坏,看底模习惯

这里的数值是通用参考,不是绝对标准。不同底模对参数敏感度不同,换模型后需要重新试参。

5.2 seed 是复现角色形象的关键

seed 是一串随机数种子。使用相同的 seed、提示词、参数和模型,可以复现几乎相同的图片。

“捕获一只纱雾酱”这句话,放到工程上其实就是“把纱雾酱的特征稳定捕获下来”。seed 是这个目标里最关键的变量之一。

实际项目中会这样做:

  1. 先生成一批候选图,记录每张图的 seed。
  2. 人工筛选出最满意的一张。
  3. 固定这张图的 seed,微调其它参数,生成风格相近的另一批图。

如果某张图特别符合预期,但构图不够完整,不要重新随机生成,而是在固定 seed 的前提下,调整提示词中的环境词或采样步数。这样可以保留角色特征,只改变画面局部。

5.3 分辨率、采样器和显存的关系

分辨率提升一倍,显存占用可能是原来的三四倍。显存不足时,优先降低分辨率,而不是关闭其它进程。

显存占用从高到低的大致规律是:高清修复 > 高分辨率 > 高 batch_size > 增加采样步数。所以如果遇到显存问题,第一步把widthheight降到 512,第二步把batch_size设为 1,第三步再考虑使用 WebUI 的--medvram--lowvram启动参数。

--medvram会降低显存占用但减慢速度,--lowvram进一步降低占用,代价是更慢。真实生成环境里,这两种模式更多用于学习和调试,生产环境还是建议准备足够显存的显卡。

6. 常见问题与排查链路

下面这些问题,是我在实际调用 WebUI API 时最容易遇到的情况。每个问题都按“现象 -> 检查 -> 解决”的顺序整理成排查链路。

6.1 WebUI 没有启动或 API 返回 502

现象:调用 FastAPI 的/generate接口,返回 502,日志里出现ConnectionErrorHTTPConnectionPool

检查顺序:

  1. 确认 WebUI 终端还活着,窗口没有关闭。
  2. 确认 WebUI 启动时带了--api参数。
  3. 在服务器上执行curl -s http://127.0.0.1:7860/sdapi/v1/sd-models,看是否返回模型列表。
  4. 如果返回空,说明 WebUI 没有正常启动;如果连接拒绝,说明端口没监听。

常见原因是启动命令缺少--api。解决办法是关闭 WebUI,重新启动时加上--api

6.2 生成结果风格飘忽不定

现象:同一个提示词,连续生成多个图片,角色长相差异很大。

原因分两种:

  • seed 没有固定,默认 -1,所以每次结果都不同。
  • 提示词中的角色特征不明确,比如只写了“pink hair”但没有写发型、发色深浅、瞳色。

建议先将 seed 固定为一个正整数,然后把角色特征清单写完整。先保证单张图的复现能力,再去追求风格多样性。

6.3 显存不足导致 OOM

现象:生成过程中,WebUI 终端出现CUDA out of memory,或系统变得卡顿。

解决办法:

  1. widthheight降到 512。
  2. batch_size设为 1。
  3. 关闭浏览器中多次打开的 WebUI 页面,避免产生额外显存占用。
  4. 如果仍然不足,使用--medvram--lowvram重启 WebUI。

生产环境中,最好在 FastAPI 接口层控制并发。WebUI 默认串行处理请求,多个并发请求并不会加速,反而可能导致显存溢出。可以通过消息队列或任务锁,保证同一时间只有一个生成任务。

6.4 中文提示词不生效

现象:提示词里写“粉色长发、蓝眼睛”,生成出来的角色特征完全对不上。

原因是很多底模训练时使用英文或标签体系,CLIP 模型对中文支持有限。WebUI 页面里可能有中文翻译插件,但那是界面翻译,不是提示词翻译。

推荐做法是把角色特征拆成英文标签:

pink hair, long hair, blue eyes

而不是写:

粉色头发,长头发,蓝色眼睛

角色名“纱雾酱”可以放在提示词里作占位,但真正决定角色形象的是可被模型理解的标签。

6.5 生成的图片被安全过滤

现象:生成结果中完全没有图像,或返回的图片是空白、纯黑、模糊色块。

原因可能是提示词触发了底模的负面概念过滤,也可能是生成过程中出现异常导致图像为空。这时先检查 WebUI 日志,再检查提示词中是否存在容易触发过滤的内容。

建议在服务层增加提示词预检查:检测明显不合规的词汇,直接返回参数错误,而不是把请求发送到 WebUI。这样既保护模型环境,也避免业务方误用。

7. 最佳实践:从“接口能出图”到“生产能上线”

7.1 先把角色特征固化成模板

不要每次调用接口都从零写提示词。推荐把角色特征集中放到配置中,如下所示:

CHARACTER_TEMPLATES = { "sawamu": { "prompt": ( "masterpiece, best quality, 1girl, solo, " "pink hair, long hair, braided hair, blue eyes, " "shy, gentle smile, school uniform, " "soft lighting, clean background" ), "negative_prompt": ( "lowres, bad anatomy, bad hands, extra fingers, " "blurry, watermark, text" ), "width": 512, "height": 512, "steps": 25, "cfg_scale": 7.0, "sampler_name": "Euler a", } }

业务方调用时只需要传character_nameseed,服务端自动补全其它参数。这样能避免同一个角色在不同请求里出现完全不同的提示词,也方便后续统一调整画风。

7.2 对外服务必须做的安全与资源控制

FastAPI 接口不要直接暴露到公网。推荐在服务前面加一层 Token 校验,简单实现可以是:

from fastapi import Header, HTTPException TOKEN = "change-me-to-a-long-random-token" async def verify_token(x_token: str = Header(...)): if x_token != TOKEN: raise HTTPException(status_code=401, detail="invalid token")

然后把verify_token作为/generate接口的依赖。这样做至少能挡住无目的的扫描请求。

资源控制方面,除了参数校验,还要考虑并发控制。可以用一个线程锁或者信号量保证单卡环境同时只有一个生成任务,避免多个任务抢显存。

7.3 记录参数、seed 和图片,建立可追溯体系

每次生成都应该留下一条记录。建议至少保存以下信息:

字段示例
任务 ID8f3a...abcd
角色名sawamu
提示词masterpiece, best quality, ...
种子10001
参数steps=25, cfg=7.0
输出文件名8f3a...abcd.png
创建时间2025-01-01 12:00:00

这些记录可以写入 SQLite 或 JSON 日志。没有记录,就无法追溯哪张图是由哪组参数生成的,也无法在业务方反馈“角色不像”时做排查。

7.4 扩展方向:LoRA、ControlNet 与批量流水线

这套基础服务跑通后,可以继续做三件事。

第一是引入 LoRA,固化角色特征。提示词只能描述特征,LoRA 能训练出一组针对特定角色形象的权重。将 LoRA 放到 WebUI 的models/Lora目录后,可以在提示词中引入,例如<lora:sawamu:0.7>。训练 LoRA 需要准备一批风格统一的图片,并对标注质量做严格检查。

第二是引入 ControlNet,控制姿态和构图。角色素材往往需要不同姿势,ControlNet 可以基于骨架图或深度图约束生成结果,避免角色姿态失控。

第三是把任务异步化。当前接口是同步等待出图,在图片较多时会产生长连接。可以引入任务队列,接口先返回任务 ID,生成完再通过回调或轮询获取结果。

这四条扩展路径,每一步都会带来新的坑和新的优化空间。但从技术脉络上看,核心始终是:让角色特征可控、让生成参数可复现、让生成能力可以被稳定调用。理解了这一点,“捕获一只纱雾酱”就从一个娱乐向的需求,变成了一条可以持续优化的工程链路。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询