搭建一个ComfyUI调用API的可视化Demo(基于gradio界面)

目录

         环境搭建:

 源码:

一、generate_image方法代码修改

第 1 步:下载 json 文件导入 ComfyUI 修改并保存 API 格式

第 2 步:修改URL,workflow等参数

第 3 步:修改 generate_image 方法、创建对应变量

二、gradio界面修改

1、block 布局

2、绑定变量

3、增加 gradio 示例 gr.Examples

4、修改 gradio 访问权限

三、报错解决

错误 1:Gradio 读取图片一直读取不到

错误 2:Gradio 生成图像显示问题

错误 3:端口号占用 ​


效果图:

环境搭建:

新建终端,启动虚拟环境 ,安装 grdio 依赖项

pip install requests Pillow gradio numpy #安装grdio依赖项

 源码:

  1. import json
  2. import os
  3. import time
  4. import random
  5. import gradio as gr
  6. import requests
  7. from PIL import Image
  8. cached_seed = 0
  9. PORT_gradio = 4586
  10. img_count = 0
  11. URL = "/prompt"
  12. OUTPUT_DIR = "/ComfyUI/output"
  13. INPUT_DIR = "/ComfyUI/input_api"
  14. workflow = "/comfy-gradio-api/json/AIC.json"
  15. LOADED_MODELS_DIR = "/ComfyUI/models/loras"
  16. def get_available_lora_models():
  17. lora_models = ["None"]
  18. for root, dirs, files in os.walk(LOADED_MODELS_DIR):
  19. for file in files:
  20. if file.endswith('.safetensors'):
  21. relative_path = os.path.relpath(os.path.join(root, file), LOADED_MODELS_DIR)
  22. lora_models.append(relative_path.replace(os.sep, '/'))
  23. return lora_models
  24. def start_queue(prompt_workflow):
  25. p = {"prompt": prompt_workflow}
  26. data = json.dumps(p).encode('utf-8')
  27. requests.post(URL, data=data)
  28. def update_seed(prompt,seed_id,prefix):
  29. global cached_seed
  30. if cached_seed == prompt[f"{seed_id}"]["inputs"]["seed"]:
  31. return get_latest_image_by_prefix(OUTPUT_DIR, f"{prefix}")
  32. cached_seed = prompt[f"{seed_id}"]["inputs"]["seed"]
  33. def preprocess_image(input_image):
  34. # """
  35. # 对输入的图像进行预处理,这里主要是将图像等比例缩放,使其最小边为512像素,
  36. # 返回预处理后的图像对象。
  37. # """
  38. image = Image.fromarray(input_image)
  39. min_side = min(image.size)
  40. scale_factor = 512 / min_side
  41. new_size = (round(image.size[0] * scale_factor), round(image.size[1] * scale_factor))
  42. return image
  43. def save_processed_image(processed_image):
  44. # """
  45. # 保存预处理后的图像到指定目录(INPUT_DIR),处理文件名冲突问题(通过序号递增保证唯一性),
  46. # 返回保存后的图像路径。
  47. # """
  48. global img_count
  49. save_name = f'test_api_{str(img_count).zfill(4)}.jpg'
  50. save_path = os.path.join(INPUT_DIR, save_name)
  51. while os.path.exists(save_path):
  52. img_count += 1
  53. save_name = f'test_api_{str(img_count).zfill(4)}.jpg'
  54. save_path = os.path.join(INPUT_DIR, save_name)
  55. processed_image.save(save_path)
  56. return save_path
  57. def get_latest_image_by_prefix(folder, prefix):
  58. files = os.listdir(folder)
  59. image_files = [f for f in files if f.lower().endswith(('.png', '.jpg', '.jpeg')) and f.startswith(prefix)]
  60. image_files.sort(key=lambda x: os.path.getmtime(os.path.join(folder, x)))
  61. latest_image = os.path.join(folder, image_files[-1]) if image_files else None
  62. return latest_image
  63. # 提取文件名中的数字部分
  64. def get_suffix(image_path):
  65. return image_path.split('_')[-2]
  66. def wait_for_new_images(*prefixes):
  67. if not prefixes:
  68. raise ValueError("At least one prefix must be provided.")
  69. previous_images = {prefix: get_latest_image_by_prefix(OUTPUT_DIR, prefix) for prefix in prefixes}
  70. previous_counts = {prefix: get_suffix(previous_images[prefix]) for prefix in prefixes}
  71. print("Waiting for new images to be generated...")
  72. while True:
  73. latest_images = {prefix: get_latest_image_by_prefix(OUTPUT_DIR, prefix) for prefix in prefixes}
  74. latest_counts = {prefix: get_suffix(latest_images[prefix]) for prefix in prefixes}
  75. for prefix in prefixes:
  76. print(f"{prefix}_count: {latest_counts[prefix]}")
  77. if latest_counts[prefixes[0]] != previous_counts[prefixes[0]] and all(latest_counts[prefix] == latest_counts[prefixes[0]] for prefix in prefixes):
  78. print("New images detected!")
  79. print("Previous images:", previous_images)
  80. print("Latest images:", latest_images)
  81. return latest_images
  82. time.sleep(1)
  83. def get_example():
  84. return [
  85. ["/comfy-gradio-api/example_.jpg", "WaterColor_style, (best quality:2),F1SC_style", 1, "flux/xuxl/动漫风格_动漫风格推文_v1.5.safetensors"]
  86. ]
  87. def generate_image(input_image,prompt_text,strength_model,lora_name):
  88. with open(workflow, "r") as file_json:
  89. prompt = json.load(file_json)
  90. prompt["83"]["inputs"]["text"] = prompt_text
  91. prompt["76"]["inputs"]["strength_model"] = strength_model
  92. prompt["76"]["inputs"]["lora_name"] = lora_name
  93. prompt["55"]["inputs"]["seed"] = random.randint(1, 1000000000000000)
  94. processed_image = preprocess_image(input_image)
  95. print("save_path = save_processed_image(processed_image)")
  96. save_path = save_processed_image(processed_image)
  97. # 将保存后的图像路径更新到工作流配置中相应位置(假设'42'节点需要该图像路径,根据实际调整)
  98. prompt["88"]["inputs"]["image"] = save_path
  99. update_seed(prompt,55,"flux_a")
  100. # 启动工作流任务队列
  101. print("start_queue(prompt)")
  102. start_queue(prompt)
  103. output_images=wait_for_new_images("flux_a")
  104. return output_images["flux_a"]

