
在二次元项目里“捕获一只纱雾酱”听起来像一句玩笑话但落到真实开发场景中它其实是一个非常具体的需求给一个角色名和一套特征描述稳定生成一批形象统一、背景干净、可以直接用于头像或素材的二次元角色图片。很多人第一反应是去图片站批量下载但那样既容易踩版权问题也很难产出符合自己需求的素材。更可控的思路是搭建一套本地 AI 绘画生成服务用提示词约束角色特征再通过 HTTP 接口把出图能力暴露给业务方。这篇文章会从 Stable Diffusion WebUI 的安装开始先跑通“提示词 - 图片”的最小链路然后用 FastAPI 封装一个角色形象生成服务。这样做的好处是角色特征可以被固化成模板生成参数可以被记录和复现出图能力也能被其他系统调用而不是一直停留在 WebUI 的调试页面里。需要先说明一点文中的“纱雾酱”只是示例角色名用来演示如何通过提示词稳定描述一个虚拟角色。它不代表任何已存在的作品角色。实际项目中建议替换成你自己的原创角色名并在使用前确认提示词、底模和参考素材的授权范围。1. 先理解“捕获一只纱雾酱”到底是在解决什么问题1.1 角色形象生成的核心链路如果要给一个角色生成图片传统做法是找画师约稿成本高、周期长。另一种做法是使用生成式模型通过文字描述生成图片。这时“捕获”就不是爬取或下载而是把“角色设定”转换成模型能理解的提示词再让模型输出图片。这条核心链路可以拆成四步定义角色特征发型、瞳色、服装、表情、画风。把特征翻译成提示词英文标签、自然语言描述、负面标签。模型推理底模、采样器、步数、CFG 等参数共同决定输出效果。筛选与复现固定 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 装好并打开 API2.1 环境要求与版本确认做 AI 绘画显卡是最重要的硬件条件。Stable Diffusion WebUI 对显存比较敏感学习环境至少要有 6 GB 显存推荐 8 GB 以上。如果只有 CPU也能生成但速度会非常慢不适合反复调参。软件环境建议如下软件版本或说明操作系统Windows 10/11、Ubuntu 20.04 以上、macOSPython3.10 或 3.11WebUI 启动脚本会创建独立虚拟环境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 --apiWindows 用户运行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把参数设置为steps25SamplerEuler a 或 DPM 2M KarrasCFG Scale7Width/Height512 x 512这套参数接近大多数模型的默认推荐区间适合第一次试水。先不要贪图高分辩率512 x 512 在大多数显卡上都能顺利出图。3.4 用 WebUI 完成第一次生成并检查效果在 WebUI 页面中填入上述提示词点击 Generate等待图片生成。观察第一张图时重点关注三件事角色是否完整有没有多手指、断手臂、面部畸形。特征是否稳定发色、发型、瞳色是否符合预期。背景是否干净是否出现多余文字、人物、水印。第一张不理想很正常。AI 绘画是概率生成不可能第一次就完美。此时先不要继续随机点生成而是把当前的 seed 记录下来再微调提示词。关于 seed 的作用后面会专门讲。4. 用 FastAPI 把生成流程封装成角色生成服务4.1 服务化要解决哪些问题WebUI 已经提供了网页和 API为什么业务方不能直接用因为 WebUI API 的参数没有限制。业务方如果传一个steps200、width1024,height1024,batch_size8的请求一张显卡可能直接爆显存其他人都无法使用。服务化的目标不是重新实现生成逻辑而是在 WebUI 前面加一道受控的门。FastAPI 在这件事上很合适它自带参数校验能生成接口文档异步支持也足够应对图片生成这种长时间任务。4.2 项目结构和依赖创建项目目录sawamu_service/ ├── app.py ├── requirements.txt └── sd_client.pyrequirements.txt内容fastapi0.115.* uvicorn[standard]0.30.* requests2.32.* pydantic2.*安装依赖pip install -r requirements.txt4.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, jsonpayload, timeout180, ) 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.pyfrom typing import Optional from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field from sd_client import generate_image app FastAPI(titleCharacter Image Generation Service) class GenerateRequest(BaseModel): prompt: str Field(..., min_length1, description正向提示词) negative_prompt: Optional[str] Field( defaultlowres, bad anatomy, bad hands, extra fingers, blurry, watermark, text, description反向提示词, ) width: int Field(default512, ge64, le768, description图片宽度) height: int Field(default512, ge64, le768, description图片高度) steps: int Field(default25, ge10, le80, description采样步数) cfg_scale: float Field(default7.0, ge1.0, le15.0, description提示词权重) sampler_name: str Field(defaultEuler 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_code502, detailstr(exc)) return {code: 0, data: result} app.get(/health) async def health(): return {status: ok}这里最关键的是 Pydantic 的Field约束。width、height、steps、cfg_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 编码。把它解码保存为本地 PNGpython -c import base64,json; djson.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_okTrue) app.post(/generate) async def generate(req: GenerateRequest): try: result generate_image(req.dict()) except Exception as exc: raise HTTPException(status_code502, detailstr(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 是这个目标里最关键的变量之一。实际项目中会这样做先生成一批候选图记录每张图的 seed。人工筛选出最满意的一张。固定这张图的 seed微调其它参数生成风格相近的另一批图。如果某张图特别符合预期但构图不够完整不要重新随机生成而是在固定 seed 的前提下调整提示词中的环境词或采样步数。这样可以保留角色特征只改变画面局部。5.3 分辨率、采样器和显存的关系分辨率提升一倍显存占用可能是原来的三四倍。显存不足时优先降低分辨率而不是关闭其它进程。显存占用从高到低的大致规律是高清修复 高分辨率 高 batch_size 增加采样步数。所以如果遇到显存问题第一步把width和height降到 512第二步把batch_size设为 1第三步再考虑使用 WebUI 的--medvram或--lowvram启动参数。--medvram会降低显存占用但减慢速度--lowvram进一步降低占用代价是更慢。真实生成环境里这两种模式更多用于学习和调试生产环境还是建议准备足够显存的显卡。6. 常见问题与排查链路下面这些问题是我在实际调用 WebUI API 时最容易遇到的情况。每个问题都按“现象 - 检查 - 解决”的顺序整理成排查链路。6.1 WebUI 没有启动或 API 返回 502现象调用 FastAPI 的/generate接口返回 502日志里出现ConnectionError或HTTPConnectionPool。检查顺序确认 WebUI 终端还活着窗口没有关闭。确认 WebUI 启动时带了--api参数。在服务器上执行curl -s http://127.0.0.1:7860/sdapi/v1/sd-models看是否返回模型列表。如果返回空说明 WebUI 没有正常启动如果连接拒绝说明端口没监听。常见原因是启动命令缺少--api。解决办法是关闭 WebUI重新启动时加上--api。6.2 生成结果风格飘忽不定现象同一个提示词连续生成多个图片角色长相差异很大。原因分两种seed 没有固定默认 -1所以每次结果都不同。提示词中的角色特征不明确比如只写了“pink hair”但没有写发型、发色深浅、瞳色。建议先将 seed 固定为一个正整数然后把角色特征清单写完整。先保证单张图的复现能力再去追求风格多样性。6.3 显存不足导致 OOM现象生成过程中WebUI 终端出现CUDA out of memory或系统变得卡顿。解决办法将width和height降到 512。将batch_size设为 1。关闭浏览器中多次打开的 WebUI 页面避免产生额外显存占用。如果仍然不足使用--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_name和seed服务端自动补全其它参数。这样能避免同一个角色在不同请求里出现完全不同的提示词也方便后续统一调整画风。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_code401, detailinvalid token)然后把verify_token作为/generate接口的依赖。这样做至少能挡住无目的的扫描请求。资源控制方面除了参数校验还要考虑并发控制。可以用一个线程锁或者信号量保证单卡环境同时只有一个生成任务避免多个任务抢显存。7.3 记录参数、seed 和图片建立可追溯体系每次生成都应该留下一条记录。建议至少保存以下信息字段示例任务 ID8f3a...abcd角色名sawamu提示词masterpiece, best quality, ...种子10001参数steps25, cfg7.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生成完再通过回调或轮询获取结果。这四条扩展路径每一步都会带来新的坑和新的优化空间。但从技术脉络上看核心始终是让角色特征可控、让生成参数可复现、让生成能力可以被稳定调用。理解了这一点“捕获一只纱雾酱”就从一个娱乐向的需求变成了一条可以持续优化的工程链路。