Ollama+ComfyUI 多模态

环境全套部署 Ollama+ComfyUI+Python

学习目标

  1. 理解整套系统数据流:文本大模型生成指令 → Python 调度 → ComfyUI 执行绘图 → 结果保存输出
  2. 完成 Ollama、ComfyUI、Python 依赖全套本地部署
  3. 理解端口作用:Ollama 11434、ComfyUI 8188
  4. 编写连通检测脚本,验证两个服务是否正常可用

课程内容

1. 课程体系整体架构:Python+Ollama+ComfyUI 工作流逻辑拆解

整套 AI 自动化工作流链路:

  1. 用户输入简单自然语言描述
  2. Ollama 本地大模型:负责理解、优化提示词、生成文案、结构化输出;
  3. Python:作为中间调度层,调用 Ollama API 拿到提示词,再调用 ComfyUI API 下发绘图任务;
  4. ComfyUI:接收参数,执行文生图 / 图生图、采样、模型推理;
  5. 图片产出后,Python 获取生成结果,本地保存、归档。

核心特点:全部本地运行,不需要云端 API 密钥,数据不出本机。

2. Ollama 本地安装、环境变量配置、跨版本兼容处理

  1. 官网下载对应系统安装包完成安装;Windows 安装后会自动注册系统服务,后台常驻。
  2. 环境变量说明
    • OLLAMA_HOST:修改监听地址,默认 127.0.0.1,如需局域网访问改为 0.0.0.0
    • OLLAMA_NUM_PARALLEL:控制并发推理数量,低配机器建议设为 1
    • OLLAMA_MAX_LOADED_MODELS:控制同时驻留内存的模型数量
  3. 跨版本兼容:新版本 Ollama 部分接口字段变更,Python SDK 版本尽量和 Ollama 主版本匹配;旧模型可以直接继续使用,不需要重新下载。

3. Ollama 常用模型拉取:llama3、qwen、mistral(适配文案、指令优化场景)

终端命令拉取模型:

复制代码
ollama pull qwen3
ollama pull llama3
ollama pull mistral
  • qwen3:中文能力优秀,适合提示词改写、文案生成,本课程主力模型
  • llama3:通用能力强,英文提示词质量高
  • mistral:速度快,硬件要求低,低配电脑首选

测试对话(终端)

复制代码
ollama run qwen3

4. ComfyUI 完整部署:源码安装、依赖修复、启动报错解决

打开全世界最大的同性交友网站 github 下载 ComfyUI 最新版 v0.34.0 的ComfyUI_windows_portable_nvidia.7z 解压,路径不要有中文、空格。

复制代码
https://github.com/Comfy-Org/ComfyUI
  1. 双击运行 run_nvidia_gpu.bat,如果报错则运行 run_cpu.bat,直到看到如下日志并打开浏览器。黑窗口不要关闭;浏览器自动打开 http://127.0.0.1:8188如果浏览器没弹出,手动复制地址打开。
bash 复制代码
[INFO] Context impl SQLiteImpl.
[INFO] Will assume non-transactional DDL.
[INFO] Using RAM pressure cache.
[INFO] Starting server

[INFO] To see the GUI go to: http://127.0.0.1:8188

官方便携包不带任何 SD 大模型,必须自己下载 Checkpoint 模型,否则下拉框是空,无法生成图。CPU 模式生成很慢,512×512 一张图可能几分钟,内存建议≥12G。

下载 SD1.5 模型:v1‑5‑pruned‑emaonly.safetensors,放到ComfyUI_windows_portable\ComfyUI\models\checkpoints 文件夹下,重启 run_cpu.bat;或者界面按R刷新模型列表ComfyUI。

点击软件图标打开最简单的文生图工作流json文件。

文生图工作流文件内容如下,创建一个记事本名为最简单的文生图工作流,内容如下并将文件后缀改为json:

javascript 复制代码
{
  "3": {
    "class_type": "CheckpointLoaderSimple",
    "inputs": {
      "ckpt_name": "v1-5-pruned-emaonly-fp16.safetensors"
    }
  },
  "4": {
    "class_type": "CLIPTextEncode",
    "inputs": {
      "text": "masterpiece, best quality, 1girl, sunset, beautiful face",
      "clip": ["3", 1]
    }
  },
  "5": {
    "class_type": "CLIPTextEncode",
    "inputs": {
      "text": "lowres, blurry, bad anatomy, deformed, ugly",
      "clip": ["3", 1]
    }
  },
  "6": {
    "class_type": "EmptyLatentImage",
    "inputs": {
      "width": 512,
      "height": 512,
      "batch_size": 1
    }
  },
  "7": {
    "class_type": "KSampler",
    "inputs": {
      "model": ["3", 0],
      "positive": ["4", 0],
      "negative": ["5", 0],
      "latent_image": ["6", 0],
      "seed": 12345,
      "steps": 20,
      "cfg": 7,
      "sampler_name": "euler",
      "scheduler": "normal",
      "denoise": 1.0
    }
  },
  "8": {
    "class_type": "VAEDecode",
    "inputs": {
      "samples": ["7", 0],
      "vae": ["3", 2]
    }
  },
  "9": {
    "class_type": "SaveImage",
    "inputs": {
      "images": ["8", 0]
    }
  }
}

如果模型名不对,切换自己安装的模型名,点击右上角运行即可生成图片。

5. Python 环境配置:安装 ollama、requests、json、pillow 等核心依赖库

json 属于 Python 标准库,无需 pip 安装

复制代码
pip install ollama requests pillow
  • ollama:Ollama 官方 Python SDK,调用本地大模型
  • requests:http 请求,用来调用 ComfyUI REST API
  • pillow:图片读取、保存、预处理

6. 本地服务端口讲解:Ollama 默认 11434、ComfyUI 默认 8188 端口原理

  • Ollama:11434 :http 服务端口,所有大模型请求都走这个端口;访问 http://127.0.0.1:11434,服务正常会返回提示。
  • ComfyUI:8188 :WebUI 与 API 服务端口;网页界面、Python 提交绘图任务都访问该端口;访问 http://127.0.0.1:8188 打开 ComfyUI 网页。

Python 本质就是通过 http 向这两个端口发送请求完成调度。


实操案例

实操 1:终端命令部署 Ollama,成功拉取 qwen3 大模型,本地运行测试对话

  1. 安装 Ollama;打开终端
bash 复制代码
# 拉取模型
ollama pull qwen3.5:4b
# 进入交互式对话
ollama run qwen3.5:4b

输入:帮我写一段AI绘画正向与反向提示词,赛博朋克城市雨夜,观察返回结果。输入 /bye 退出对话。

实操 2:启动 ComfyUI,熟悉界面布局、节点面板、工作流保存 / 加载功能

  1. 启动 ComfyUI,浏览器打开 http://127.0.0.1:8188
  2. 界面认识:
    • 左侧:节点菜单;中间画布;队列生成按钮;
    • Save:保存工作流 json;Load:加载外部工作流 json
  3. 随便拖拽几个节点,保存工作流到本地,再重新 Load 导入,验证文件可用。

实操 3:Python 脚本测试连通性,分别检测 Ollama、ComfyUI 服务是否正常启动

完整可运行检测脚本 check_services.py

py 复制代码
import requests

OLLAMA_URL = "http://127.0.0.1:11434"
COMFYUI_URL = "http://127.0.0.1:8188"

def check_ollama():
    try:
        resp = requests.get(OLLAMA_URL, timeout=3)
        if resp.status_code == 200:
            print("✅ Ollama服务正常,端口11434")
            return True
    except Exception:
        print("❌ Ollama服务未启动,请运行Ollama程序")
        return False

def check_comfyui():
    try:
        resp = requests.get(COMFYUI_URL, timeout=3)
        if resp.status_code == 200:
            print("✅ ComfyUI服务正常,端口8188")
            return True
    except Exception:
        print("❌ ComfyUI服务未启动,请执行启动脚本")
        return False

if __name__ == "__main__":
    print("===== 服务连通检测 =====")
    check_ollama()
    check_comfyui()

运行脚本,分别测试两个服务开启 / 关闭状态下输出提示。


课后练习

练习 1:完整重装一遍全套环境,记录所有报错及解决方案(形成个人排错手册)

建议建立文档 day1_error_log.md,记录:

  1. 报错原文
  2. 触发条件
  3. 修复步骤

重点记录:端口占用、pip 依赖失败、显存报错、模型拉取慢等问题。

练习 2:通过 Ollama 终端命令,完成 5 次不同场景的文本生成(文案、提问、总结)

终端执行 ollama run qwen3,完成 5 类任务:

  1. 短视频带货文案
  2. 科普问题问答
  3. 一段文章摘要总结
  4. AI 绘画正向提示词生成
  5. 简单故事创作

练习 3:Python 编写检测脚本,自动判断两大服务是否运行,未运行则给出提示

基于上面的check_services.py做扩展:

  1. 如果两个服务全部正常,打印 系统就绪,可以开始调用
  2. 如果任意一个服务异常,打印哪些服务缺失;
  3. 设置超时时间,防止卡死;
  4. 返回布尔值,可以给后续程序调用判断是否继续执行。

参考扩展逻辑:

py 复制代码
def all_services_ok():
    o_ok = check_ollama()
    c_ok = check_comfyui()
    return o_ok and c_ok

if __name__ == "__main__":
    if all_services_ok():
        print("\n🎉 全部服务就绪,可以进行AI任务")
    else:
        print("\n⚠️ 存在服务未启动,请启动后重试")

Python 基础调用 Ollama

学习目标

  1. 理解 Ollama Python SDK 本地 API 调用原理,掌握核心参数作用
  2. 掌握普通生成、流式输出、多轮对话代码编写
  3. 学会解析返回数据,提取有效文本
  4. 增加基础异常捕获,处理服务未启动、模型不存在、超时等问题

课程内容

1、Ollama Python SDK 核心原理:本地 API 调用机制、无需外网请求

Ollama 安装完成后会在本机启动 HTTP 服务,监听127.0.0.1:11434

  • ollama python SDK 本质就是对本地 HTTP 接口做封装,所有请求都在本机完成,不需要公网、不需要 API‑key
  • 数据不经过第三方服务器,推理全部消耗本机 CPU/GPU 资源。
  • 两种调用方式:①官方 ollama 库;②直接 requests 请求原生 http 接口。本课程优先使用官方 SDK。

前置依赖安装(第 1 天已完成)

复制代码
pip install ollama requests

2、Ollama 核心参数详解:model、prompt、temperature、max_tokens、stream 流式输出

表格

参数 作用
model 指定使用的模型名称,例如qwen3llama3
prompt 用户输入提示词,要大模型处理的指令
temperature 温度,0~1;越低输出越严谨确定;越高创造性越强
max_tokens 最大输出 token,限制返回文本长度
stream True开启流式返回,逐块拿到结果;False一次性返回全部结果

3、基础文本生成、单次问答、多轮对话代码实现

  1. 单次文本生成:直接传入 prompt,获取完整返回结果。
  2. 流式生成:stream=True,循环迭代输出片段,实现打字机效果。
  3. 多轮对话:使用 messages 数组结构,携带历史上下文,实现记忆对话。
py 复制代码
import ollama

# 单次问答
resp = ollama.generate(model="qwen3", prompt="写一句朋友圈文案")
print(resp.response)

# 多轮对话格式 messages
messages = [
    {"role":"user","content":"介绍一下AI绘画"},
    {"role":"assistant","content":"AI绘画是人工智能生成图像技术"},
    {"role":"user","content":"有哪些工具可以使用"}
]
resp = ollama.chat(model="qwen3", messages=messages)
print(resp.message.content)

4、返回结果解析:提取纯文本内容、过滤冗余参数信息

ollama 返回是对象,内部包含大量元数据:模型名、耗时、token 数量等。

  • ollama.generate() → 有效文本:resp.response
  • ollama.chat() → 有效文本:resp.message.content

❌不要直接 print 整个返回对象,会打印大量无关调试信息,业务代码只提取需要的文本字段。

5、基础异常处理:服务未启动、模型不存在、超时报错解决

常见异常类型:

  1. ConnectionError:Ollama 服务没有启动,端口连不上
  2. ollama.ResponseError:模型不存在、模型拉取失败
  3. TimeoutError:推理超时,硬件性能不足导致
    使用try‑except捕获对应异常,给出友好提示而不是直接抛出堆栈。

实操案例

实操 1:Python 调用 Ollama 实现单句文案生成(短视频文案、朋友圈文案)

文件:day2_01_simple_gen.py

py 复制代码
import ollama

def generate_copy(prompt_text:str):
    res = ollama.generate(
        model="qwen3",
        prompt=prompt_text,
        temperature=0.7,
        max_tokens=200
    )
    return res.response

if __name__ == "__main__":
    text1 = generate_copy("写一条治愈风朋友圈文案")
    print("朋友圈文案:\n", text1)
    text2 = generate_copy("写一条短视频好物分享开头文案")
    print("\n短视频文案:\n", text2)

实操 2:实现流式输出对话,模拟 ChatGPT 实时打字效果

文件:day2_02_stream_chat.py

py 复制代码
import ollama

def stream_generate(prompt_text):
    stream = ollama.generate(
        model="qwen3",
        prompt=prompt_text,
        stream=True,
        temperature=0.6
    )
    print("输出:", end="", flush=True)
    for chunk in stream:
        piece = chunk.response
        print(piece, end="", flush=True)
    print()

if __name__ == "__main__":
    stream_generate("简单介绍什么是Ollama本地大模型")

实操 3:自定义温度参数,对比创意型、严谨型文本生成效果差异

文件:day2_03_temp_compare.py

py 复制代码
import ollama

prompt = "给我生成3个奇幻故事的简短小标题"

# low temp:严谨、重复少、创造力低
resp_low = ollama.generate(model="qwen3", prompt=prompt, temperature=0.1)
# high temp:创意强、随机性大
resp_high = ollama.generate(model="qwen3", prompt=prompt, temperature=1.0)

print("=== temperature=0.1(严谨)===")
print(resp_low.response)
print("\n=== temperature=1.0(高创意)===")
print(resp_high.response)

运行观察对比两者输出差异,理解 temperature 参数对生成效果的影响。


课后练习

练习 1:编写通用问答脚本,支持用户输入任意问题,调用 Ollama 返回精准答案

需求:

  1. 控制台循环接收用户输入问题;输入 quit 退出程序
  2. 将用户输入作为 prompt 传给 Ollama
  3. 打印返回答案

练习 2:封装文本生成函数,可自定义模型、创意度、生成字数

