Skip to content
 
📑标签
🏷后端 🏷AIGC 🏷python 🏷model 🏷comfyui

🗒初墨

🍊Hello,各位好,我是面包!

厌倦了千篇一律的AI流程?教你用Python打造专属ComfyUI插件,让Stable Diffusion听你指挥!

节点文件模板

py

# 导入依赖库
import random
import torch

# 定义一个类,一个节点就是一个类,comfyui会引入这个类作为一个节点
class your_node:

    def __init__(self)
        pass

    # 定义节点索引
    @classmethod
    def INPUT_TYPES(cls):
        inputs = {

            # 必选输入,不输入会报错
            "required": {

                "pipe": ("PIPE_LINE",),

                "整数": ("INT", {
                    "default": 0, 
                    "min": 0 ,
                    "max": 1000,
                    "step": 2,
                    "display":"number"}),

                "浮点数": ("FLOAT", {
                    "default": 0, 
                    "min": 0 ,
                    "max": 1000,
                    "step": 2,
                    "round":0.001,
                    "display":"slider"}),

                "字符串": ("STRING", {
                    "default": "字符串", 
                    "multiline": True}),

                "布尔值": ("BOOLEAN", {
                    "default": True,}),

                "下拉选择框": (["None"] + ["enable", "disable"]),                                
                
            }

            # 可选输入
            "optional":{
                "model":("MODEL",),
                "vae":("VAE",),
                "clip":("CLIP",),
                "latent":("LATENT",),
                "image":("IMAGE",),
                "pos":("CONDITIONING",),
                "neg":("CONDITIONING",),
                "xyPlot":("XYPLOT",),
            }

            # 隐藏输入
            "hidden":{
                "my_unique_id":{"UNIQUE_ID"},
            }

        }
        
        return inputs

    OUTPUT_NODE = True

    RETURN_TYPES = ("INT",)

    RETURN_NAMES = ("1个整数",)

    FUNCTION = "test"

    def run(self,)
        pass
py
from .example1 import A,B,C

MODE_CLASS_MAPPINGS = {

    # ui界面搜索节点名称
    "funtion A":A,
    "funtion A":A,
    "funtion A":A,
    "run_node":run_node,
}

实现一个最简单的运算计算器的功能插件

首先我们在./comfyui/ComfyUI/custom_nodes新建一个comfyui_breadq文件夹

然后在其中创建__init__.pyCalculator.py两个文件。

md
.
 comfyui
 └─ .ComfyUI                       
    └─ custom_nodes                    
    └─ comfyui_breadq                       
       ├─ __init__.py
       └─ Calculator.py

Calculator.py

py
class SimpleCalculator:
    @classmethod
    def INPUT_TYPES(cls):
        inputs = {
            "required": {
                "input1": ("INT", {"default": 0, "label": "Input 1"}),
                "input2": ("INT", {"default": 0, "label": "Input 2"}),
                "operation": (["add", "subtract", "multiply", "divide"], {"default": "add", "label": "Operation"})
            }
        }
        return inputs

    RETURN_TYPES = ("INT",)
    RETURN_NAMES = ("RESULT",)
    FUNCTION = "calculate"
    CATEGORY = "bread/SimpleCalculator"

    def calculate(self, input1, input2, operation):
        if operation == "add":
            result = input1 + input2
        elif operation == "subtract":
            result = input1 - input2
        elif operation == "multiply":
            result = input1 * input2
        elif operation == "divide":
            if input2 != 0:
                result = input1 // input2
            else:
                raise ValueError("Division by zero is not allowed.")
        else:
            raise ValueError(f"Unsupported operation: {operation}")

        return (result,)

参数

  • INPUT_TYPES 定义节点输入
  • RETURN_TYPES 定义节点将生成的输出
  • RETURN_NAMES 输出点标识
  • FUNCTION 在节点执行时调用的函数名称

__init__.py

