
ComfyUI 커스텀 노드를 직접 제작하여 워크플로우 확장하기
요약
ComfyUI의 워크플로우 확장을 위해 커스텀 노드를 직접 제작하는 방법을 설명합니다. 이미지에 워터마크를 추가하는 실습을 통해 노드의 입력/출력 정의, 실행 로직 작성, 텐서 변환 과정을 다룹니다.
핵심 포인트
- ComfyUI 커스텀 노드 제작을 위한 기본 구조와 규칙 학습
- Python을 이용한 이미지 처리 및 워터마크 추가 로직 구현
- ComfyUI 텐서(float32)와 Pillow 이미지(uint8) 간의 변환 방법
- INPUT_TYPES 및 RETURN_TYPES 정의를 통한 UI 자동 생성 원리
ComfyUI, 사용하고 계신가요? 노드 기반의 이미지 생성 도구로, Stable Diffusion의 워크플로우를 비주얼하게 구성할 수 있는 도구입니다. AUTOMATIC1111의 WebUI와 달리, 처리 흐름이 그래프로 보이는 점이 아주 매력적이죠.
그러다 보면 한동안 사용하다 보면 "이 처리는 표준 노드로는 할 수 없겠는데"라는 상황이 생기곤 합니다. 예를 들어 생성 후에 자동으로 워터마크(Watermark)를 넣고 싶다거나, 특정 사이즈로 리사이즈(Resize)한 뒤 저장하고 싶을 때 같은 경우입니다.
그럴 때는 커스텀 노드(Custom Node)를 직접 만드는 것이 가장 빠릅니다. 그래서 이번에는 실제로 작동하는 커스텀 노드를 하나 만들면서 그 구조를 해설해 보겠습니다.
이번에는 "생성된 이미지에 자동으로 워터마크(반투명 텍스트)를 추가하는 노드"를 만들 것입니다. 실용성도 어느 정도 있고, 무엇보다 커스텀 노드의 기본이 전부 담겨 있기 때문입니다.
ComfyUI는 기동 시 custom_nodes/ 디렉토리를 탐색하여 Python 파일을 읽어 들입니다. 각 파일 안에서 특정 규칙에 따라 클래스를 정의해 두면, 그것이 자동으로 노드로 인식되는 구조입니다.
필요한 것은 단 3가지입니다:
- 입력과 출력의 타입을 정의하기
- 메인 처리를
execute와 같은 함수에 작성하기 - 노드의 표시 이름과 카테고리를 등록하기
이것뿐입니다. 프레임워크 측에서 번거로운 UI 부분을 전부 처리해 주기 때문에, 우리는 처리 로직에만 집중할 수 있습니다.
이번 코드는 다음 환경에서 동작을 확인했습니다:
- ComfyUI (최신 포터블 버전)
- Python 3.10+
- Pillow (아마 처음부터 설치되어 있을 것입니다)
ComfyUI Manager를 통해 적당히 커스텀 노드를 넣어보며 놀아본 적이 있는 환경이라면, 그대로 사용해도 괜찮습니다.
ComfyUI/
└── custom_nodes/
└── comfyui-watermark-node/
...
처음에는 이 2개의 파일만 있으면 됩니다. __init__.py에서 노드를 로드하고, watermark_node.py에 처리를 작성합니다.
먼저 watermark_node.py를 만듭니다.
import torch
import numpy as np
from PIL import Image, ImageDraw, ImageFont
...
INPUT_TYPES에서 노드에 표시할 파라미터를 정의합니다. "IMAGE" 타입은 ComfyUI의 내장 타입으로, 노드 간에 이미지를 주고받을 때 사용합니다. 문자열, 소수, 정수, 선택식 드롭다운 등 대부분의 UI 부품이 이 정의만으로 자동 생성됩니다. 편리하죠.
RETURN_TYPES는 출력 타입입니다. 이번에는 이미지를 1개 반환할 뿐입니다. FUNCTION에는 실행 시 호출될 메서드 이름을 지정합니다. CATEGORY는 노드 목록의 우클릭 메뉴에서 표시되는 분류명입니다.
여기가 약간 주의해야 할 포인트입니다. ComfyUI의 이미지 텐서(Tensor)는 (배치, 높이, 너비, 채널)의 shape를 가지며, 값은 0.01.0의 float32입니다. 반면 Pillow는 0255의 uint8을 기대합니다. 따라서 변환이 필요합니다.
def tensor_to_pil(self, img_tensor):
"""ComfyUI의 텐서 → Pillow Image (배치의 첫 번째 장)"""
# shape: (B, H, W, C) → 첫 번째 장만 추출
...
unsqueeze(0)로 배치 차원을 추가하는 것을 잊기 쉽습니다. 이를 수행하지 않으면 다음 단계의 노드에서 shape가 맞지 않아 에러가 발생합니다.
텍스트 그리기에는 Pillow의 ImageDraw를 사용합니다.
def add_watermark(self, image, text, opacity, font_size, position):
# 텐서 → PIL
pil_img = self.tensor_to_pil(image)
...
폰트 경로는 환경에 따라 다르기 때문에, 일단 주요 OS를 커버하는 폴백(Fallback) 로직을 작성해 두었습니다. 제대로 하려면 폰트 파일을 노드와 같은 디렉토리에 동봉하는 것이 확실합니다.
마지막으로 __init__.py에서 노드를 익스포트(Export)합니다.
# custom_nodes/comfyui-watermark-node/__init__.py
from .watermark_node import WatermarkNode
NODE_CLASS_MAPPINGS = {
...
NODE_CLASS_MAPPINGS가 가장 중요합니다. 이 딕셔너리에 클래스를 등록하지 않으면 ComfyUI가 인식하지 못합니다. NODE_DISPLAY_NAME_MAPPINGS는 UI에 표시되는 이름이며, 이 부분은 생략해도 동작합니다.
custom_nodes/comfyui-watermark-node/에 위 2개 파일을 배치 - ComfyUI 재시작
- 노드 목록을 우클릭 →
image/watermark→Add Watermark - 적절한 이미지 생성 워크플로우의 Save Image 앞에 연결
파라미터를 조절하면 다음과 같이 동작합니다:
- text: 워터마크 문자열 (일본어도 가능, 폰트가 있는 경우)
- opacity: 투명도. 0.2~0.3 정도가 적당함
- font_size: 이미지 크기에 따라 조정
- position: 우측 하단이 기본값, 필요에 따라 변경
"노드가 목록에 나타나지 않아요"
→ __init__.py의 NODE_CLASS_MAPPINGS를 확인하세요. 오타(typo)가 많은 부분입니다.
"이미지가 검게 나와요"
→ 텐서(Tensor)의 값 범위(range)가 잘못되었습니다. 0.0-1.0 사이의 float32인지 확인하세요.
"일본어 폰트가 깨져요 (두부 현상)"
→ _get_font()에서 사용하는 폰트가 일본어를 지원하지 않습니다. 경로를 수정하거나 폰트 파일을 동봉하세요.
자주 사용하는 것만 발췌:
| 타입 | 설명 | 정의 예시 |
|---|---|---|
IMAGE | 이미지 텐서 (Image Tensor) | "image": ("IMAGE",) |
LATENT | 잠재 공간 텐서 (Latent Tensor) | "latent": ("LATENT",) |
STRING | 텍스트 입력 | "prompt": ("STRING", {"multiline": True}) |
FLOAT | 실수 (Float) | "strength": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0}) |
INT | 정수 (Integer) | "steps": ("INT", {"default": 20, "min": 1, "max": 100}) |
BOOLEAN | 토글 스위치 | 사실 표준으로 지원되지 않으며, INT로 0/1을 대신 사용하는 것이 통례입니다 |
"required"가 아니라 "optional"로 설정하면 입력 핀(input pin)을 생략할 수 있습니다.
@classmethod
def INPUT_TYPES(cls):
return {
...
}
튜플(Tuple)로 반환하면 됩니다.
RETURN_TYPES = ("IMAGE", "INT",)
RETURN_NAMES = ("watermarked_image", "text_length",)
def add_watermark(self, image, text, opacity, font_size, position):
...
print()는 ComfyUI를 실행한 터미널에 출력됩니다. 사소해 보이지만 이것만으로도 충분한 경우가 많습니다. 본격적으로 작업하려면 logging 모듈을 사용하는 것도 좋은 방법입니다.
ComfyUI 커스텀 노드 제작, 생각보다 간단하지 않았나요? 핵심 포인트는 다음과 같습니다:
INPUT_TYPES로 UI를 정의하고,FUNCTION으로 처리를 지정할 것- 텐서(Tensor)와 PIL 변환 시 타입과 값 범위에 주의할 것
NODE_CLASS_MAPPINGS에 등록하는 것을 잊지 말 것
이번 워터마크 노드는 단순한 예시이지만, 여기서 발전시키면 "생성 후 자동 리사이즈", "메타데이터 삽입", "여러 장을 그리드에 배열"과 같은 실용적인 노드도 동일한 패턴으로 만들 수 있습니다.
워크플로우에 한 끗 차이를 더하고 싶을 때, 꼭 시도해 보세요.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기