一、generate_image方法代码修改

第 1 步:下载 json 文件导入 ComfyUI 修改并保存 API 格式

打开ComfyUI,导入 json,检查是否是 save Image 节点,并修改保存输出图像的前缀名 gradio/Flux_lora/xxx

注意:xxx 需要区别与其他重命名文件,例如文件中有 flux_a 前缀命名的图片,xxx 不能只是 flux,要取名为区别与 flux_a 的 flux_b,否则读取的图片会乱。

第 2 步:将 API 格式的 json 保存到服务器,修改URL,workflow等参数

URL:ComfyUI启动地址

INPUT_DIR:ComFyUI 上传图片存放地址

OUTPUT_DIR:图像输出地址

workflow:工作流json保存地址

LOADED_MODELS_DIR:模型保存地址

  1. URL = "/prompt"
  2. OUTPUT_DIR = "/ComfyUI/output"
  3. INPUT_DIR = "/ComfyUI/input_api"
  4. workflow = "/comfy-gradio-api/json/AIC_.json"
  5. LOADED_MODELS_DIR = "/ComfyUI/models/loras"

第 3 步:修改 generate_image 方法、创建对应变量

1、加入开放的变量参数,对应修改即可,记得加入形参

注意:如果是文生图,删掉 input_image 和下列 4 行图像处理保存代码即可。