py
from .Calculator import SimpleCalculator
NODE_CLASS_MAPPINGS = {
    "breadq_Calculator": SimpleCalculator
}
NODE_DISPLAY_NAMES_MAPPINGS = {
    "breadq_Calculator": "My Tutorial - SimpleCalculator"
}
__all__ = ['NODE_CLASS_MAPPINGS', 'NODE_DISPLAY_NAMES_MAPPINGS']

参数

  • NODE_CLASS_MAPPINGS 这是一个字典,用于将节点的唯一标识符key映射到具体的类value
  • NODE_DISPLAY_NAMES_MAPPINGS 定义节点在图形化界面中显示的友好名称,但好像没起作用

效果展示

小纸条

修改完插件记得重启一下comfyui,不然插件不会更新!

面向GPT编程的图片嵌入插件

利用Kimi编写py代码,实现了指定大小、指定数量的图片随机且不重叠地嵌入指定大小的背景图里

代码组

py
import os
import random
import torch
from PIL import Image
import numpy as np

class ImageSelector:
    @classmethod
    def INPUT_TYPES(cls):
        return {
            "required": {
                "folder_path": ("STRING", {"default": "", "multiline": False}),  # 文件夹路径
                "num_images": ("INT", {"default": 1, "min": 1, "max": 100}),  # 抽取的图片数量
                "scale_factor": ("FLOAT", {"default": 1.0, "min": 0.1, "max": 10.0, "step": 0.1}),  # 缩放倍率
                "background_width": ("INT", {"default": 800, "min": 1, "max": 8192}),  # 背景宽度
                "background_height": ("INT", {"default": 600, "min": 1, "max": 8192}),  # 背景高度
                "background_color": ("COLOR", {"default": (255, 255, 255)}),  # 背景颜色
                "seed_value": ("INT", {"default": 42, "min": 0, "max": 0xffffffffffffffff}),  # 随机种子值
            }
        }

    RETURN_TYPES = ("IMAGE",)  # 定义节点的输出类型
    FUNCTION = "choose_image"  # 定义节点执行时调用的函数名称
    CATEGORY = "Image Processing"  # 定义节点分类

    def choose_image(self, folder_path, num_images, scale_factor, background_width, background_height, background_color, seed_value):
        """
        节点的主要逻辑:选择图片、缩放并嵌入背景
        """
        # 设置随机种子(仅影响嵌入位置)
        random.seed(seed_value)  # 设置 Python 的随机种子
        torch.manual_seed(seed_value)  # 设置 PyTorch 的随机种子
        np.random.seed(seed_value)  # 设置 NumPy 的随机种子
        print(f"随机种子设置为: {seed_value}")

        # 获取文件夹中所有图片路径
        image_extensions = ['.jpg', '.jpeg', '.png', '.bmp']
        image_paths = [os.path.join(folder_path, f) for f in os.listdir(folder_path)
                       if os.path.splitext(f)[1].lower() in image_extensions]

        if len(image_paths) < num_images:
            raise ValueError("文件夹中图片数量不足!")

        # 随机选择指定数量的图片
        selected_paths = random.sample(image_paths, num_images)

        # 加载并缩放图片
        scaled_images = []
        for path in selected_paths:
            img = Image.open(path).convert("RGB")  # 确保是 RGB 格式
            width, height = img.size
            new_size = (int(width * scale_factor), int(height * scale_factor))
            scaled_img = img.resize(new_size, Image.LANCZOS)  # 使用 LANCZOS 替代 ANTIALIAS
            scaled_images.append(scaled_img)

        # 创建背景并嵌入图片
        background = Image.new('RGB', (background_width, background_height), background_color)
        placed_positions = []

        for img in scaled_images:
            img_width, img_height = img.size
            max_x = background_width - img_width
            max_y = background_height - img_height

            while True:
                x = random.randint(0, max_x)
                y = random.randint(0, max_y)
                overlap = any(self.check_overlap(x, y, img_width, img_height, pos) for pos in placed_positions)
                if not overlap:
                    break

            background.paste(img, (x, y))
            placed_positions.append((x, y, img_width, img_height))

        # 将 PIL 图像转换为 torch.Tensor
        background_tensor = self.pil_to_tensor(background)

        return (background_tensor,)

    @staticmethod
    def check_overlap(x1, y1, w1, h1, pos):
        """
        检查两个矩形是否重叠
        """
        x2, y2, w2, h2 = pos
        return not (x1 + w1 <= x2 or x2 + w2 <= x1 or y1 + h1 <= y2 or y2 + h2 <= y1)

    @staticmethod
    def pil_to_tensor(image):
        """
        将 PIL 图像转换为 torch.Tensor
        """
        # 将 PIL 图像转换为 NumPy 数组
        img_array = np.array(image).astype(np.float32) / 255.0
        # 转换为 torch.Tensor 并调整维度顺序为 [B, H, W, C]
        img_tensor = torch.from_numpy(img_array).unsqueeze(0)  # 添加批处理维度
        return img_tensor