函数接口参考:

py 复制代码
def llm_generate(prompt:str, model:str="qwen3", temp:float=0.7, max_tokens:int=300)->str:
    """
    :param prompt: 用户提示词
    :param model: 使用模型名称
    :param temp: temperature温度
    :param max_tokens:最大输出token
    :return:返回生成文本字符串
    """
    # 在这里实现代码

练习 3:处理异常场景,服务断开时自动提示 "请启动 Ollama 服务"

给上面封装的llm_generate()函数增加异常捕获:

  1. 捕获连接异常,打印提示:请启动Ollama服务
  2. 捕获模型不存在异常,打印提示:模型不存在,请先执行ollama pull拉取对应模型
  3. 出现异常时返回空字符串,方便上层调用判断

参考异常捕获模板:

py 复制代码
import ollama
try:
    resp = ollama.generate(model=model, prompt=prompt, temperature=temp, max_tokens=max_tokens)
    return resp.response
except Exception as e:
    if "connection" in str(e).lower():
        print("请启动Ollama服务")
    elif "model" in str(e).lower():
        print("模型不存在,请先执行ollama pull拉取对应模型")
    return ""

测试方法:关闭 Ollama 软件,运行脚本,验证是否输出友好提示,而不是直接抛出报错堆栈。

Ollama 高阶文本能力

学习目标

  1. 掌握 AI 绘画正向 / 反向提示词编写规范,学会写高质量 Prompt
  2. 掌握 Ollama 强制 JSON 结构化输出,便于程序解析数据
  3. 实现文本改写、风格转换、批量生成提示词
  4. 理解不同模型在提示词优化任务上的选型差异
  5. 能够输出可直接供给 ComfyUI 使用的标准化提示词

课程内容

1、Prompt 工程基础:AI 绘图提示词优化逻辑、正向 / 反向提示词规范

  • 正向提示词:描述希望画面出现的元素:主体、构图、光影、材质、风格、画质词(8k,超细节,胶片质感)
  • 反向提示词:描述不希望出现的内容:畸形手、模糊、水印、文字、低分辨率、变形、噪点等瑕疵。

提示词优化逻辑:

用户简单口语描述 → 大模型扩充细节、增加画质修饰词 → 分离正向、反向提示词 → 输出给 ComfyUI。

书写规范:

  • 关键词用逗号分隔;
  • 优先主体,再风格,再光影,最后画质修饰;
  • 反向提示词固定通用瑕疵词,减少画面崩坏。

示例:

口语:一个女孩站在樱花树下

优化正向:少女,站在盛开的樱花树下,春日柔光,胶片摄影,细腻皮肤,8k,高清细节,氛围感

反向:畸形,手指残缺,模糊,水印,文字,低画质,变形

2、Ollama 结构化输出:强制 JSON 格式返回,适配程序自动化读取

Ollama 支持指定输出格式 format="json",强制大模型返回合法 JSON 字符串,避免返回多余自然语言描述,Python 可以直接json.loads()解析。

关键点:必须在 Prompt 中明确告诉模型 JSON 字段结构,配合format="json"参数,否则容易输出非法 JSON。

示例 prompt 模板:

复制代码
你是AI绘图提示词生成助手,严格只返回JSON,不要额外解释。
输出字段:
{
"subject":"画面主题",
"positive":"正向提示词",
"negative":"反向提示词",
"style":"绘画风格",
"size":"画面分辨率"
}
输入描述:{用户输入}

3、文本改写、精简、扩写、风格转换(治愈、赛博、国风、简约)

大模型可以按照指令完成文本变换:

  1. 扩写:把简单描述扩充丰富细节;
  2. 精简:压缩冗余描述,保留核心关键词;
  3. 风格迁移:同一主体切换国风、赛博朋克、日系治愈、极简写实等。

核心思路:在 prompt 里显式指定输出要求,例如:

将下面画面描述转换为国风绘画风格提示词,扩充光影、构图、材质细节。

4、批量文本生成逻辑:循环调用模型、参数批量迭代

批量任务基础逻辑:

  1. 准备输入列表(主题列表、描述列表);
  2. for 循环遍历每一条输入;
  3. 循环内调用 Ollama 生成提示词;
  4. 收集结果,保存内存 / 写入本地文件;

注意:循环之间增加短暂延时,防止短时间大量请求压垮 Ollama 服务。

5、模型选型技巧:不同大模型适配绘图提示词优化的优劣对比

表格

模型 特点 适用场景
qwen3 中文强,JSON 输出稳定,提示词扩充质量好 课程首选,中文提示词生成、结构化输出
llama3 英文提示词质量高,中文略弱 生成欧美风格、英文提示词
mistral 速度快,资源占用低;JSON 容易出错 低配机器,简单文本生成,不适合复杂 JSON

提示词 + JSON 结构化任务优先选择 qwen3。


实操案例

实操 1:利用 Ollama 将普通口语化文案,优化为专业 AI 绘图正向提示词

文件:day3_01_prompt_optimize.py

复制代码
import ollama

def optimize_prompt(raw_desc: str):
    prompt = f"""
你是AI绘画提示词专家,把用户口语描述扩写成专业正向提示词,增加风格、光影、画质细节,只输出正向提示词,不要多余文字。
用户描述:{raw_desc}
"""
    resp = ollama.generate(
        model="qwen3",
        prompt=prompt,
        temperature=0.6
    )
    return resp.response

if __name__ == "__main__":
    raw = "一个小猫坐在窗台看月亮"
    res = optimize_prompt(raw)
    print("原始描述:", raw)
    print("优化后正向提示词:\n", res)

实操 2:强制模型以 JSON 格式输出(包含主题、正向词、反向词、风格、尺寸)

文件:day3_02_json_output.py

复制代码
import ollama
import json

def gen_prompt_json(raw_desc: str):
    sys_prompt = f"""
你是AI绘图提示词生成助手,严格输出JSON,不要任何额外解释。
输出json字段:
subject:画面主题字符串
positive:正向提示词,逗号分隔
negative:反向提示词,逗号分隔
style:绘画风格
size:推荐分辨率例如1024x1024
用户输入描述:{raw_desc}
"""
    resp = ollama.generate(
        model="qwen3",
        prompt=sys_prompt,
        format="json",
        temperature=0.5
    )
    # 解析json字符串为字典
    data = json.loads(resp.response)
    return data

if __name__ == "__main__":
    result = gen_prompt_json("古风江南水乡夜景")
    print(type(result))
    print("主题:", result["subject"])
    print("正向:", result["positive"])
    print("反向:", result["negative"])
    print("风格:", result["style"])
    print("尺寸:", result["size"])

实操 3:批量生成 10 组不同风格的风景绘图提示词

文件:day3_03_batch_scene.py

复制代码
import ollama
import json
import time

scene_list = ["高山云海", "海边落日", "森林溪流", "沙漠星空", "雪山湖泊",
              "古镇清晨", "瀑布峡谷", "海岛沙滩", "枫林山谷", "溶洞秘境"]
style_list = ["国风", "赛博朋克", "日系插画", "写实摄影", "油画"]

batch_result = []

for idx, scene in enumerate(scene_list):
    print(f"正在生成第{idx+1}/10组:{scene}")
    prompt = f"""
根据风景主题 {scene},随机选用一种风格,生成绘图提示词,返回JSON,字段subject,positive,negative,style,size。不要多余文字。
"""
    resp = ollama.generate(model="qwen3", prompt=prompt, format="json", temperature=0.7)
    item = json.loads(resp.response)
    batch_result.append(item)
    time.sleep(0.3)  # 请求间隔,防止压服务

# 打印全部结果
for one in batch_result:
    print(f"\n【{one['subject']}】 style:{one['style']}")
    print(f"positive:{one['positive']}")

# 保存批量结果
with open("batch_scene.txt","w",encoding="utf‑8") as f:
    for d in batch_result:
        f.write(json.dumps(d,ensure_ascii=False)+"\n")
print("\n批量生成完成,已保存 batch_scene.txt")

课后练习

练习 1:编写脚本,输入任意画面描述,自动输出标准化 ComfyUI 绘图提示词

需求:

  1. 控制台接收用户输入画面描述;输入 exit 退出程序
  2. 调用 Ollama 自动扩充为高质量正向提示词,生成通用反向提示词
  3. 打印输出:正向提示词、反向提示词,可直接复制粘贴到 ComfyUI 使用。

练习 2:实现 JSON 结构化输出,自动拆分正向、反向提示词,单独保存为变量

基于实操 2 代码改造:

  1. 函数返回字典,positivenegative作为独立字段;
  2. 增加异常捕获:JSON 解析失败捕获json.JSONDecodeError,返回空字典;
  3. 上层调用可以直接拿到 data["positive"]data["negative"],方便后续传给 ComfyUI API。

异常捕获参考片段

复制代码
try:
    data = json.loads(resp.response)
except json.JSONDecodeError:
    print("模型返回非法JSON数据")
    data = {}

练习 3:批量生成 20 组人物绘图提示词,保存到本地 txt 文件

需求:

  1. 准备人物主题列表(古风女子、机甲少年、街头少女等 20 个主题);
  2. 循环批量调用 Ollama 生成 JSON 格式提示词;
  3. 设置延时,避免请求过快;
  4. 将 20 组结果写入本地 person_prompt_20.txt;每条一行,json 字符串;
  5. 增加简单异常处理,单条失败不中断整个批量任务,打印哪一条生成失败。

拓展思考:

当 Ollama 返回 JSON 偶尔格式错乱如何处理?(提示:可以增加重试机制,失败重新调用一次)

ComfyUI 核心节点精讲

学习目标

  1. 理解 ComfyUI 完整文生图执行链路:加载模型→输入提示词→采样生成→保存图片
  2. 掌握 4 个最核心基础节点作用与连线逻辑
  3. 理解采样步数、CFG、种子、分辨率、采样器对画面的影响
  4. 学会正向 / 反向提示词配置,降低画面崩坏概率
  5. 掌握工作流保存、导出 JSON、加载复用,为后续 Python 远程调用做准备

课程内容

1、ComfyUI 核心工作流逻辑:加载模型→输入提示词→采样生成→保存图片

完整文生图执行链路:

  1. 加载模型:Checkpoint(大模型)载入,包含扩散模型、VAE、CLIP 文本编码器
  2. 输入提示词:正向、反向提示词交给 CLIP 编码,转为模型可识别向量
  3. 采样生成:KSampler 执行扩散采样,在噪声潜空间逐步去噪生成图片潜变量
  4. VAE 解码:把潜变量 latent 转为真实图像像素
  5. 保存图片 :SaveImage 输出图片到本地输出目录 ComfyUI/output

所有节点靠连线传递数据;数据流向是从左向右。

2、必备核心节点详解:Checkpoint 加载、CLIP 文本编码、KSampler 采样、SaveImage 保存

  1. CheckpointLoaderSimple(加载大模型)
    • 输入:选择 Stable Diffusion 大模型 ckpt/safetensors
    • 输出:MODEL(扩散模型)、CLIP(文本编码器)、VAE(图像解码器)
    • 添加节点->模型->加载器->checkpoint 加载器(简易)
  1. CLIPTextEncode(文本编码)
  • 输入:文本提示词、CLIP
  • 输出:CONDITION(条件向量);正向、反向提示词各用一个该节点
  • 添加节点->模型->条件->CLIP文本编码
  1. EmptyLatentImage(空潜图)
    • 设置生成图片宽高,生成空白噪声潜空间,决定输出图片分辨率
    • 添加节点->模型->Latent->空 Latent 图像
  1. KSampler(采样器)
    • 输入:model,positive 正向条件,negative 反向条件,latent_image 噪声图,seed 种子,steps 步数,cfg,sampler 采样器,scheduler 调度器
    • 输出:采样完成后的 latent 潜变量
    • 添加节点->模型->采样-> k 采样器
  1. VAEDecode(VAE 解码)
  • 将 latent 潜数据解码为图像
  • 添加节点->模型->Latent->VAE 编/解码
  1. SaveImage(保存图片)
    • 接收 image,保存到 output 文件夹
    • 添加节点->图像->保存图像

标准连线顺序:

CheckpointLoaderSimple → MODEL 给到 KSampler;CLIP 给到两个 CLIPTextEncode;VAE 给到 VAEDecode;EmptyLatentImage 给到 KSampler;KSampler 输出 latent 给到 VAEDecode;VAEDecode 输出 image 给到 SaveImage。

3、基础参数调节:步数、CFG 值、种子、分辨率、采样器选型

表格

参数 说明 常用参考值
seed 种子 随机种子;固定种子 + 参数完全一致,产出图片一致;-1 代表随机 ‑1 随机,固定数字可复现画面
steps 采样步数 去噪迭代次数;步数太低细节差;过高不会提升画质还浪费时间 20‑30 步
CFG 提示词强度;数值越大越严格遵守提示词;过高容易过曝、色彩崩坏;太低画面偏离描述 6‑8
width/height 输出分辨率,SD1.5 建议 512‑768;SDXL 建议 1024‑1024 SDXL:1024×1024
sampler 采样器 采样算法;euler、dpmpp‑2m‑karras 综合效果好 dpmpp‑2m‑karras(通用首选)

4、正向 / 反向提示词节点使用,画面瑕疵规避技巧

  • 两个CLIPTextEncode节点:
    • 第一个:正向提示词,画面想要的元素,主体、风格、光影、画质词
    • 第二个:反向提示词,传入 KSampler 的 negative 输入口
  • 通用反向提示词模板:
    lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, jpeg artifacts, signature, watermark, username, blurry

技巧:反向提示词不是越多越好,堆砌过多会污染画面;保留通用瑕疵词即可。

5、工作流保存、导出、重置、复用方法

  1. Save:保存工作流(UI 格式,用于网页端打开编辑,不能直接给 Python 调用)
  2. Save(API) :导出 API 格式 JSON!!这是 Python 调用 ComfyUI 必须的工作流文件,会把所有节点参数完整导出;

⚠️普通 Save 保存的 json 不能直接用于 API 调用,一定要选Save(API)

  1. Load:加载 API 工作流 json 文件
  2. Clear:清空画布重新搭建

重要:后续 Python 调用 ComfyUI 读取的就是Save(API)导出的 JSON 文件。


实操案例

实操 1:手动搭建完整文生图工作流,成功生成第一张 AI 图片