图生图则保留,并修改输入图像对应的 API 号,多图输入时再加 4 行即可。

  1. processed_image = preprocess_image(input_image)
  2. # 保存预处理后的图像,并处理文件名冲突问题,获取保存路径
  3. print("save_path = save_processed_image(processed_image)")
  4. save_path = save_processed_image(processed_image)
  5. # 将保存后的图像路径更新到工作流配置中相应位置(假设'42'节点需要该图像路径,根据实际调整)
  6. prompt["12"]["inputs"]["image"] = save_path

2、seed 种子刷新

大部分情况是都要加的,没有就会一直输出同一张图,找到工作流 json 中对应的 seed 的号码,这里是 55,并修改,输出文件的前缀,flux_b。有的工作流 json 里面可能没有 seed 选项,删掉这行代码即可

3、修改生1图还是生多图

(1)、生成一张图片时,修改一个前缀

 修改代码为:

  1. output_images=wait_for_new_images("flux_a")
  2. return output_images["flux_a"]


(2)、生成多张图片时,修改返回值加入多个前缀,注意要返回 list

 修改代码为: 

  1. output_images = wait_for_new_images("seg1_fg", "seg1_mask", "seg2_fg","seg2_mask")
  2. output_images_list = [output_images[prefix] for prefix in ["seg1_fg", "seg1_mask", "seg2_fg", "seg2_mask"]]
  3. return tuple(output_images_list)

二、gradio界面修改

  1. def create_gradio_interface():
  2. with gr.Blocks() as demo:
  3. with gr.Row():
  4. # 图像输入组件和输出图像组件保持平行
  5. with gr.Column(scale=1):
  6. image_input = gr.Image(label="输入图像", type="numpy")
  7. with gr.Column(scale=1):
  8. output_image = gr.Image(label="输出图像")
  9. with gr.Row():
  10. prompt_input = gr.Textbox(label="正向提示词")
  11. # 创建一行用于其他输入组件
  12. with gr.Row():
  13. strength_model = gr.Slider(minimum=0, maximum=1, step=0.01, value=1, label="Lora模型权重")
  14. lora_name=gr.Dropdown(choices=get_available_lora_models(), label="Lora 模型", info="选择 Lora 模型名")
  15. # 提交按钮
  16. submit_button = gr.Button("生成图像")
  17. gr.Examples(
  18. examples=get_example(),
  19. fn=generate_image,
  20. inputs=[image_input, prompt_input, strength_model,lora_name],
  21. outputs=output_image,
  22. cache_examples=True,
  23. )
  24. # 按钮点击事件绑定
  25. submit_button.click(
  26. generate_image,
  27. inputs=[image_input, prompt_input, strength_model,lora_name],
  28. outputs=output_image
  29. )
  30. demo.launch(
  31. server_name="0.0.0.0",
  32. server_port=PORT_gradio,
  33. allowed_paths=["/ComfyUI/output", "/tmp"]
  34. )
  35. if __name__ == "__main__":
  36. create_gradio_interface()

1、block 布局,加入或删除需要开放的参数,开始布局。

(1)、gr.Row() 组件水平排列

使用 Row 函数会将组件按照水平排列,但是在 Row 函数块里面的组件都会保持同等高度

(2)、gr.Colum() 组件垂直排列

组件通常是垂直排列,我们可以通过 Row 函数和 Column 函数生成不同复杂的布局。

(3)、gr.Image 图像框

(4)、gr.Textbox 用户输入框

(5)、gr.Slider 值的输入,滑条。value(默认值),

(6)、gr.Dropdown 用户选择框,choices=get_available_lora_models(),提取可选择的模型

2、绑定变量

注意:按钮点击事件绑定变量尽量要与 generate_image 方法形参顺序相同,不然有可能输出不了图像

3、增加 gradio 示例 gr.Examples