sh
from .bread import ImageSelector

NODE_CLASS_MAPPINGS = {
    "Image Selector": ImageSelector,
}

__all__ = ['NODE_CLASS_MAPPINGS']
md
.
 comfyui
 └─ .ComfyUI                       
    └─ custom_nodes                    
    └─ comfyui_bread                       
       ├─ __init__.py
       └─ bread.py

工作流

图片展示

问题

  1. 图像的数据类型是 torch.Tensor,其形状为 [B,H,W,C],其中 B 是批处理大小,C 是通道数
  2. 随机数只能在0~2^32之间,ComfyUI自带的随机数节点使用随机模式时超过了这个范围,因此不能使用随机模式

解读进阶插件的源码

以ComfyUI-BiRefNet插件为例

utils.py
py
import os

def check_download_model(model_path, repo_id="ViperYX/BiRefNet"):
    """
    检查指定路径下是否存在模型文件,如果不存在,则从 Hugging Face Hub 下载模型。
    
    参数:
        model_path (str): 目标模型文件的完整路径(包括文件名)。
        repo_id (str): Hugging Face Hub 上的仓库 ID,默认为 "ViperYX/BiRefNet"。
    
    返回:
        bool: 如果下载了模型文件,则返回 True;如果文件已存在,则返回 False。
    """
    
    # 检查目标模型文件是否存在
    if not os.path.exists(model_path):
        # 获取模型文件所在的目录路径
        folder_path = os.path.dirname(model_path)
        
        # 如果目录不存在,则创建它(包括所有父目录)
        if not os.path.exists(folder_path):
            os.makedirs(folder_path)
        
        # 提取目标文件名
        file_name = os.path.basename(model_path)
        
        # 打印下载信息,告知用户正在下载模型
        print(f"Downloading BiRefNet model to: {model_path}")
        
        # 导入 Hugging Face Hub 的 snapshot_download 函数
        from huggingface_hub import snapshot_download
        
        # 从 Hugging Face Hub 下载模型文件
        # 参数说明:
        # - repo_id: 指定仓库 ID
        # - allow_patterns: 指定下载匹配文件名的文件
        # - local_dir: 指定下载到的本地目录
        # - local_dir_use_symlinks: 设置为 False,直接下载文件,不使用符号链接
        snapshot_download(repo_id=repo_id,
                          allow_patterns=[f"*{file_name}*"],
                          local_dir=folder_path,
                          local_dir_use_symlinks=False)
        
        # 如果成功下载,返回 True
        return True
    
    # 如果文件已存在,返回 False
    return False

INFO

Os库提供通用的、基本的操作系统交互功能

  • Os库是python标准库,包含几百个函数
  • 常用路径操作、进程管理、环境参数等几类
  • 路径操作:os.path子库,处理文件路径及信息
  • 进程管理:启动系统中其他程序
  • 环境参数:获得系统软件硬件信息等环境参数