实操步骤:

  1. 打开 ComfyUI 网页界面 http://127.0.0.1:8188
  2. 清空画布,依次添加节点:
    • CheckpointLoaderSimple
    • 2 个 CLIPTextEncode
    • EmptyLatentImage
    • KSampler
    • VAEDecode
    • SaveImage
  3. 按照标准链路完成连线
  4. Checkpoint 选择 SDXL 基础模型;EmptyLatentImage 设置 1024×1024
  5. 正向提示词输入:国风少女,山间竹林,清晨薄雾,唯美,8k,超细节
  6. 反向提示词填入通用瑕疵词
  7. KSampler 参数:seed=-1,steps=25,cfg=7,sampler:dpmpp‑2m‑karras
  8. 点击队列生成按钮,等待出图,在 output 文件夹查看生成图片。
  9. 使用Save(API)导出该工作流,保存为 txt2img_base.json

实操 2:调节不同采样步数、CFG 值,对比画面清晰度、细节差异

基于上面搭建好的工作流,固定种子,只修改参数,对比效果:

  1. 固定 seed 为同一个数字(例如 123456),保证其他条件不变
  2. 实验组 A:steps=10,cfg=7 → 生成图片,观察细节模糊
  3. 实验组 B:steps=25,cfg=7 → 标准参数
  4. 实验组 C:steps=25,cfg=12 → CFG 过高,观察色彩、过曝问题
  5. 实验组 D:steps=25,cfg=3 → CFG 过低,画面脱离提示词描述

记录现象:步数不足细节缺失;CFG 过高画面炸裂;CFG 过低不听提示词。

实操 3:添加反向提示词,去除画面模糊、畸形、水印等问题

  1. 断开反向提示词节点,把 negative 输入留空,生成一张图片,观察畸形、模糊瑕疵。
  2. 重新接入反向提示词节点,填入通用反向提示词模板,再次生成。
  3. 对比两张图片差异,理解反向提示词作用。
  4. 导出当前完整工作流 txt2img_with_neg.json

课后练习

练习 1:独立搭建 3 套不同风格(国风、赛博朋克、日系清新)文生图工作流

要求全部手动搭建,不要导入现成工作流:

  1. 工作流 1:国风主题,正向提示词偏向古风山水人物
  2. 工作流 2:赛博朋克主题,城市霓虹、未来机械风格
  3. 工作流 3:日系清新插画,柔和光影,动漫质感

每套工作流都配置正向、反向提示词,设置合理分辨率、采样参数,均可以正常出图。

练习 2:记录最优参数组合,形成个人绘图参数模板

新建文档 comfyui_param_template.md,记录:

  1. SDXL 通用文生图最优参数:steps、CFG、采样器、分辨率
  2. 国风风格推荐参数
  3. 赛博朋克风格推荐参数
  4. 日系清新风格推荐参数
  5. 通用反向提示词完整模板

练习 3:保存所有工作流文件,命名分类便于后续 Python 调用

重点:全部使用 Save(API) 导出 API 格式 JSON(不能普通 Save)

文件命名规范:

  • txt2img_chinese_style.json 国风文生图
  • txt2img_cyberpunk.json 赛博朋克文生图
  • txt2img_japanese_fresh.json 日系清新文生图

把 3 个 json 文件统一放到项目目录下 comfy_workflow/ 文件夹,为第 6 天 Python 调用 ComfyUI 做准备。


思考题:

  1. Save 和 Save (API) 两种 json 有什么区别?为什么 Python 远程调用必须用 Save (API) 版本?
  2. 固定 seed 种子,所有参数不变,两次生成图片是否会完全一致?什么情况下会不一致?

ComfyUI 图生图与高清放大工作流

学习目标

  1. 掌握图生图核心节点,理解图生图的运行原理
  2. 学会图片加载、缩放、超分放大,实现小图转高清
  3. 理解 denoise(图生图重绘强度)对画面的影响
  4. 掌握分辨率适配、裁剪、画布填充实用技巧
  5. 能够排查图片格式、尺寸超限、显存不足等常见问题

课程内容

1、图生图核心节点:LoadImage 加载本地图片、ImageResize 尺寸调整

图生图(img2img)原理:不是完全重新生成,而是在原图噪声基础上做重绘修改

  • LoadImage :加载本地磁盘图片文件,输出 IMAGE 图像数据与 MASK 蒙版;支持 jpg、png;不支持 webp 等部分格式。
  • ImageResize :调整图片宽高尺寸,支持多种缩放模式:
    • nearest:最近邻,速度快画质差
    • bilinear:双线性,均衡通用
    • bicubic:双三次缩放,图像更平滑

图生图不再使用 EmptyLatentImage,而是用VAEEncode把输入图片编码成 latent 潜变量,送入 KSampler。

图生图基础链路:

LoadImageImageResizeVAEEncode → KSampler (latent 输入)

同时:CheckpointLoaderSimple、CLIPTextEncode 正向 / 反向提示词、VAEDecode、SaveImage 保持不变。

图生图json文件

javascript 复制代码
{
  "3": {
    "class_type": "CheckpointLoaderSimple",
    "inputs": {
      "ckpt_name": "v1-5-pruned-emaonly-fp16.safetensors"
    }
  },
  "4": {
    "class_type": "CLIPTextEncode",
    "inputs": {
      "text": "masterpiece, best quality",
      "clip": ["3", 1]
    }
  },
  "5": {
    "class_type": "CLIPTextEncode",
    "inputs": {
      "text": "lowres, blurry, deformed, ugly",
      "clip": ["3", 1]
    }
  },
  "10": {
    "class_type": "LoadImage",
    "inputs": {
      "image": ""
    }
  },
  "11": {
    "class_type": "VAEEncode",
    "inputs": {
      "pixels": ["10", 0],
      "vae": ["3", 2]
    }
  },
  "7": {
    "class_type": "KSampler",
    "inputs": {
      "model": ["3", 0],
      "positive": ["4", 0],
      "negative": ["5", 0],
      "latent_image": ["11", 0],
      "seed": 5678,
      "steps": 22,
      "cfg": 7,
      "sampler_name": "euler",
      "scheduler": "normal",
      "denoise": 0.6
    }
  },
  "8": {
    "class_type": "VAEDecode",
    "inputs": {
      "samples": ["7", 0],
      "vae": ["3", 2]
    }
  },
  "9": {
    "class_type": "SaveImage",
    "inputs": {
      "images": ["8", 0]
    }
  }
}

2、高清放大节点:Upscale、超分修复,模糊图片变高清原理

两种主流放大方案:

  1. LatentUpscale(潜空间放大):在 latent 潜空间放大,速度快,显存占用低;放大后再解码;细节提升有限。
  2. ImageUpscaleWithModel(AI 超分模型放大):使用 Real‑ESRGAN 等超分模型,对已经解码完成的图像做像素级修复放大;画质最好,显存消耗更大。

原理:

  • 普通放大只是拉伸像素;
  • AI 超分模型会预测补充丢失细节、纹理、边缘,把低分辨率模糊图重建为高清大图。

常用超分模型:RealESRGAN‑x4‑plus,放到models/upscale_models目录。

3、图片分辨率适配、比例裁剪、画布填充技巧

实际业务中原图尺寸五花八门,直接送入模型容易变形、出黑边:

  1. 按比例缩放:先缩放到目标长边,短边不足不强行拉伸;
  2. 裁剪(Crop):超出部分裁掉,保证宽高严格匹配模型输入尺寸;
  3. 画布填充(Pad):不足部分填充黑色 / 模糊边缘,不裁剪主体;

SDXL 推荐输入尺寸:1024×1024;不要传入奇数字分辨率,容易出现 bug。
节点:ImagePadForOutpaint 画布填充;ImageCrop 图片裁剪。

4、图生图强度参数详解:保留原图比例与创意修改平衡

核心参数 denoise strength(重绘强度),范围 0‑1

  • denoise=0:完全不修改,输出原图;
  • denoise=0.2‑0.4:低强度,微调原图,色彩、细节小改动,构图主体基本不变;
  • denoise=0.5‑0.6:中等强度,图生图常用;保留大致构图,大幅度更换风格;
  • denoise=0.7‑0.9:高强度;构图改动很大,接近文生图效果;
  • denoise=1.0:完全丢弃原图信息,等价文生图。

风格转换建议 denoise:0.5‑0.6;图片局部微调 denoise:0.2‑0.3。

5、常见报错:图片格式不兼容、尺寸超限、显存不足解决

  1. 图片加载失败
  • 不支持 webp、heic;转成 jpg/png;路径不要中文、空格;
  1. 尺寸超限
  • 分辨率设置过大,latent 体积超出显存;SDXL 尽量控制单边不超过 1536;大图先缩放再送入模型。
  1. 显存不足 OOM
  • 降低输出分辨率;
  • 开启模型 CPU 卸载;
  • 优先使用 latent 放大,少用像素级超分;
  • 关闭其他占用显卡程序;
  • 使用小模型。

实操案例

实操 1:搭建图生图工作流,将普通照片转化为动漫风格图片

文件:手动搭建工作流,导出 API 格式 img2img_anime.json

节点清单:

  1. CheckpointLoaderSimple(SDXL 大模型)
  2. LoadImage:加载一张真人照片
  3. ImageResize:缩放到 1024×1024
  4. VAEEncode:把图片编码为 latent
  5. 2×CLIPTextEncode:正向提示词:日系动漫风格,细腻上色,明亮光影;反向填入通用瑕疵词
  6. KSampler:latent 来自 VAEEncode;denoise=0.55;steps=25,cfg=7,采样器 dpmpp‑2m‑karras
  7. VAEDecode
  8. SaveImage

操作步骤:

  1. 加载一张真人实拍照片;
  2. 设置 denoise=0.55;正向提示词写动漫风格;
  3. 队列生成,观察照片转为动漫画风;
  4. Save (API) 导出工作流 img2img_anime.json

实操 2:使用高清放大节点,将 512 尺寸图片放大至 2K 高清画质

工作流:upscale_2k.json

流程:

文生图 / 图生图输出 512×512 小图 → ImageUpscaleWithModel(Real‑ESRGAN‑x4‑plus)放大 → SaveImage

512 ×4 =2048,得到 2K 图像。

操作要点:

  1. 确认超分模型文件放置在models/upscale_models
  2. 先生成一张 512 尺寸的图;
  3. 接入 AI 超分放大节点,执行生成;对比原图和放大后细节差异。

对比实验:LatentUpscale 潜空间放大 VS Real‑ESRGAN 像素超分放大,观察细节差距。

实操 3:调节图生图强度,实现原图微调、大幅改写两种效果

固定所有参数,只修改 denoise 重绘强度,使用同一张输入图片。

  1. denoise=0.3:低强度;只做调色、轻微美化,人物构图几乎不变;
  2. denoise=0.7:高强度;画面构图、形象发生很大改变;接近重新创作。

对比两张输出图片,记录 denoise 强度带来的画面变化。导出工作流 img2img_denoise_demo.json


课后练习

练习 1:针对 3 张不同原图,完成风格转换 + 高清修复全套操作

准备 3 张本地图片:人像照片、风景照片、静物照片。

对每张图片完整流程:

  1. LoadImage 加载 → 缩放适配 SDXL 尺寸;
  2. 图生图做风格改写(人像→国风;风景→油画;静物→赛博朋克);denoise 设置 0.5‑0.6;
  3. AI 超分放大到 2K;
  4. 保存输出图片;

观察不同原图在相同 denoise 下效果差异。

练习 2:搭建通用高清修图工作流,适配所有普通图片一键高清

要求:

  1. 输入任意图片,自动缩放;
  2. Real‑ESRGAN x4 超分放大;
  3. 不需要图生图重绘,只做高清修复;
  4. Save (API) 导出工作流 pure_upscale_workflow.json

后续 Python 脚本可以直接加载这个 API 工作流,传入图片路径一键高清。

练习 3:整理显存不足、图片加载失败的全套解决方案

新建文档 comfyui_img_error_solve.md,整理故障清单:

  1. 图片加载失败:报错现象、产生原因、处理步骤
  2. webp/heic 格式无法读取:处理办法
  3. OOM 显存耗尽:分级解决方案(分辨率、放大方式、模型卸载)
  4. 图片尺寸奇数值导致画面错乱
  5. denoise 参数设置不合理导致完全丢失原图内容

思考题:

  1. denoise=1 的时候图生图和文生图有什么区别?
  2. LatentUpscale 和 ImageUpscaleWithModel 分别适合什么场景?优缺点分别是什么?

Python 调用 ComfyUI API 提交绘图任务

学习目标

  1. 理解 ComfyUI API 工作原理,掌握 8188 端口远程调用机制
  2. 掌握使用 requests 向 ComfyUI 提交队列任务、轮询任务状态、获取生成图片
  3. 看懂 API 工作流 JSON 结构,学会动态修改节点参数
  4. 实现图片获取、本地保存、按时间命名
  5. 增加服务检测、超时异常处理

课程内容

1、ComfyUI API 接口原理:8188 端口远程调用机制、工作流 JSON 调度逻辑

ComfyUI 网页界面本身就是基于 API 实现。

  • 服务地址:http://127.0.0.1:8188
  • 核心接口:
    • /prompt:POST 提交工作流 JSON,返回任务 ID (prompt_id),任务进入队列排队执行
    • /history/{prompt_id}:查询任务执行历史,获取生成结果文件信息
    • /view:根据文件名下载生成的图片

调度流程:

  1. ComfyUI 网页使用 Save(API) 导出完整工作流 JSON;JSON 内部是所有节点 ID、节点类型、节点参数。
  2. Python 读取该 JSON,修改需要动态变化的字段(提示词、分辨率、步数等)。
  3. POST 发送 JSON 到 /prompt,拿到prompt_id任务编号。
  4. 循环轮询 /history/{prompt_id},判断任务是否完成。
  5. 任务完成后从 history 拿到输出图片文件名,调用/view下载图片字节流,保存到本地。

⚠️ 必须使用Save(API)导出的 JSON;普通 Save 的 UI 工作流 JSON 不能用于 API 调用。

2、Python requests 库调用 ComfyUI API 核心代码

整体流程伪代码:

复制代码
#1.读取API工作流json
#2.动态修改节点参数
#3.post提交 /prompt 获取prompt_id
#4.循环轮询history接口等待任务完成
#5.获取图片文件名,requests.get("/view")下载图片
#6.写入本地文件

依赖:第 1 天已安装 requests

3、工作流 JSON 文件解析:节点参数、输入输出映射关系

API‑JSON 结构示例片段:

复制代码
{
  "3": {
    "class_type": "KSampler",
    "inputs": {
      "seed": 12345,
      "steps":25,
      "cfg":7.0,
      ...
    }
  },
  "6": {
    "class_type":"CLIPTextEncode",
    "inputs":{"text":"正向提示词","clip":["4",0]}
  }
}
  • 外层 key:节点 ID 字符串 (数字字符串,如"3""6"
  • class_type:节点类型
  • inputs:节点输入参数;普通参数直接赋值;连线是数组格式 ["节点ID",输出端口索引]

动态修改数据方式:workflow_json["6"]["inputs"]["text"] = "新的正向提示词"

关键点:需要先在 ComfyUI 确认你要修改的功能对应的节点 ID

4、本地图片生成、结果回调、文件自动保存原理

  1. ComfyUI 推理完成后图片会先保存在自身output目录;同时在 history 记录输出文件名。
  2. Python 不直接读取 ComfyUI 本地 output 文件夹,而是调用/view?filename=xxx.png接口下载图片二进制数据。
  3. 拿到二进制 bytes,使用open(目标路径,"wb")写入磁盘,实现自定义路径保存。
  4. 没有回调接口,只能轮询查询 history判断任务是否完成。

5、服务状态检测、调用超时基础处理

  • 先访问根地址 http://127.0.0.1:8188 判断 ComfyUI 服务是否启动;
  • 提交任务设置 post 超时;轮询设置最大等待次数,防止脚本无限死循环;
  • 捕获连接异常、超时异常,输出友好提示。

实操案例

实操 1:导出 ComfyUI 基础文生图工作流 JSON 文件

  1. 打开第 4 天搭建好的基础文生图工作流;
  2. 点击菜单 Save(API),导出文件,命名:txt2img_api.json
  3. 将该 json 放到项目目录;
  4. 用 VS Code 打开 json,观察节点 ID,记录:
    • 正向提示词节点 ID
    • 反向提示词节点 ID
    • KSampler 节点 ID
    • EmptyLatentImage 分辨率节点 ID

示例假设:

正向 CLIPTextEncode 节点 ID:"6"

反向 CLIPTextEncode 节点 ID:"7"

KSampler 节点 ID:"3"

EmptyLatentImage 节点 ID:"5"

实操 2:编写 Python 脚本,远程调用 ComfyUI 自动生成图片

文件:day6_01_comfy_api_base.py

复制代码
import requests
import json
import time

COMFYUI_BASE = "http://127.0.0.1:8188"

def check_comfyui_service():
    try:
        resp = requests.get(COMFYUI_BASE, timeout=3)
        return resp.status_code == 200
    except Exception:
        print("ComfyUI服务未启动,请先启动ComfyUI")
        return False

def submit_workflow(workflow:dict):
    payload = {"prompt": workflow}
    resp = requests.post(f"{COMFYUI_BASE}/prompt", json=payload, timeout=10)
    res = resp.json()
    prompt_id = res["prompt_id"]
    return prompt_id

def wait_for_done(prompt_id:str, max_wait=120):
    waited = 0
    while waited < max_wait:
        resp = requests.get(f"{COMFYUI_BASE}/history/{prompt_id}", timeout=5)
        hist = resp.json()
        if prompt_id in hist:
            return hist[prompt_id]
        time.sleep(1)
        waited +=1
    raise TimeoutError("任务执行超时")

def download_image(filename:str, save_path:str):
    url = f"{COMFYUI_BASE}/view"
    params = {"filename":filename}
    resp = requests.get(url, params=params, timeout=15)
    with open(save_path,"wb") as f:
        f.write(resp.content)

if __name__ == "__main__":
    if not check_comfyui_service():
        exit(1)

    # 读取API工作流
    with open("txt2img_api.json","r",encoding="utf-8") as f:
        workflow = json.load(f)

    prompt_id = submit_workflow(workflow)
    print(f"任务已提交 prompt_id:{prompt_id},等待生成...")
    history_result = wait_for_done(prompt_id)

    # 获取输出图片文件名(SaveImage输出)
    outputs = history_result["outputs"]
    for node_out in outputs.values():
        if "images" in node_out:
            img_info = node_out["images"][0]
            fn = img_info["filename"]
            download_image(fn, "output_01.png")
            print(f"图片已保存至 output_01.png")
            break

运行前确认 ComfyUI 已经启动;执行脚本,脚本自动提交任务,下载图片到本地。

实操 3:修改脚本参数,通过 Python 修改提示词、分辨率、采样步数

文件:day6_02_modify_params.py

根据你实际导出 json 里的节点 ID 修改下面 ID 字符串

复制代码
import requests
import json
import time

COMFYUI_BASE = "http://127.0.0.1:8188"

# 复用上面函数 check_comfyui_service submit_workflow wait_for_done download_image

if __name__ == "__main__":
    if not check_comfyui_service():
        exit(1)

    with open("txt2img_api.json","r",encoding="utf‑8") as f:
        workflow = json.load(f)

    # =========动态修改节点参数(根据你实际节点ID修改)=========
    workflow["6"]["inputs"]["text"] = "赛博朋克雨夜都市,霓虹灯光,电影质感,8k,超细节" #正向提示词
    workflow["3"]["inputs"]["steps"] = 28       #采样步数
    workflow["3"]["inputs"]["cfg"] = 7.5        #CFG强度
    workflow["5"]["inputs"]["width"] = 1024     #宽度
    workflow["5"]["inputs"]["height"] = 1024    #高度
    # =====================================================

    prompt_id = submit_workflow(workflow)
    print(f"提交任务 {prompt_id}")
    hist_result = wait_for_done(prompt_id)
    outputs = hist_result["outputs"]
    for node_out in outputs.values():
        if "images" in node_out:
            fn = node_out["images"][0]["filename"]
            download_image(fn, "output_modify.png")
            print(f"已保存 output_modify.png")
            break

运行脚本:Python 修改工作流内部参数,提交任务生成新图。

重要提醒:如果你的 JSON 节点 ID 不是"3" "5" "6",代码不会生效,需要打开你的 txt2img_api.json 核对真实节点 ID。


课后练习

练习 1:实现 Python 动态修改工作流提示词,一键生成不同风格图片

需求:

  1. 定义风格列表:国风山水、赛博朋克城市、日系插画海边;
  2. for 循环遍历风格列表;每次循环复制一份原始 workflow 字典,修改正向提示词;
  3. 逐个提交任务;每张图片输出不同文件名;
  4. 增加 sleep 间隔,不要并发大量提交压垮 ComfyUI。

提示:复制字典使用 workflow_copy = json.loads(json.dumps(workflow)) 做深度拷贝,避免修改同一个字典对象。

练习 2:编写脚本自动检测 ComfyUI 服务状态,异常自动告警

改造封装函数:

  1. 脚本启动首先调用检测函数;ComfyUI 未启动打印告警文字,直接退出程序。
  2. 提交任务捕获连接异常、超时异常;捕获后打印:ComfyUI调用异常,请检查服务
  3. 任务最大等待时间设置为 150 秒,超时抛出提示。

练习 3:将生成的图片自动保存到指定文件夹,按时间命名

需求点:

  1. 创建输出目录 ./comfy_output/,不存在则自动创建;
  2. 使用datetime获取当前时间戳,文件名格式 gen_20260829_221030.png
  3. 下载图片保存到该目录下;
  4. 打印完整保存路径。

参考时间命名片段:

复制代码
import os
from datetime import datetime

out_dir = "./comfy_output"
os.makedirs(out_dir, exist_ok=True)
now_str = datetime.now().strftime("%Y%m%d_%H%M%S")
save_path = os.path.join(out_dir,f"gen_{now_str}.png")

Ollama + ComfyUI 端到端完整闭环

学习目标

  1. 打通完整 AI 生成链路:用户简单自然语言描述 → Ollama 生成结构化提示词 → Python 把提示词注入 ComfyUI 工作流 → 自动生成图片
  2. 掌握多模块之间数据传递,把 Ollama 输出的正向、反向提示词动态替换 ComfyUI‑API 工作流节点参数
  3. 增加全链路异常拦截:大模型返回空、JSON 解析失败、绘图超时、参数为空防护
  4. 将代码模块化封装,拆分工具函数,便于后续复用、扩展批量任务

课程内容

1、完整工作流闭环:用户输入简单描述→Ollama 优化专业提示词→Python 传递参数→ComfyUI 生成图片

整体业务链路:

  1. 用户输入口语化简单描述,例如:古风山水,山间明月
  2. Ollama (qwen3) 接收原始描述,输出结构化 JSON:subject、positive 正向提示词、negative 反向提示词、style 风格
  3. Python 解析 JSON,拿到正向、反向提示词字符串
  4. 读取 ComfyUI API 工作流 json,动态修改 CLIPTextEncode 节点的 text 参数
  5. 通过 ComfyUI API 提交任务,等待推理完成
  6. 下载图片,按时间命名保存到本地输出文件夹

整套系统全部本地运行,不需要外网 API 密钥。

2、多模块数据对接:Ollama 文本输出变量传入 ComfyUI API 参数

数据流转关键点:

  • Ollama 输出是内存中的字典变量 prompt_data["positive"]prompt_data["negative"]

  • 不能写入中间文件,直接内存传递给 ComfyUI workflow 字典;

  • ⚠️提交任务前必须做深度拷贝工作流 json 对象,避免多次循环生成时共用同一个对象导致参数错乱。

    深度拷贝工作流,防止修改原始模板

    workflow_template = json.load(open("txt2img_api.json","r",encoding="utf‑8"))
    workflow = json.loads(json.dumps(workflow_template))

    把ollama输出赋值给工作流节点

    workflow["6"]["inputs"]["text"] = prompt_data["positive"]
    workflow["7"]["inputs"]["text"] = prompt_data["negative"]

3、正向 / 反向提示词动态替换逻辑

  1. 在 ComfyUI 导出的 API 工作流确认节点 ID:
    • 正向提示词节点 ID:如 "6"(CLIPTextEncode)
    • 反向提示词节点 ID:如 "7"(CLIPTextEncode)
  2. Python 读取 Ollama 返回字典,覆盖两个节点的text输入字段;
  3. 如果 Ollama 返回的 negative 为空字符串,程序自动填充一套通用反向瑕疵词兜底,防止反向提示词为空引发画面崩坏。

通用兜底反向提示词:

复制代码
lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, cropped, worst quality, low quality, blurry, watermark, signature

4、链路异常处理:大模型生成失败、绘图超时、参数为空拦截

链路每一步都有可能失败,需要分层拦截:

  1. Ollama 服务不可用:直接返回错误,不往下执行绘图;
  2. Ollama 返回 JSON 解析失败:重试 1 次,仍然失败返回;
  3. 解析后 positive 正向提示词为空字符串:拦截,不提交 ComfyUI 任务;
  4. ComfyUI 服务未启动:直接终止;
  5. ComfyUI 任务超时:捕获 TimeoutError;
  6. 生成结果无图片输出:捕获,打印日志。

流程控制原则:上游失败,不要执行下游昂贵的 GPU 绘图任务

5、基础自动化流程封装,代码模块化拆分

把功能拆分为独立函数:

  1. check_ollama_service():检测 Ollama 服务
  2. generate_ai_prompt(raw_desc:str):调用 Ollama,返回结构化提示词字典
  3. check_comfyui_service():检测 ComfyUI 服务
  4. comfy_submit_and_generate(workflow_dict):提交工作流,下载返回图片路径
  5. end_to_end_generate(raw_user_desc:str):对外统一入口函数,串联全部流程

模块化好处:函数可以单独调试;后续做批量生成、GUI 调用都可以直接复用end_to_end_generate()


实操案例

前置准备:

  1. Ollama 服务已启动;ComfyUI 服务已启动;
  2. 使用第 6 天导出 API 工作流 txt2img_api.json;核对自己 json 的正向、反向节点 ID;
  3. 依赖:pip install ollama requests

实操 1:搭建极简全链路流程,输入 "古风山水",自动生成优化提示词 + AI 图片

文件 day7_01_end2end_simple.py

复制代码
import ollama
import requests
import json
import time
import os
from datetime import datetime

OLLAMA_URL = "http://127.0.0.1:11434"
COMFYUI_URL = "http://127.0.0.1:8188"
WORKFLOW_PATH = "txt2img_api.json"
OUTPUT_DIR = "./e2e_output"
os.makedirs(OUTPUT_DIR, exist_ok=True)

# --------服务检测--------
def check_ollama_service():
    try:
        r = requests.get(OLLAMA_URL, timeout=3)
        return r.status_code ==200
    except Exception:
        print("[错误] Ollama服务未启动")
        return False

def check_comfyui_service():
    try:
        r = requests.get(COMFYUI_URL, timeout=3)
        return r.status_code ==200
    except Exception:
        print("[错误] ComfyUI服务未启动")
        return False

# --------Ollama生成结构化提示词--------
def generate_ai_prompt(raw_desc:str):
    sys_prompt = f"""
你是AI绘画提示词助手,严格输出JSON,不要额外解释。
字段:subject,positive,negative,style。
用户描述:{raw_desc}
"""
    try:
        resp = ollama.generate(
            model="qwen3",
            prompt=sys_prompt,
            format="json",
            temperature=0.6
        )
        data = json.loads(resp.response)
        return data
    except json.JSONDecodeError:
        print("[警告] Ollama返回非法JSON")
        return None
    except Exception as e:
        print(f"[警告] LLM调用异常 {e}")
        return None

# --------ComfyUI API工具函数--------
def submit_workflow(workflow:dict):
    payload = {"prompt":workflow}
    r = requests.post(f"{COMFYUI_URL}/prompt", json=payload, timeout=10)
    return r.json()["prompt_id"]

def wait_for_done(prompt_id, max_wait=120):
    waited = 0
    while waited < max_wait:
        r = requests.get(f"{COMFYUI_URL}/history/{prompt_id}", timeout=5)
        hist = r.json()
        if prompt_id in hist:
            return hist[prompt_id]
        time.sleep(1)
        waited +=1
    raise TimeoutError("绘图任务超时")

def download_image(filename, save_path):
    r = requests.get(f"{COMFYUI_URL}/view", params={"filename":filename}, timeout=15)
    with open(save_path,"wb") as f:
        f.write(r.content)

# --------端到端主流程--------
def end_to_end_run(user_input:str):
    if not check_ollama_service() or not check_comfyui_service():
        return None

    print(f"\n用户输入:{user_input}")
    prompt_data = generate_ai_prompt(user_input)
    if prompt_data is None or not prompt_data.get("positive"):
        print("[错误] 获取提示词失败,终止绘图")
        return None
    print(f"优化正向提示词:{prompt_data['positive']}")
    print(f"优化反向提示词:{prompt_data['negative']}")

    # 读取模板,深度拷贝
    with open(WORKFLOW_PATH,"r",encoding="utf‑8") as f:
        template = json.load(f)
    workflow = json.loads(json.dumps(template))

    # =========修改为你自己工作流真实节点ID=========
    workflow["6"]["inputs"]["text"] = prompt_data["positive"]
    workflow["7"]["inputs"]["text"] = prompt_data["negative"]
    # ============================================

    prompt_id = submit_workflow(workflow)
    print(f"绘图任务提交 prompt_id:{prompt_id}")
    hist_result = wait_for_done(prompt_id)

    # 获取输出图片
    for node_out in hist_result["outputs"].values():
        if "images" in node_out:
            img_file = node_out["images"][0]["filename"]
            time_str = datetime.now().strftime("%Y%m%d_%H%M%S")
            save_path = os.path.join(OUTPUT_DIR,f"gen_{time_str}.png")
            download_image(img_file, save_path)
            print(f"✅图片已保存 {save_path}")
            return save_path
    print("[错误]未获取生成图片")
    return None

if __name__ == "__main__":
    end_to_end_run("古风山水,山间明月,薄雾缭绕")

运行脚本,观察完整链路:用户简单描述 → Ollama 扩充提示词 → ComfyUI 出图保存。

实操 2:实现风格自定义,支持用户指定国风、赛博、日系等风格生成图片

修改end_to_end_run调用处,在传给 Ollama 的 prompt 中增加风格指令。

复制代码
def generate_ai_prompt_with_style(raw_desc:str, style:str):
    sys_prompt = f"""
你是AI绘画提示词助手,绘画风格强制使用:{style}。严格输出JSON,不要额外解释。
字段:subject,positive,negative,style。
用户描述:{raw_desc}
"""
    resp = ollama.generate(model="qwen3", prompt=sys_prompt, format="json", temperature=0.6)
    return json.loads(resp.response)

if __name__ == "__main__":
    # 测试不同风格
    end_to_end_run("城市夜景,高楼")
    # 在调用内部把风格参数传入generate_ai_prompt_with_style,例如 style="赛博朋克"

测试三组:国风赛博朋克日系插画,观察输出画面风格变化。

实操 3:完善异常拦截,避免空参数、无效文本导致的绘图失败

在主流程增加校验逻辑:

  1. 如果prompt_data["positive"]为空字符串,直接 return,不提交 ComfyUI;
  2. 如果prompt_data["negative"]为空,自动填充通用反向提示词兜底;
  3. 捕获绘图 TimeoutError;
  4. 用户输入为空字符串直接拦截。

关键片段:

复制代码
# 兜底反向提示词
default_neg = "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, cropped, worst quality, low quality, blurry, watermark, signature"

prompt_data = generate_ai_prompt(user_input)
if prompt_data is None or not prompt_data.get("positive"):
    print("[错误]正向提示词为空,放弃绘图")
    return None
if not prompt_data.get("negative"):
    prompt_data["negative"] = default_neg

课后练习

练习 1:封装完整单图生成函数,一行代码即可完成「输入描述→出图」全流程

对外封装函数接口:

复制代码
def generate_image(user_desc: str, style: str = None) -> str|None:
    """
    :param user_desc: 用户原始画面描述
    :param style: 指定绘画风格,None由大模型自选
    :return: 成功返回图片本地路径;失败返回None
    """
    # 实现完整逻辑

调用示例:

复制代码
path = generate_image("海边落日沙滩", style="油画")
print(path)

练习 2:实现反向提示词自动通用填充,规避画面瑕疵

需求:

  1. Ollama 返回 negative 为空的时候,自动填充内置通用反向词;
  2. 如果 Ollama 返回有内容,则把 Ollama 输出的反向词和内置通用反向词做拼接合并;
  3. 最终传给 ComfyUI。

示例逻辑:

复制代码
base_neg = "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, cropped, worst quality, low quality, blurry, watermark, signature"
llm_neg = prompt_data.get("negative","").strip()
if llm_neg:
    final_neg = llm_neg + "," + base_neg
else:
    final_neg = base_neg
workflow["7"]["inputs"]["text"] = final_neg

练习 3:测试 10 组不同场景描述,确保全链路稳定运行

准备 10 条测试描述(人像、风景、静物、幻想场景)示例:

复制代码
"樱花树下少女"
"雪山湖泊日出"
"未来机甲战士"
"江南古镇雨夜"
"猫咪趴在窗台看月亮"
"浩瀚宇宙星云"
"林间小木屋,秋天枫叶"
"深海发光水母"
"敦煌飞天壁画"
"赛博朋克雨中街头"

循环调用generate_image();每轮之间加 time.sleep (1);记录:

  1. 是否全部正常出图;
  2. 记录失败案例;
  3. 观察提示词生成质量。

出现 JSON 解析失败属于偶发,增加单次重试逻辑提升稳定性。


思考题

  1. 为什么每次提交 ComfyUI 之前要对 workflow 做深度拷贝?直接修改模板字典会发生什么问题?
  2. 如果 Ollama 和 ComfyUI 其中一个服务挂掉,程序应该怎么处理,是否继续往下执行?

AI 批量生成任务开发

学习目标

  1. 掌握批量任务的执行逻辑:循环迭代、任务队列、限速控制,避免服务过载与显存溢出
  2. 实现读取 txt /excel 关键词素材,批量调用 Ollama 生成提示词
  3. 批量调用 ComfyUI API 完成出图,实现自动分类、命名保存
  4. 学会批量任务调优:请求间隔、任务限速、显存保护策略
  5. 添加进度输出、日志文件记录,方便排查批量任务失败案例

课程内容

1、批量任务逻辑:循环调度、参数列表迭代、批量任务队列

批量两种实现思路:

  1. 串行循环(本课程采用,稳定):一条任务完整跑完(Ollama 生成提示词→ComfyUI 出图保存完成),再执行下一条。

优点:不会并发抢占 GPU 显存,不容易 OOM;调试简单;适合本地单机。

缺点:速度慢。

  1. 并发队列(进阶):多任务入队列,限制并发数量;ComfyUI 本身自带任务队列,但并发提交过多极易爆显存,不建议新手直接使用。

串行批量流程:

读取关键词列表 → for 循环遍历每一条素材

 ├─调用 Ollama 生成提示词

 ├─校验参数合法性,失败跳过当前条目,记录日志

 ├─深度拷贝 ComfyUI 工作流模板,替换正向 / 反向提示词

 ├─提交 ComfyUI 绘图任务,等待完成

 ├─下载图片,分类保存

 ├─间隔 sleep,保护 Ollama、ComfyUI 服务

 └─打印进度,写入日志

循环结束。

重要:每次循环必须深度拷贝工作流模板,不能复用同一个字典对象,否则参数会互相污染。

2、Python 读取本地 txt/excel 批量关键词,批量调用 Ollama 优化提示词

  • TXT 读取:每行一条主题关键词;readlines()读取,strip 去除换行、空格;过滤空行。
  • Excel 读取:使用pandas读取,读取指定列作为主题列表。

安装依赖:pip install pandas openpyxl

示例 txt 素材 batch_keyword.txt

复制代码
樱花树下少女
雪山湖泊日出
未来机甲战士
江南古镇雨夜
猫咪趴在窗台看月亮
浩瀚宇宙星云
林间小木屋,秋天枫叶
深海发光水母
敦煌飞天壁画
赛博朋克雨中街头
......

读取 txt 简单示例:

复制代码
with open("batch_keyword.txt","r",encoding="utf‑8") as f:
    lines = f.readlines()
keyword_list = [line.strip() for line in lines if line.strip()]

3、批量调用 ComfyUI 生成图片,自动分类保存、命名

保存策略:

  1. 根输出目录 ./batch_output/
  2. 可以按风格建立子文件夹,例如 ./batch_output/国风/./batch_output/赛博朋克/
  3. 文件命名规则:{序号}_{主题缩写}_{时间戳}.png
  4. 每条任务对应的提示词 json 同步保存到日志目录,方便回溯;
  5. 单条失败不中断整体批量,跳过当前,记录失败日志,继续下一条。

4、批量任务限速、防卡顿、显存溢出优化

本地 GPU 显存有限,批量极易 OOM,优化手段:

  1. 串行执行,禁止并发提交多个 ComfyUI 任务
  2. Ollama 调用后加短延时 time.sleep(0.2~0.5);ComfyUI 完成后延时 time.sleep(0.5~1),释放显存;
  3. 控制生成分辨率不要过大;SDXL 优先 1024×1024;
  4. 批量任务不要一次性几百条跑,可以分批次;
  5. 一旦出现 OOM,脚本记录日志,继续下一条;有条件可以增加简单显存检测。

5、批量任务进度打印、日志记录功能实现

日志包含信息:序号、原始关键词、生成的正向提示词、输出图片路径、任务状态(成功 / 失败、失败原因)。

两种日志输出:

  1. 控制台实时打印进度:[3/20] 正在处理:xxx
  2. 写入本地日志文件 batch_log.txt,追加模式 a;任务结束后可以回看哪些条目失败。

日志写入示例:

复制代码
def write_log(msg:str):
    with open("batch_log.txt","a",encoding="utf‑8") as f:
        f.write(f"{msg}\n")
    print(msg)

实操案例

前置:复用第 7 天的工具函数:服务检测、generate_ai_prompt、submit_workflow、wait_for_done、download_image。

工作流文件:txt2img_api.json,核对节点 ID。

实操 1:读取本地关键词列表,批量生成 20 组优化绘图提示词

文件 day8_01_batch_prompt.py

复制代码
import json
import time
import os
from day7_01_end2end_simple import check_ollama_service, generate_ai_prompt

KEYWORD_FILE = "batch_keyword.txt"
PROMPT_SAVE = "batch_prompt_result.json"

def read_keywords(file_path):
    with open(file_path,"r",encoding="utf‑8") as f:
        lines = f.readlines()
    kw_list = [x.strip() for x in lines if x.strip()]
    return kw_list

if __name__ == "__main__":
    if not check_ollama_service():
        exit()
    keywords = read_keywords(KEYWORD_FILE)
    print(f"读取到 {len(keywords)} 条关键词")
    result_list = []
    for idx, kw in enumerate(keywords[:20]):
        print(f"\n[{idx+1}/20] 处理关键词:{kw}")
        prompt_info = generate_ai_prompt(kw)
        if prompt_info:
            result_list.append(prompt_info)
        else:
            print(f"[{idx+1}/20] 提示词生成失败 {kw}")
        time.sleep(0.3)

    with open(PROMPT_SAVE,"w",encoding="utf‑8") as f:
        json.dump(result_list,f,ensure_ascii=False,indent=2)
    print(f"\n完成,提示词结果保存 {PROMPT_SAVE}")

运行效果:读取 txt 前 20 条关键词,调用 Ollama 批量生成结构化提示词,保存 json 文件。

实操 2:基于批量提示词,自动批量生成 20 张风格统一的 AI 图片

文件 day8_02_batch_img.py

复制代码
import json
import time
import os
from datetime import datetime
from day7_01_end2end_simple import (
    check_ollama_service, check_comfyui_service,
    submit_workflow, wait_for_done, download_image
)

WORKFLOW_PATH = "txt2img_api.json"
BATCH_OUTPUT_DIR = "./batch_output"
os.makedirs(BATCH_OUTPUT_DIR, exist_ok=True)
PROMPT_SAVE = "batch_prompt_result.json"
DEFAULT_NEG = "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, cropped, worst quality, low quality, blurry, watermark, signature"

def write_log(msg):
    with open("batch_log.txt","a",encoding="utf‑8") as f:
        f.write(f"{msg}\n")
    print(msg)

if __name__ == "__main__":
    if not check_ollama_service() or not check_comfyui_service():
        write_log("服务检测失败,退出批量任务")
        exit(1)

    # 读取已经批量生成好的提示词
    with open(PROMPT_SAVE,"r",encoding="utf‑8") as f:
        prompt_batch = json.load(f)

    # 读取工作流模板
    with open(WORKFLOW_PATH,"r",encoding="utf‑8") as f:
        workflow_template = json.load(f)

    total = len(prompt_batch)
    success_count = 0

    for idx, item in enumerate(prompt_batch):
        index_show = idx + 1
        subject = item.get("subject","unknown")
        positive = item.get("positive","").strip()
        llm_neg = item.get("negative","").strip()

        write_log(f"\n====[{index_show}/{total}] subject:{subject}====")
        if not positive:
            write_log(f"[{index_show}/{total}] 正向提示词为空,跳过")
            continue

        # 合并反向提示词
        final_neg = f"{llm_neg},{DEFAULT_NEG}" if llm_neg else DEFAULT_NEG

        # 深度拷贝模板,非常关键
        workflow = json.loads(json.dumps(workflow_template))
        # 修改节点ID,替换为你自己工作流真实ID
        workflow["6"]["inputs"]["text"] = positive
        workflow["7"]["inputs"]["text"] = final_neg

        try:
            prompt_id = submit_workflow(workflow)
            write_log(f"提交任务 prompt_id:{prompt_id}")
            hist = wait_for_done(prompt_id, max_wait=150)

            for node_out in hist["outputs"].values():
                if "images" in node_out:
                    img_filename = node_out["images"][0]["filename"]
                    time_str = datetime.now().strftime("%Y%m%d_%H%M%S")
                    save_name = f"{index_show}_{subject}_{time_str}.png"
                    save_path = os.path.join(BATCH_OUTPUT_DIR, save_name)
                    download_image(img_filename, save_path)
                    write_log(f"✅成功:{save_path}")
                    success_count +=1
                    break
        except Exception as e:
            write_log(f"❌任务失败:{str(e)}")
        time.sleep(0.8) # 任务间隔,释放显存

    write_log(f"\n====批量任务结束,成功:{success_count}/{total}====")

实操 3:添加进度条日志,实时显示当前批量任务进度

简易控制台进度打印,不需要第三方库,直接打印百分比。

如果需要美观进度条,可以安装 pip install tqdm

简易原生进度打印片段:

复制代码
total = len(prompt_batch)
for idx, item in enumerate(prompt_batch):
    index_show = idx +1
    progress = (index_show / total)*100
    print(f"\r进度:{index_show}/{total} ({progress:.1f}%)",end="")
    # ...业务逻辑

tqdm 版本(实操替换循环):

复制代码
from tqdm import tqdm

for idx, item in tqdm(enumerate(prompt_batch), total=len(prompt_batch), desc="批量生成图片"):
    # 内部业务代码
    pass

运行脚本,控制台实时输出当前进度百分比;全部任务完成查看batch_log.txt日志。


课后练习

练习 1:读取 Excel 关键词批量生成(pandas)

准备 excel 文件 batch_topic.xlsx,A 列存放画面主题关键词。

需求:

  1. pandas 读取 excel A 列,过滤空值得到关键词列表;
  2. 复用批量逻辑,完成 Ollama 提示词生成 + ComfyUI 批量出图;
  3. 图片输出目录按 excel 行号命名。

参考读取代码片段:

复制代码
import pandas as pd
df = pd.read_excel("batch_topic.xlsx")
kw_list = df.iloc[:,0].dropna().tolist()
kw_list = [str(x).strip() for x in kw_list]

练习 2:增加按风格分类输出文件夹

需求:

  1. Ollama 返回style字段;
  2. 根据 style 自动创建子文件夹,例如 ./batch_output/国风/
  3. 生成图片保存到对应风格子目录;
  4. 风格名称做文件名过滤,去除/ \ : * ? " < > |非法字符。

练习 3:批量异常健壮性完善

  1. 单条 Ollama JSON 解析失败增加一次重试机会;重试失败记录日志跳过;
  2. ComfyUI 任务超时捕获,记录失败日志,不中断整体批量;
  3. 统计:总任务数、成功数、失败数;批量结束打印汇总统计;
  4. 将每一条成功任务的subject、positive、negative、图片路径写入 csv 日志文件方便后期查阅。

思考题

  1. 批量任务为什么强烈建议串行而不是并发提交多个 ComfyUI 任务?
  2. 如果去掉深度拷贝json.loads(json.dumps(template)),直接修改 template 字典循环批量,会出现什么现象?

ComfyUI LoRA + ControlNet 精准控图与 API 动态调参

学习目标

  1. 理解 LoRA 模型作用,掌握风格、人物、场景 LoRA 加载、权重调节、适配技巧
  2. 掌握 ControlNet 常用控制类型:姿态、线稿、边缘构图,解决人物动作跑偏、构图不可控问题
  3. 熟悉 LoRA 权重、ControlNet 控制强度对画面的影响,掌握参数调优区间
  4. 导出包含 LoRA、ControlNet 的 API 格式工作流 JSON,理清节点输入输出关系
  5. 通过 Python API 动态修改 LoRA 权重、ControlNet 强度,实现代码层面控图

课程内容

1、LoRA 模型加载与调用:人物、风格、场景 LoRA 适配技巧

LoRA 是轻量微调模型,依附主 Checkpoint 大模型使用,不单独运行。

  • 存放路径:ComfyUI/models/loras/*.safetensors
  • 核心节点:LoraLoader
    输入:
  • model:主模型
  • clip:CLIP 文本编码器
  • lora_name:选择 LoRA 文件
  • strength_model:模型权重,影响画面内容、人物特征
  • strength_clip:文本权重,影响提示词语义

输出:更新后的 modelclip,再向下游节点传递。

参数经验区间:

  • 人物 LoRAstrength_model=0.6‑0.8,过高会面部崩坏
  • 风格 LoRAstrength_model=0.4‑0.7,不要超过 0.8,避免画风污染
  • 场景 LoRAstrength_model=0.5‑0.7

注意:LoRA 对主模型有版本匹配要求;SDXL LoRA 只能搭配 SDXL 主模型;SD1.5 LoRA 只能搭配 SD1.5 主模型。

工作流链路:

CheckpointLoaderSimple → 输出 model、clip 送入LoraLoader → LoraLoader 输出更新后的 model、clip 再给到 CLIPTextEncode、KSampler。

2、ControlNet 核心功能:姿态控制、线条控制、构图固定,解决 AI 画面跑偏问题

ControlNet 可以把外部参考信息(姿态、线稿、边缘图)嵌入扩散采样过程,强制约束生成画面构图、人物动作。

常用类型:

  1. OpenPose(姿态):输入姿态图,固定人物肢体动作,人物姿势不会乱飘
  2. Canny(边缘):输入线稿 / 草图,AI 沿着轮廓生成画面
  3. Depth(深度):固定空间景深、物体相对位置

核心节点:

  • ControlNetLoader:加载 ControlNet 权重模型,路径 models/controlnet/
  • ControlNetApply:把 controlnet 模型、参考图片、控制强度传入,输出带约束的 condition 给到 KSampler 的 positive 输入。

基础链路:

  1. LoadImage 读取姿态 / 线稿参考图 → 送入 ControlNetApply
  2. ControlNetLoader 加载对应 ControlNet 模型
  3. ControlNetApply 输出 condition,与 CLIPTextEncode 正向条件合并,给到 KSampler positive 输入。

ComfyUI 中多条件合并使用 ConditioningCombine 节点。

3、LoRA 权重、ControlNet 强度参数优化

  1. LoRA strength_model
  • 0:不生效;数值越大 LoRA 特征越强;>0.8 容易出现畸形、崩坏。
  • strength_clip 一般和 strength_model 保持一致,部分风格 LoRA 可略低。
  1. ControlNet strength(控制强度)
  • 0.3‑0.4:弱约束,大致参考构图,允许 AI 自由创作
  • 0.5‑0.7:中等约束,最常用;严格遵循姿态 / 轮廓,保留生成自由度
  • >0.8:强约束,画面高度复刻参考图线条姿态,但容易僵硬、细节死板。

实操建议:姿态控制优先 0.5‑0.7;线稿转图 0.6‑0.8。

4、带 LoRA/ControlNet 的工作流 JSON 导出与参数适配

  1. 完整搭建包含:Checkpoint → LoraLoader → ControlNet 整套节点;调试出图正常。
  2. 使用 Save(API) 导出工作流 JSON,不能用普通 Save。
  3. 在 JSON 中记录关键节点 ID:
    • LoraLoader 节点 ID:修改 lora_namestrength_modelstrength_clip
    • ControlNetApply 节点 ID:修改 strength 控制强度
    • ControlNetLoader 节点 ID:修改 controlnet 模型文件名

注意:JSON 里lora_name是字符串文件名,例如 "my_style_lora.safetensors",文件名必须和磁盘上完全一致。

5、Python 动态控制 LoRA 权重、ControlNet 参数

读取 API 工作流模板,深度拷贝后直接修改对应节点inputs字段:

复制代码
# 修改LoRA权重
workflow["12"]["inputs"]["strength_model"] = 0.65
workflow["12"]["inputs"]["strength_clip"] = 0.65
# 切换LoRA文件
workflow["12"]["inputs"]["lora_name"] = "character_lora.safetensors"

# 修改ControlNet控制强度
workflow["18"]["inputs"]["strength"] = 0.6

"12""18"是示例节点 ID,以你导出 JSON 真实 ID 为准。
如果需要动态切换 ControlNet 模型:修改ControlNetLoader节点control_net_name字段。


实操案例

前置准备:

  1. 准备 LoRA 文件放入models/loras;ControlNet 模型放入models/controlnet
  2. 准备一张 openpose 姿态参考图片;
  3. Ollama、ComfyUI 服务正常运行;复用第 7 天 API 工具函数。

实操 1:搭建 LoRA 专属绘图工作流,生成指定人物 / 风格图片

  1. ComfyUI 新建画布
  2. 节点:CheckpointLoaderSimple(SDXL) → LoraLoader
  3. LoraLoader 选择一个风格 / 人物 LoRA;设置strength_model=0.65strength_clip=0.65
  4. LoraLoader 输出 model 给到 KSampler;输出 clip 给到两个 CLIPTextEncode
  5. CLIPTextEncode 填写正向、反向提示词;EmptyLatentImage、KSampler、VAEDecode、SaveImage 完成基础文生图链路。
  6. 队列生成图片,观察 LoRA 带来人物 / 风格效果。
  7. 调整权重分别为 0.4 /0.75 对比画面差异。
  8. Save (API) 导出工作流:workflow_lora_api.json

实操 2:使用 ControlNet 姿态控制,固定人物动作生成精准画面

搭建 LoRA + OpenPose 姿态控制完整工作流:

  1. 在 LoRA 工作流基础上增加:
    • LoadImage:加载 openpose 姿态参考图
    • ControlNetLoader:选择 SDXL openpose controlnet 模型
    • ControlNetApply:输入 controlnet 模型、姿态图片、strength=0.6
    • ConditioningCombine:把 CLIPTextEncode 正向条件 和 ControlNetApply 输出条件合并,输出给到 KSampler positive。
  2. 正向提示词写人像描述;保持 LoRA 启用。
  3. 生成图片:观察人物姿态严格跟随参考姿态图。
  4. 修改 strength=0.3 和 0.8,对比约束强弱带来画面变化。
  5. Save (API) 导出完整工作流:workflow_lora_cn_api.json

实操 3:Python 动态修改 LoRA 权重,实现画面风格强弱微调

文件 day9_01_lora_cn_dynamic.py

节点 ID 替换为你导出 json 中的真实 ID

复制代码
import json
import time
import os
from datetime import datetime
# 复用第7天工具函数:服务检测、submit_workflow、wait_for_done、download_image

COMFYUI_URL = "http://127.0.0.1:8188"
WORKFLOW_TPL = "workflow_lora_cn_api.json"
OUT_DIR = "./lora_cn_output"
os.makedirs(OUT_DIR, exist_ok=True)

if __name__ == "__main__":
    # 读取模板
    with open(WORKFLOW_TPL,"r",encoding="utf‑8") as f:
        template = json.load(f)

    # 测试多组LoRA权重
    weight_list = [0.4,0.6,0.75]
    for w in weight_list:
        workflow = json.loads(json.dumps(template))
        # =========替换为你JSON真实节点ID=========
        workflow["12"]["inputs"]["strength_model"] = w
        workflow["12"]["inputs"]["strength_clip"] = w
        workflow["18"]["inputs"]["strength"] = 0.6
        # =======================================

        prompt_id = submit_workflow(workflow)
        print(f"提交任务 LoRA权重={w}, prompt_id={prompt_id}")
        hist = wait_for_done(prompt_id)
        for node_out in hist["outputs"].values():
            if "images" in node_out:
                fn = node_out["images"][0]["filename"]
                save_name = f"lora_w_{w}_{datetime.now().strftime('%H%M%S')}.png"
                save_path = os.path.join(OUT_DIR, save_name)
                download_image(fn, save_path)
                print(f"保存 {save_path}")
        time.sleep(1)

运行脚本,自动生成不同 LoRA 权重图片,观察画风强弱变化。


课后练习

练习 1:搭建一套「LoRA+ControlNet」精准控图通用工作流

要求:

  1. 集成:Checkpoint、LoraLoader、OpenPose ControlNet 整套节点;
  2. 正向、反向 CLIPTextEncode 完整配置;
  3. 可正常出图,姿态约束生效,LoRA 人物 / 风格生效;
  4. Save (API) 导出 workflow_lora_cn_general.json;记录全部关键节点 ID,备注到文档。

练习 2:编写 Python 脚本,动态切换 LoRA 模型、调整控制强度

需求:

  1. 定义 LoRA 列表,例如:["style_a.safetensors","style_b.safetensors"]
  2. 循环切换 lora_name;同时动态修改 LoRA 权重、ControlNet strength;
  3. 每次循环深度拷贝工作流模板;
  4. 生成图片文件名带上 LoRA 名称、权重参数;保存到对应输出文件夹。

练习 3:批量生成 10 张固定构图、不同风格的精准 AI 图片

完整链路:

  1. 固定 ControlNet 姿态参考图,保持构图、人物动作完全不变;
  2. 调用 Ollama 生成 10 组不同风格主题提示词;
  3. Python 注入提示词到工作流;动态切换不同风格 LoRA;
  4. 批量串行生成 10 张图片;每张人物姿态动作不变,但是画风、场景不同;
  5. 输出日志记录 LoRA 名称、权重、ControlNet 强度、提示词、图片路径。

提示:ControlNet 参考图是写死在工作流 LoadImage 节点中;如需动态替换参考图片,需要使用 ComfyUI API 上传图片接口,把图片上传到 ComfyUI 服务端,再修改节点image字段。


思考题

  1. LoRA strength_model 设置到 1.0 以上,大概率会出现什么现象?
  2. ControlNet strength=1 的时候,画面会出现什么效果?实际业务为什么一般不建议开到 1?

多模态视觉模型识图 + AI 自动重绘完整链路

学习目标

  1. 掌握 Ollama 部署视觉多模态模型,实现本地图片解析、画面描述
  2. 使用 Python 调用多模态模型读取本地图片,提取画面主体、构图、缺陷
  3. 根据图片分析结果自动生成图生图重绘提示词
  4. 打通完整链路:识图分析 → 生成优化提示词 → ComfyUI 图生图重绘
  5. 调优多模态参数,提升图片识别准确率,规避描述偏差

课程内容

1、Ollama 多模态模型部署(llama3‑vision),图片识别、画面描述能力

多模态模型可以同时接收图片 + 文本 Prompt,对图片做理解、描述、打分、找缺陷。

本地运行,不需要联网 API。

部署命令:

复制代码
ollama pull llama3‑vision

也可以使用 qwen‑vl 等国产视觉模型。

能力:

  • 描述画面主体、人物、场景、光影、构图
  • 识别画面缺陷:手部畸形、五官崩坏、构图失衡、画质模糊
  • 根据原图内容生成适配图生图的正向、反向提示词

限制:不能识别极小细节;对艺术抽象图描述会有偏差。

2、Python 调用多模态模型,读取本地图片、解析画面内容

ollama 库多模态调用格式:传入images=[图片路径]参数。

复制代码
resp = ollama.generate(
    model="llama3‑vision",
    prompt="请描述这张图片,输出结构化JSON",
    images=["./test.jpg"],
    format="json"
)

关键点:

  1. 图片路径为本地磁盘路径,Ollama 会读取图片编码;
  2. 强制输出 JSON 格式,方便 Python 解析;
  3. 图片格式优先 jpg/png;过大图片模型会自动缩放。

返回 JSON 设计字段:

复制代码
{
  "subject":"画面主体内容",
  "scene":"场景环境",
  "style":"当前画面风格",
  "defects":"画面存在缺陷",
  "positive":"图生图正向重绘提示词",
  "negative":"图生图反向提示词"
}

3、基于原图内容,自动生成优化重绘提示词

Prompt 设计思路(给视觉模型):

任务:分析输入图片,为图生图生成优化提示词。

  1. 识别画面主体、场景、构图;
  2. 找出图片缺陷:手部畸形、面部崩坏、模糊、畸变;
  3. 保留原图主体与构图,优化细节、画质;
  4. 输出适合图生图的 positive、negative 提示词;
  5. 严格输出 JSON,不要多余文字。

得到positivenegative后,直接交给 ComfyUI 图生图工作流使用。

4、图理解 + 图重绘全链路逻辑:识图→分析→优化提示词→重绘图片

完整端到端链路:

  1. 传入一张本地原图路径
  2. Ollama‑vision 加载图片,AI 识图分析,输出结构化重绘提示词
  3. Python 解析 JSON,拿到正向、反向提示词
  4. 读取 ComfyUI 图生图 API 工作流模板(img2img_api.json)
  5. 把原图送入 ComfyUI 图生图节点;动态替换正向 / 反向提示词,设置 denoise 重绘强度
  6. 提交 API 任务,等待生成重绘图片
  7. 保存输出图片,完成智能重绘

denoise 建议:0.4‑0.6,保留原图主体构图,优化细节画质。

5、多模态调用参数优化,提升画面识别精准度

核心参数:

  1. temperature:0.2‑0.4。识图任务调低,输出稳定,减少脑洞编造;
  2. 提示词尽量约束输出格式,强制 JSON;
  3. 如果图片过大,先做缩放,避免模型解析性能下降;
  4. 增加兜底逻辑:多模态 JSON 解析失败,使用默认提示词,不中断流程。

实操案例

前置条件

  1. Ollama 已拉取llama3‑vision模型;ComfyUI 正常运行;
  2. 第 5 天搭建好图生图工作流,Save (API) 导出 img2img_api.json
  3. 核对工作流节点 ID:正向 CLIP、反向 CLIP、denoise 所在 KSampler 节点、LoadImage 节点。
  4. 依赖:pip install ollama requests

⚠️ComfyUI API 图生图:LoadImage 节点默认读取服务端磁盘文件;如果要动态传入客户端本地图片,需要调用 ComfyUI /upload/image接口上传图片,拿到服务端文件名,赋值给 LoadImage 节点image字段。

实操 1:部署视觉大模型,Python 调用实现本地图片内容识别

文件 day10_01_vision_analyse.py

复制代码
import ollama
import json

VISION_MODEL = "llama3‑vision"

def analyse_image(image_path:str):
    prompt = """
你是图片分析助手。分析这张图片,识别主体、场景、画面缺陷。
输出用于图生图重绘的正向、反向提示词。
严格输出JSON,字段:subject,scene,style,defects,positive,negative。不要额外说明。
"""
    try:
        res = ollama.generate(
            model=VISION_MODEL,
            prompt=prompt,
            images=[image_path],
            format="json",
            temperature=0.3
        )
        data = json.loads(res.response)
        return data
    except json.JSONDecodeError:
        print("多模态返回JSON解析失败")
        return None
    except Exception as e:
        print(f"多模态调用异常:{e}")
        return None

if __name__ == "__main__":
    img = "./test_input.jpg"
    result = analyse_image(img)
    if result:
        print(json.dumps(result,ensure_ascii=False,indent=2))

运行:传入一张本地图片,控制台输出图片分析结果与重绘提示词。

实操 2:AI 自动分析原图缺陷,生成优化重绘提示词

挑选一张有瑕疵图片(手部畸形、模糊老照片)运行脚本。

观察输出:

  • defects字段识别出缺陷;
  • positive保留原图主体,增加高清、细腻细节;
  • negative自动带上缺陷描述,规避原有问题。

示例输出片段:

复制代码
{
  "subject":"一个站在草地上的少女",
  "scene":"户外草地,黄昏",
  "style":"写实照片",
  "defects":"手部畸形,画面模糊,面部细节丢失",
  "positive":"少女站在草地上,黄昏光影,高清写实,细腻皮肤,精致五官,优美手部,8k",
  "negative":"畸形手,模糊,五官崩坏,低画质"
}

实操 3:完成「原图识图→智能优化→ComfyUI 重绘」全自动化流程

需要 ComfyUI 上传图片接口,把客户端图片上传到 ComfyUI 服务端。

文件 day10_02_vision_img2img.py

复制代码
import ollama
import requests
import json
import os
from datetime import datetime

COMFYUI_URL = "http://127.0.0.1:8188"
WORKFLOW = "img2img_api.json"
OUT_DIR = "./vision_rewrite_output"
os.makedirs(OUT_DIR, exist_ok=True)
VISION_MODEL = "llama3‑vision"
DEFAULT_NEG = "lowres, bad anatomy, bad hands, blurry, watermark"

# --------复用工具函数--------
def check_comfyui_service():
    try:
        return requests.get(COMFYUI_URL,timeout=3).status_code ==200
    except Exception:
        print("ComfyUI未启动")
        return False

def upload_to_comfy(image_path:str):
    """上传本地图片到ComfyUI服务端,返回服务端图片文件名"""
    url = f"{COMFYUI_URL}/upload/image"
    with open(image_path,"rb") as f:
        files = {"image":f}
        resp = requests.post(url,files=files,timeout=20)
        return resp.json()["name"]

def submit_workflow(workflow:dict):
    return requests.post(f"{COMFYUI_URL}/prompt",json={"prompt":workflow},timeout=10).json()["prompt_id"]

def wait_for_done(prompt_id,max_wait=150):
    waited=0
    while waited<max_wait:
        r=requests.get(f"{COMFYUI_URL}/history/{prompt_id}",timeout=5)
        hist=r.json()
        if prompt_id in hist:
            return hist[prompt_id]
        import time
        time.sleep(1)
        waited+=1
    raise TimeoutError("任务超时")

def download_image(filename,save_path):
    r=requests.get(f"{COMFYUI_URL}/view",params={"filename":filename},timeout=15)
    with open(save_path,"wb") as f:
        f.write(r.content)

def analyse_image(image_path:str):
    prompt = """
分析图片,输出图生图重绘JSON:subject,scene,style,defects,positive,negative。
保留原图主体构图,修复缺陷。严格JSON输出。
"""
    res = ollama.generate(model=VISION_MODEL,prompt=prompt,images=[image_path],format="json",temperature=0.3)
    return json.loads(res.response)

# --------完整识图重绘主流程--------
def vision_rewrite(input_img_path:str):
    if not check_comfyui_service():
        return None
    print(f"[1]多模态分析图片 {input_img_path}")
    vision_result = analyse_image(input_img_path)
    if not vision_result or not vision_result.get("positive"):
        print("图片分析失败")
        return None

    print(f"解析得到正向提示词:{vision_result['positive']}")
    llm_neg = vision_result.get("negative","").strip()
    final_neg = f"{llm_neg},{DEFAULT_NEG}" if llm_neg else DEFAULT_NEG

    # 上传图片到ComfyUI服务端
    comfy_img_name = upload_to_comfy(input_img_path)
    print(f"[2]图片上传完成:{comfy_img_name}")

    # 读取图生图模板,深度拷贝
    with open(WORKFLOW,"r",encoding="utf‑8") as f:
        tpl = json.load(f)
    wf = json.loads(json.dumps(tpl))

    # =========修改为你自己工作流真实节点ID=========
    wf["10"]["inputs"]["image"] = comfy_img_name   # LoadImage节点
    wf["6"]["inputs"]["text"] = vision_result["positive"]
    wf["7"]["inputs"]["text"] = final_neg
    wf["3"]["inputs"]["denoise"] = 0.52             #图生图重绘强度
    # ============================================

    prompt_id = submit_workflow(wf)
    print(f"[3]提交绘图任务 {prompt_id}")
    hist = wait_for_done(prompt_id)

    for node_out in hist["outputs"].values():
        if "images" in node_out:
            fn = node_out["images"][0]["filename"]
            time_str = datetime.now().strftime("%Y%m%d_%H%M%S")
            save_path = os.path.join(OUT_DIR,f"rewrite_{time_str}.png")
            download_image(fn,save_path)
            print(f"✅重绘完成,保存:{save_path}")
            return save_path
    return None

if __name__ == "__main__":
    vision_rewrite("./test_input.jpg")

运行流程:本地图片上传 ComfyUI → llama3‑vision 识图分析 → 自动生成重绘提示词 → ComfyUI 图生图重绘优化图片输出。


课后练习

练习 1:实现任意图片自动智能重绘,保留主体、优化画质和风格

封装函数接口:

复制代码
def auto_rewrite_image(input_path:str, denoise:float=0.5) -> str|None:
    """
    :param input_path:本地原图路径
    :param denoise:重绘强度0‑1
    :return:输出重绘图片路径
    """

需求:

  1. 支持任意 jpg/png 输入;
  2. 多模态识图自动生成提示词;denoise 参数可外部传入;
  3. 失败返回 None,打印错误信息;
  4. 重绘图片按时间命名保存输出目录。

练习 2:封装多模态识图函数,支持批量图片解析优化

批量处理文件夹下多张图片:

  1. 遍历输入文件夹 ./input_batch/,读取所有 jpg/png;
  2. 循环调用auto_rewrite_image(),串行执行;每张图片完成 sleep (1);
  3. 生成日志记录:原图路径、分析得到的 subject、positive、输出图片路径;
  4. 单张失败记录日志,不中断批量流程。

练习 3:对比普通文生图和识图重绘的效果差异,优化提示词逻辑

实验对比:

  1. 方案 A:普通文生图,人工写提示词生成一张图;
  2. 方案 B:把 A 生成的图作为输入,交给多模态识图重绘;
    观察:
  • 是否修复原图缺陷;主体构图是否保留;
    优化多模态 Prompt:让模型更多保留原图构图,减少过度改动。

调优提示词增加约束:严格保留原图物体位置、构图布局,只优化细节画质,不要大幅度改变画面内容。


思考题

  1. denoise 设置为 0.85 做识图重绘,会发生什么现象?什么场景适合高 denoise?
  2. 如果不使用 ComfyUI /upload/image上传接口,直接本地路径填进 LoadImage 节点为什么不能跨客户端脚本使用?

AI 本地生成项目工程化与模块化重构

学习目标

  1. 学习工程化思想:把原来单文件大脚本拆分为配置、核心业务、日志、文件工具模块,便于维护、复用、二次开发
  2. 使用 JSON 配置文件统一管理端口、模型、默认参数、目录路径,修改参数不用改动业务代码
  3. 将提示词生成、ComfyUI 调用、多模态识图、图片 IO、异常捕获封装成类 / 独立函数
  4. 搭建完整日志系统:记录时间、入参、返回结果、成功失败状态,日志按日期归档
  5. 清理冗余代码,统一异常处理,提升代码健壮性,做成可复用项目模板

课程内容

1、项目工程化拆分:配置文件、核心调用、日志模块、文件管理分离

原来所有代码写在一个 py 文件,缺点:参数散落在代码各处;修改要改源码;代码臃肿;不方便复用。

模块化拆分原则:配置和业务分离;工具函数和主逻辑分离;日志和业务分离

模块划分:

  • config.json:全局配置,所有可调参数全部放这里
  • core/ai_generator.py:Ollama 文本 / 多模态提示词生成封装
  • core/comfy_client.py:ComfyUI API 客户端封装(提交任务、等待、上传下载图片)
  • utils/file_helper.py:文件夹创建、文件名过滤、遍历图片文件
  • utils/logger_helper.py:日志模块,控制台 + 文件双输出,按日期保存日志
  • main.py:程序入口,对外简单调用接口
  • batch_run.py:批量任务入口脚本
  • workflows/:存放所有 ComfyUI API 工作流 json 模板
  • input/:输入图片 / 关键词 txt
  • output/:输出生成图片
  • logs/:日志文件目录

2、JSON 配置文件管理:统一管理模型路径、端口、默认参数、保存路径

config.json存放全部可变参数:Ollama 地址、ComfyUI 地址、模型名称、默认 LoRA、ControlNet 强度、denoise、各种目录、兜底反向提示词。

修改参数只改配置文件,不碰业务 py 源码。

示例配置字段:

复制代码
{
  "ollama":{
    "base_url":"http://127.0.0.1:11434",
    "llm_model":"qwen3",
    "vision_model":"llama3‑vision",
    "temperature":0.6,
    "vision_temperature":0.3
  },
  "comfyui":{
    "base_url":"http://127.0.0.1:8188",
    "default_denoise":0.5,
    "lora_strength":0.6,
    "controlnet_strength":0.6
  },
  "path":{
    "workflow_dir":"./workflows",
    "input_dir":"./input",
    "output_dir":"./output",
    "log_dir":"./logs"
  },
  "default_negative":"lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, cropped, worst quality, low quality, blurry, watermark, signature",
  "batch_sleep":0.8
}

Python 读取配置:启动时加载一次 config.json,全局使用。

3、函数封装:提示词生成、绘图调用、图片保存、异常处理独立封装

面向对象封装思路:

  1. OllamaClient:封装文本生成提示词、多模态图片分析;服务检测;统一异常捕获
  2. ComfyUIClient:封装服务检测、上传图片、提交工作流、等待任务、下载图片
  3. 上层业务只调用类的方法,不关心底层 http 细节。

好处:

  • 底层接口变更,只修改类内部,上层调用代码不用动
  • 可以单独单元测试每个模块
  • 批量任务、单图任务、识图重绘全部复用底层类

4、日志系统搭建:记录每一次调用时间、参数、成功 / 失败状态

日志需要记录内容:

  • 时间戳
  • 调用类型:文生图 / 图生图 / 多模态识图 / 批量任务
  • 入参:用户描述、图片路径、关键参数
  • 输出:生成提示词、输出图片路径
  • 状态:SUCCESS / FAIL,附带失败异常信息

日志策略:

  1. 同时输出控制台 + 本地日志文件;
  2. 日志文件按日期命名,例如 2026‑08‑29.log,避免单日志文件过大;
  3. 日志目录不存在自动创建;
  4. 使用 python 标准库logging,不要自己简单 open 文件追加。

5、代码精简优化,去除冗余逻辑,提升运行效率

工程化优化要点:

  1. 删除重复的服务检测、sleep、路径创建代码,统一工具函数;
  2. 统一异常捕获,分层处理:网络异常、JSON 解析异常、IO 异常;
  3. 工作流模板只在初始化读取一次,运行时做深度拷贝,不重复读磁盘;
  4. 常量全部迁移到 config.json,代码中不写硬编码字符串;
  5. 增加参数校验,空字符串、空路径提前拦截,不向下传递无效参数。

实操案例

实操 1:搭建标准化项目文件夹结构,拆分各个功能模块

完整目录树:

复制代码
ai_local_project/
├── config.json                 # 全局配置文件
├── main.py                     # 主程序入口
├── batch_run.py                # 批量任务入口
├── core/
│   ├── __init__.py
│   ├── ai_generator.py         # Ollama封装:文本、多模态
│   └── comfy_client.py         # ComfyUI API客户端
├── utils/
│   ├── __init__.py
│   ├── file_helper.py          # 文件、目录工具
│   └── logger_helper.py        # 日志工具
├── workflows/                  # ComfyUI工作流模板
│   ├── txt2img_api.json
│   ├── img2img_api.json
│   └── lora_cn_api.json
├── input/                      # 输入素材:图片、关键词txt
├── output/                     # 生成输出图片
└── logs/                       # 运行日志

创建各个空文件和文件夹。

实操 2:编写全局配置文件,统一管理所有可调参数

config.json

复制代码
{
  "ollama": {
    "base_url": "http://127.0.0.1:11434",
    "llm_model": "qwen3",
    "vision_model": "llama3‑vision",
    "temperature": 0.6,
    "vision_temperature": 0.3
  },
  "comfyui": {
    "base_url": "http://127.0.0.1:8188",
    "default_denoise": 0.5,
    "lora_strength": 0.6,
    "controlnet_strength": 0.6,
    "task_max_wait": 150
  },
  "path": {
    "workflow_dir": "./workflows",
    "input_dir": "./input",
    "output_dir": "./output",
    "log_dir": "./logs"
  },
  "default_negative": "lowres, bad anatomy, bad hands, text, error, missing fingers, extra digit, cropped, worst quality, low quality, blurry, watermark, signature",
  "batch_sleep": 0.8
}

读取配置工具,utils/file_helper.py

复制代码
import json
import os

def load_config(config_path="config.json")->dict:
    with open(config_path,"r",encoding="utf‑8") as f:
        cfg = json.load(f)
    # 自动创建目录
    for key in ["workflow_dir","input_dir","output_dir","log_dir"]:
        p = cfg["path"][key]
        os.makedirs(p, exist_ok=True)
    return cfg

实操 3:封装全套调用类,实现代码极简调用

utils/logger_helper.py

复制代码
import logging
import os
from datetime import datetime

def setup_logger(log_dir:str):
    os.makedirs(log_dir,exist_ok=True)
    log_file = os.path.join(log_dir,f"{datetime.now().strftime('%Y‑%m‑%d')}.log")
    logger = logging.getLogger("ai_project")
    logger.setLevel(logging.INFO)
    # 去重handler
    if logger.handlers:
        logger.handlers.clear()
    fmt = logging.Formatter("%(asctime)s | %(levelname)s | %(message)s")
    # 文件输出
    fh = logging.FileHandler(log_file,encoding="utf‑8")
    fh.setFormatter(fmt)
    # 控制台输出
    ch = logging.StreamHandler()
    ch.setFormatter(fmt)
    logger.addHandler(fh)
    logger.addHandler(ch)
    return logger

core/ai_generator.py(Ollama 封装)

复制代码
import ollama
import json
import requests

class OllamaClient:
    def __init__(self, cfg:dict):
        self.cfg = cfg
        self.base_url = cfg["ollama"]["base_url"]
        self.llm_model = cfg["ollama"]["llm_model"]
        self.vision_model = cfg["ollama"]["vision_model"]
        self.temp = cfg["ollama"]["temperature"]
        self.vision_temp = cfg["ollama"]["vision_temperature"]

    def health_check(self)->bool:
        try:
            return requests.get(self.base_url,timeout=3).status_code==200
        except Exception:
            return False

    def generate_prompt(self, user_desc:str, style:str=None)->dict|None:
        sys_prompt = "你是AI绘画提示词助手,输出JSON字段:subject,positive,negative,style。不要多余文字。"
        if style:
            sys_prompt += f"强制绘画风格:{style}"
        try:
            resp = ollama.generate(
                model=self.llm_model,
                prompt=f"{sys_prompt}\n用户描述:{user_desc}",
                format="json",
                temperature=self.temp
            )
            return json.loads(resp.response)
        except Exception:
            return None

    def vision_analyze(self, img_path:str)->dict|None:
        prompt = "分析图片,输出图生图重绘JSON:subject,scene,style,defects,positive,negative。保留原图主体构图。严格JSON。"
        try:
            resp = ollama.generate(
                model=self.vision_model,
                prompt=prompt,
                images=[img_path],
                format="json",
                temperature=self.vision_temp
            )
            return json.loads(resp.response)
        except Exception:
            return None

core/comfy_client.py ComfyUI 客户端封装

复制代码
import requests
import json
import time
import os

class ComfyUIClient:
    def __init__(self, cfg:dict):
        self.cfg = cfg
        self.base_url = cfg["comfyui"]["base_url"]
        self.max_wait = cfg["comfyui"]["task_max_wait"]
        self.workflow_dir = cfg["path"]["workflow_dir"]

    def health_check(self)->bool:
        try:
            return requests.get(self.base_url,timeout=3).status_code==200
        except Exception:
            return False

    def load_workflow(self, name:str)->dict:
        fp = os.path.join(self.workflow_dir,name)
        with open(fp,"r",encoding="utf‑8") as f:
            return json.load(f)

    def upload_image(self, local_img_path:str)->str:
        url = f"{self.base_url}/upload/image"
        with open(local_img_path,"rb") as f:
            r = requests.post(url,files={"image":f},timeout=20)
        return r.json()["name"]

    def submit(self, workflow:dict)->str:
        r = requests.post(f"{self.base_url}/prompt",json={"prompt":workflow},timeout=10)
        return r.json()["prompt_id"]

    def wait_finish(self, prompt_id:str):
        waited = 0
        while waited < self.max_wait:
            r = requests.get(f"{self.base_url}/history/{prompt_id}",timeout=5)
            hist = r.json()
            if prompt_id in hist:
                return hist[prompt_id]
            time.sleep(1)
            waited +=1
        raise TimeoutError("ComfyUI任务超时")

    def download(self, filename:str, save_path:str):
        r = requests.get(f"{self.base_url}/view",params={"filename":filename},timeout=15)
        with open(save_path,"wb") as f:
            f.write(r.content)

main.py 入口,极简调用示例

复制代码
from utils.file_helper import load_config
from utils.logger_helper import setup_logger
from core.ai_generator import OllamaClient
from core.comfy_client import ComfyUIClient
import json
import os
from datetime import datetime

cfg = load_config("config.json")
logger = setup_logger(cfg["path"]["log_dir"])

ollama_client = OllamaClient(cfg)
comfy_client = ComfyUIClient(cfg)

def txt2img_generate(user_input:str, style=None):
    logger.info(f"开始文生图,用户输入:{user_input}, style:{style}")
    if not ollama_client.health_check() or not comfy_client.health_check():
        logger.error("服务未就绪")
        return None
    prompt_data = ollama_client.generate_prompt(user_input,style)
    if not prompt_data or not prompt_data.get("positive"):
        logger.error("提示词生成失败")
        return None
    llm_neg = prompt_data.get("negative","").strip()
    final_neg = f"{llm_neg},{cfg['default_negative']}" if llm_neg else cfg["default_negative"]

    tpl = comfy_client.load_workflow("txt2img_api.json")
    wf = json.loads(json.dumps(tpl))
    # 替换为你实际工作流节点ID
    wf["6"]["inputs"]["text"] = prompt_data["positive"]
    wf["7"]["inputs"]["text"] = final_neg

    pid = comfy_client.submit(wf)
    logger.info(f"提交绘图任务 pid:{pid}")
    hist = comfy_client.wait_finish(pid)
    out_dir = cfg["path"]["output_dir"]
    for node_out in hist["outputs"].values():
        if "images" in node_out:
            fn = node_out["images"][0]["filename"]
            save_name = f"{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
            save_path = os.path.join(out_dir,save_name)
            comfy_client.download(fn,save_path)
            logger.info(f"生成成功 {save_path}")
            return save_path
    logger.error("未获取输出图片")
    return None

if __name__ == "__main__":
    path = txt2img_generate("古风山水,山间明月薄雾")
    print(path)

运行main.py,只需要一行函数调用完成文生图,所有参数全部由 config.json 控制。


课后练习

练习 1:完成个人标准化 AI 自动化项目工程模板,可复用所有项目

完成完整项目模板要求:

  1. 完整目录结构,所有模块文件齐全;
  2. 文生图、图生图、识图重绘三个对外业务函数全部实现;
  3. 所有硬编码参数全部迁移至config.json;代码内无魔写死的端口、路径、权重;
  4. 工作流模板统一放在workflows文件夹;
  5. 可以直接复制整个项目文件夹用于新业务开发。

练习 2:完善日志记录功能,自动保存每日运行日志

需求:

  1. 日志按日期生成日志文件,例如logs/2026‑08‑29.log
  2. 每次调用记录:调用类型、入参、提示词、输出图片路径、成功失败;异常堆栈写入日志;
  3. 批量任务运行时统计总任务、成功、失败数量写入日志末尾;
  4. 日志同时输出控制台,方便调试。

练习 3:实现参数一键修改,无需改动核心代码

验证工程化效果:

  1. 修改config.json里面:comfyui.lora_strengthollama.temperaturebatch_sleep
  2. 不修改任何 py 源码;运行程序,确认参数生效;
  3. 新增一个配置项,例如:max_batch_count最大批量条数,在批量脚本读取使用。

思考题

  1. 为什么工作流模板加载后运行前,必须做json.loads(json.dumps(tpl))深度拷贝?
  2. 工程化把配置抽离 JSON,带来哪些好处?

为什么 AI 任务优先跑 GPU,不用 CPU

一、一句话核心结论

CPU 擅长复杂逻辑串行思考 ;GPU 擅长成千上万简单数学同时并行计算

深度学习本质就是海量矩阵乘法,非常适合 GPU。

二、拆开对比,给学生讲明白

1)内部硬件结构完全不一样

  • CPU(中央处理器)
    核心很少:普通家用 CPU 只有 6‑16 个物理核心。
    擅长:判断、分支、if‑else、复杂逻辑、操作系统、软件调度。

比喻:一个高级专家,做事精细,但是同时干的活不多。

  • GPU(图形显卡)
    几千个小计算核心(CUDA 流处理器)。RTX3060‑8G 有 3584 个 CUDA 核心
    每一个小核心能力不强,但是可以几千个同时一起干活

比喻:几千个普通工人,同时做一模一样简单重复的运算。

2)AI 神经网络到底在干什么?

不管是 ComfyUI 画图、Ollama 大模型、YOLO 目标检测:

90% 以上时间都在做矩阵乘法,大量浮点数运算。

矩阵运算特点:计算简单、重复、可以并行。

  • CPU:只能一批一批慢慢算,排队执行 → 速度极慢
  • GPU:几千个核心同时开工,一次性批量算完 → 速度几十~上百倍提升

举真实例子给学生直观感受:

同样一张图 FLUX 生成:

  • CPU:几十分钟
  • RTX3060 8G 显卡:几十秒

三、还有第二个关键:显存 VRAM(非常重要,学生最容易忽略)

  • CPU 用的是系统内存 RAM。内存总线速度慢。
  • GPU 自带高速显存 VRAM ,带宽巨大。
    几十亿参数的 AI 模型,需要一次性把全部权重放进高速内存里。

大模型体积很大,如果没有显存,CPU 内存搬运数据会严重堵车。
补充:Ollama 可以 CPU 跑,是因为用了 gguf 量化 + llama‑cpp 高度优化 C++ 代码,做了很多妥协,代价就是速度很慢

四、表格总结(直接放进 PPT)

表格

项目 CPU GPU
核心数量 少(6‑16 核) 几千个小计算核心
擅长任务 复杂逻辑、分支判断 大规模并行浮点计算、矩阵运算
AI 推理速度 很慢 快几十~上百倍
高速存储器 内存 RAM,带宽低 显卡显存 VRAM,带宽极高
适合场景 轻量测试、小模型演示 绘图、视频、YOLO、大模型生产任务

五、三个常见误区(课堂考点)

  1. ❌ 误区:CPU 性能弱,所以跑不动 AI
    ✅ 真相:CPU 理论可以算,但是架构天生不适合大规模并行,效率极低。就算顶级 i9,跑 AI 依然慢。
  2. ❌ 误区:只要显卡越好,AI 一定越快
    ✅ 真相:第一看 CUDA 核心数量,第二看显存大小。显存不够直接爆 OOM,根本加载不起大模型。
    8G 显存就跑不动完整版 Wan2.1 视频模型,不是显卡算力不够,是装不下模型。
  3. ❌ 误区:所有 AI 都必须 GPU
    ✅ 真相:
  • 小量化大模型:Ollama 可以 CPU 勉强运行,适合学习演示;
  • 图像、视频生成任务,计算量爆炸,几乎必须 GPU

六、拓展补充(前沿拓展)

  1. NPU:手机端 AI 专用芯片,专门做 AI 加速,介于 CPU‑GPU 之间;
  2. TPU:谷歌专门为神经网络设计的处理器,云端大规模训练;
  3. AMD 显卡:有显卡但是 CUDA 生态差,PyTorch、ComfyUI 兼容不好,所以 AI 圈大多用 N 卡。

七、课堂记忆口诀

CPU 动脑子,GPU 出力气;

矩阵并行算 AI,显卡速度快千里。

相关推荐
Bode_200214 分钟前
决策设计方法
人工智能·交互·智能工厂
AI砖家18 分钟前
Kimi K3 本地部署全解析:需要什么配置的电脑,到底要花多少钱?
人工智能·ai编程
H03111698521 分钟前
教学设计PPT模板平台选用参考
人工智能
众壹新能源科技22 分钟前
功率预测误差考核怎么降?从气象源到上报口径的排查清单
运维·人工智能·自动化
这张生成的图像能检测吗33 分钟前
(论文速读)SubspaceAD:用 PCA 子空间做 Training-Free Few-Shot 异常检测
人工智能·机器学习·pca·异常检测·少样本学习
QT界面美化性能优化1 小时前
QT+AI:使用AI技术为QT应用程序赋能
c++·人工智能·qt·opencv·qt教程·qt6.3
LadiesAndGentlemen1 小时前
开源地理空间智能项目中的本体思想 4-4:收尾篇——五种做法怎么选,治理之后还剩什么活
人工智能·语言模型·开源·aigc·知识图谱
四方云1 小时前
把电话能力无缝嵌入企业自有CRM
大数据·人工智能
STQY燊桐启元(深圳)电子科技1 小时前
医疗精密电源散热方案:相变陶瓷片保障医疗设备长效稳定低干扰
大数据·网络·人工智能