添加方法 get_example(),返回的值的顺序要和绑定变量的顺序一样

  1. def get_example():
  2. return [
  3. ["/comfy-gradio-api/example_.jpg", "WaterColor_style, (best quality:2),F1SC_style", 1, "flux/xuxl/动漫风格_动漫风格推文_v1.5.safetensors"]
  4. ]
  5. #修改返回值,顺序要和绑定变量的顺序一样
  6. gr.Examples(
  7. examples=get_example(),
  8. fn=generate_image,
  9. inputs=[image_input, prompt_input, strength_model,lora_name],
  10. outputs=output_image,
  11. cache_examples=True,
  12. )

效果:

4、修改 gradio 访问权限

  1. demo.launch(
  2. server_name="0.0.0.0",
  3. server_port=PORT_gradio,
  4. allowed_paths=["/ComfyUI/output", "/tmp"]
  5. )

到这里,恭喜你,就已经初步完成了简单的测试 demo 搭建,如果对你有帮助,期待你的点赞!!!

三、报错解决

错误 1:Gradio 读取图片一直读取不到

情况 1:输出图片的后缀 num 未读取正确,如下图:

原因:ComfyUI 生成图片格式后缀方式偶然会不一样,有时候 00001_.jpg 有时候 00001.jpg

解决方法:修改提取后缀方法,这个改为-1

错误 2:Gradio 生成图像显示问题

情况 1:Gradio 无法将生成的图像文件从移动到 Gradio 的缓存目录

解决方法:修改launch() 方法的allowed_paths 参数: 你可以通过向launch() 方法添加allowed_paths 参数,明确允许 Gradio 访问你的输出目录/home/Intern/liuld/ComfyUI/output在你的gr.Interfacelaunch() 方法中添加allowed_paths 参数,修改为如下:

  1. demo.launch(
  2. server_name="0.0.0.0",
  3. server_port=PORT_gradio,
  4. allowed_paths=["/ComfyUI/output", "/tmp"]
  5. )

这样,Gradio 将允许访问/ComfyUI/output 目录中的文件。

情况 2:ComUI 的组件后端图像不是保存图像组件,是预览图像组件,gradio 不能找到文件

解决方法:更换组件为保存图像,并保存 json 的 API 格式,打开复制后端的信息将其更换即可

错误 3:端口号占用 

解决方法 1:查询占用端口的进程号:lsof -i :1563

删除后台进程 kill -9 3467673(PID)

解决方法 2执行 kill.py

修改 gradio 监听端口,运行 kill.py

 kill.py代码如下:

  1. import os
  2. import signal
  3. import subprocess
  4. port_pid_gradio = 7315
  5. def find_process_by_port(port):
  6. try:
  7. # 使用 lsof 命令查找占用端口的进程
  8. command = f"lsof -i :{port}"
  9. result = subprocess.check_output(command, shell=True, stderr=subprocess.PIPE).decode("utf-8")
  10. # 检查输出是否为空,空表示没有进程占用端口
  11. if not result:
  12. print(f"No process found using port {port}.")
  13. return None
  14. # 获取进程信息,获取进程ID(PID)
  15. for line in result.splitlines():
  16. if 'LISTEN' in line: # 只关注处于监听状态的进程
  17. parts = line.split()
  18. pid = int(parts[1]) # 获取 PID
  19. print(f"Process found with PID: {pid}")
  20. return pid
  21. except subprocess.CalledProcessError:
  22. print(f"No process found using port {port}.")
  23. return None
  24. def kill_process(pid):
  25. try:
  26. os.kill(pid, signal.SIGKILL) # 发送 SIGKILL 信号终止进程
  27. print(f"Process {pid} has been killed successfully.")
  28. except ProcessLookupError:
  29. print(f"Process {pid} not found.")
  30. except PermissionError:
  31. print(f"No permission to kill process {pid}.")
  32. def main():
  33. pid = find_process_by_port(port_pid_gradio)
  34. if pid:
  35. kill_process(pid)
  36. if __name__ == "__main__":
  37. main()

欢迎 点赞👍 | 收藏⭐ | 评论✍ | 关注🤗