它是怎么找到huggingface指定下载链接的?

在代码中,模型文件的下载链接并不是直接指定的,而是通过 Hugging Face Hub 的 API 动态获取的。 具体来说,代码使用了 huggingface_hub 库中的 snapshot_download 函数来实现下载功能。以下是代码中下载链接的查找和下载过程的详细解释:

  1. Hugging Face Hub 的工作原理

Hugging Face Hub 是一个托管和共享机器学习模型的平台。每个模型或资源都被存储在一个仓库(repository)中,仓库的唯一标识是 repo_id。例如,repo_id="ViperYX/BiRefNet" 表示用户 ViperYX 下的 BiRefNet 仓库。

仓库结构:Hugging Face Hub 上的仓库可以包含多个文件,例如模型权重文件、配置文件、文档等。

API 访问:通过 Hugging Face 提供的 API 和工具(如 huggingface_hub 库),可以访问这些仓库中的文件。

  1. 代码中的下载逻辑

在代码中,snapshot_download 函数负责从 Hugging Face Hub 下载文件。以下是关键步骤:

  • 指定仓库和文件模式
py
snapshot_download(repo_id=repo_id,
                  allow_patterns=[f"*{file_name}*"],
                  local_dir=folder_path,
                  local_dir_use_symlinks=False)

repo_id:指定 Hugging Face Hub 上的仓库 ID。例如,ViperYX/BiRefNet 表示从用户 ViperYX 的 BiRefNet 仓库中下载。

allow_patterns:指定要下载的文件模式。代码中使用了 f"{file_name}",这是一个通配符模式,表示下载仓库中与目标文件名匹配的所有文件。

例如,如果目标文件名是 model.pth,则会匹配仓库中所有包含 model.pth` 的文件。

  • 动态查找下载链接

snapshot_download 函数的工作原理如下: 访问 Hugging Face Hub 的 API: 通过 repo_id,函数会访问 Hugging Face Hub 的 API,获取指定仓库的文件列表。 API 会返回仓库中所有文件的元数据,包括文件名、大小、存储路径等。 匹配文件: 使用 allow_patterns 参数,函数会从仓库的文件列表中筛选出符合条件的文件。例如,如果 allow_patterns=["model.pth"],则会筛选出仓库中所有文件名包含 model.pth 的文件。 获取下载链接: Hugging Face Hub 的 API 会为每个匹配的文件提供一个下载链接。 这些链接通常是 Hugging Face 的 CDN(内容分发网络)地址,用于高效地下载文件。 下载文件: snapshot_download 函数会使用这些下载链接,将文件下载到本地的 local_dir 目录中。 如果 local_dir_use_symlinks=False,则会直接将文件存储到本地路径,而不是创建符号链接。 3. 总结:如何找到下载链接 代码并没有直接指定下载链接,而是通过 Hugging Face Hub 的 API 动态获取。 snapshot_download 函数通过以下步骤找到下载链接: 使用 repo_id 访问 Hugging Face Hub 的仓库。 通过 API 获取仓库中所有文件的元数据。 使用 allow_patterns 筛选出目标文件。 从 API 返回的元数据中提取文件的下载链接。 使用这些链接下载文件到本地。

小纸条

  • 向量数据:2D 张量,形状为 (samples, features)。

  • 时间序列数据或序列数据:3D 张量,形状为 (samples, timesteps, features)。

  • 图像:4D 张量,形状为 (samples, height, width, channels) 或 (samples, channels,height, width)。

  • 视频:5D 张量,形状为 (samples, frames, height, width, channels) 或 (samples, frames, channels, height, width)。

requirements.txt文件格式:

requests==1.2.0

Flask==0.10.1requests==1.2.0

Flask==0.10.1

文渊阁
Playlist
Total 4
  • 星茶会
    灰澈
  • song1
    author1
  • soewrewfg1
    author1
  • PIKASONIC - Blossom
    author1