Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa813002f2 | ||
|
|
e5850cc5c3 | ||
|
|
5ec405e393 | ||
|
|
3725d4bf63 | ||
|
|
d7cabb2921 | ||
|
|
067ba6de36 | ||
|
|
3f66ebcba6 |
@@ -2,7 +2,7 @@
|
||||
|
||||
<img src="https://github.com/Gourieff/Assets/raw/main/sd-webui-reactor/ReActor_logo_NEW_EN.png?raw=true" alt="logo" width="180px"/>
|
||||
|
||||

|
||||

|
||||
|
||||
<a href="https://boosty.to/artgourieff" target="_blank">
|
||||
<img src="https://lovemet.ru/img/boosty.jpg" width="108" alt="Support Me on Boosty"/>
|
||||
@@ -46,12 +46,22 @@
|
||||
|
||||
## What's new in the latest update
|
||||
|
||||
### 0.7.2 <sub><sup>BETA1</sup></sub>
|
||||
|
||||
- **New `Cinematic Enhancer` node**. Forget about plastic skin and boring pictures. This Node can help make any photorealistic image more soulful. You can add different effects (simple or advanced grain, vignette, blur, color correction, HDR) and apply LUT.<br><u>Requirements:</u>
|
||||
- Put LUT-files in `models/luts`, you can find some collection [here](https://huggingface.co/datasets/Gourieff/ReActor/tree/main/luts)<br>
|
||||
- `pip install colour-science` in your Comfy Python -OR- just run install.bat (Win users) in the ReActor's folder
|
||||
|
||||
*Inspired by such nodes as: Image Apply LUT ([essentials](https://github.com/cubiq/ComfyUI_essentials)), LayerFilter: Film V2 ([LayerStyle](https://github.com/chflame163/ComfyUI_LayerStyle)), Color Correction ([post-processing-nodes](https://github.com/EllangoK/ComfyUI-post-processing-nodes)), HDR Effects ([SuperBeasts](https://github.com/SuperBeastsAI/ComfyUI-SuperBeasts))*
|
||||
|
||||
- Nodes divided into subcategories.
|
||||
|
||||
### 0.7.1
|
||||
|
||||
- **New `DLSS5 Frame Enhancer` node**. NVIDIA's latest DLSS 5 technology for enhancing image quality (both overall frame or faces by mask). <br><u>See [Installation Instructions](https://github.com/Gourieff/ComfyUI-ReActor/blob/main/r_dlssnr/dll/README.md).</u><br><pre>The integration operates in an isolated Host mode, fully eliminating conflicts with ComfyUI's CUDA context.<br>Huge thanks to the author of the [Merserk/dlss5-visual-enhancer](https://github.com/Merserk/dlss5-visual-enhancer) project for the C++ wrapper (neuroframe_engine.dll and neuroframe_caller.dll), which formed the base of the computational bridge for this node.<br>Also thanks Gemini 3.1 Pro (via [Google Gemini](https://gemini.google.com/app)) for the contribution.</pre><u>Requirements:</u><br>
|
||||
-- Windows 10/11<br>
|
||||
-- NVIDIA display driver >= 616.x<br>
|
||||
-- NVIDIA RTX 40/50-series GPU<br>
|
||||
- **New `DLSS5 Frame Enhancer` node**. NVIDIA's latest DLSS 5 technology for enhancing image quality (both overall frame or faces by mask). <br><u>See [Installation Instructions](https://github.com/Gourieff/ComfyUI-ReActor/blob/main/r_dlssnr/dll/README.md).</u><br><pre>The integration operates in an isolated Host mode, fully eliminating conflicts with ComfyUI's CUDA context.<br>Huge thanks to the author of the [Merserk/dlss5-visual-enhancer](https://github.com/Merserk/dlss5-visual-enhancer) project for the C++ wrapper (neuroframe_engine.dll and neuroframe_caller.dll), which formed the base of the computational bridge for this node.<br>Also thanks Gemini 3.1 Pro (via [Google Gemini](https://gemini.google.com/app)) for the contribution.</pre><u>Requirements:</u>
|
||||
- Windows 10/11
|
||||
- NVIDIA display driver >= 616.x
|
||||
- NVIDIA RTX 40/50-series GPU<br>
|
||||
(compatibility with older RTX series is unconfirmed)
|
||||
- Other fixes and improvements
|
||||
|
||||
@@ -305,6 +315,7 @@ List of Nodes:
|
||||
- ImageRGBA2RGB (Convert RGBA to RGB)
|
||||
- ReActorUnload (Unload ReActor models from VRAM)
|
||||
- DLSS5FrameEnhancer (Enhance frame quality with NVIDIA DLSS 5)
|
||||
- ReActorCinematicEnhancer (Add cinematic effects to any image)
|
||||
|
||||
Connect all required slots and run the query.
|
||||
|
||||
|
||||
+16
-5
@@ -2,7 +2,7 @@
|
||||
|
||||
<img src="https://github.com/Gourieff/Assets/raw/main/sd-webui-reactor/ReActor_logo_NEW_RU.png?raw=true" alt="logo" width="180px"/>
|
||||
|
||||

|
||||

|
||||
|
||||
<a href="https://boosty.to/artgourieff" target="_blank">
|
||||
<img src="https://lovemet.ru/img/boosty.jpg" width="108" alt="Поддержать проект на Boosty"/>
|
||||
@@ -46,12 +46,22 @@
|
||||
|
||||
## Что нового в последнем обновлении
|
||||
|
||||
### 0.7.2 <sub><sup>BETA1</sup></sub>
|
||||
|
||||
- **Новый узел `Cinematic Enhancer`**. Забудьте о пластиковой коже и скучной картинке. Этот узел поможет вдохнуть душу в любое фотореалистичное изображение. Вы можете добавлять разные эффекты (простое и продвинутое зерно, виньетку, размытие, коррекцию цвета, HDR) и применить LUT.<br><u>Требования:</u>
|
||||
- Положите LUT-файлы в `models/luts`, небольшую коллекцию можете найти [тут](https://huggingface.co/datasets/Gourieff/ReActor/tree/main/luts)<br>
|
||||
- `pip install colour-science` в Пайтон вашего Comfy -ИЛИ- просто запустите install.bat (пользователи Win) внутри папки РеАктора
|
||||
|
||||
*Вдохновлено такими узлами как: Image Apply LUT ([essentials](https://github.com/cubiq/ComfyUI_essentials)), LayerFilter: Film V2 ([LayerStyle](https://github.com/chflame163/ComfyUI_LayerStyle)), Color Correction ([post-processing-nodes](https://github.com/EllangoK/ComfyUI-post-processing-nodes)), HDR Effects ([SuperBeasts](https://github.com/SuperBeastsAI/ComfyUI-SuperBeasts))*
|
||||
|
||||
- Разделение узлов на подкатегории.
|
||||
|
||||
### 0.7.1
|
||||
|
||||
- **Новый узел `DLSS5 Frame Enhancer`**. Новейшая технология DLSS 5 от NVIDIA для улучшения качества изображения (кадра целиком или лиц по маске).<br><u>Следуйте [инструкции по установке](https://github.com/Gourieff/ComfyUI-ReActor/blob/main/r_dlssnr/dll/README.md).</u><br><pre>Интеграция работает в изолированном Host-режиме, что полностью исключает конфликты с CUDA-контекстом ComfyUI.<br>Огромная благодарность автору проекта [Merserk/dlss5-visual-enhancer](https://github.com/Merserk/dlss5-visual-enhancer) за C++ обертку (neuroframe_engine.dll и neuroframe_caller.dll), которая легла в основу вычислительного моста для данного узла.<br>Также благодарность Gemini 3.1 Pro (через [Google Gemini](https://gemini.google.com/app)) за ассистирование и помощь.</pre><u>Требования:</u><br>
|
||||
-- Windows 10/11<br>
|
||||
-- NVIDIA display driver >= 616.x<br>
|
||||
-- NVIDIA RTX 40/50-series GPU<br>
|
||||
- **Новый узел `DLSS5 Frame Enhancer`**. Новейшая технология DLSS 5 от NVIDIA для улучшения качества изображения (кадра целиком или лиц по маске).<br><u>Следуйте [инструкции по установке](https://github.com/Gourieff/ComfyUI-ReActor/blob/main/r_dlssnr/dll/README.md).</u><br><pre>Интеграция работает в изолированном Host-режиме, что полностью исключает конфликты с CUDA-контекстом ComfyUI.<br>Огромная благодарность автору проекта [Merserk/dlss5-visual-enhancer](https://github.com/Merserk/dlss5-visual-enhancer) за C++ обертку (neuroframe_engine.dll и neuroframe_caller.dll), которая легла в основу вычислительного моста для данного узла.<br>Также благодарность Gemini 3.1 Pro (через [Google Gemini](https://gemini.google.com/app)) за ассистирование и помощь.</pre><u>Требования:</u>
|
||||
- Windows 10/11
|
||||
- NVIDIA display driver >= 616.x
|
||||
- NVIDIA RTX 40/50-series GPU
|
||||
(совместимость с более старыми сериями RTX не подтверждена)
|
||||
- Прочие улучшение и исправления
|
||||
|
||||
@@ -307,6 +317,7 @@ Basic workflow [💾](https://github.com/Gourieff/Assets/blob/main/comfyui-react
|
||||
- ImageRGBA2RGB (Конвертировать RGBA в RGB)
|
||||
- ReActorUnload (Выгрузить модели РеАктора из VRAM)
|
||||
- DLSS5FrameEnhancer (Улучшение детализации кадра с NVIDIA DLSS 5)
|
||||
- ReActorCinematicEnhancer (Добавление кинематографических эффектов к изображению)
|
||||
|
||||
Соедините все необходимые слоты (slots) и запустите очередь (query).
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@ import scripts.r_masking.segs as masking_segs
|
||||
import scripts.reactor_sfw as sfw
|
||||
|
||||
from r_dlssnr.dlss5_node import DLSS5FrameEnhancer
|
||||
from r_nodes.cinematic_enhancer import ReActorCinematicEnhancer
|
||||
|
||||
|
||||
models_dir = folder_paths.models_dir
|
||||
@@ -173,7 +174,7 @@ class reactor:
|
||||
RETURN_TYPES = ("IMAGE","FACE_MODEL","IMAGE")
|
||||
RETURN_NAMES = ("SWAPPED_IMAGE","FACE_MODEL","ORIGINAL_IMAGE")
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/Face Swap"
|
||||
|
||||
def __init__(self):
|
||||
# self.face_helper = None
|
||||
@@ -546,7 +547,7 @@ class ReActorPlusOpt:
|
||||
RETURN_TYPES = ("IMAGE","FACE_MODEL","IMAGE")
|
||||
RETURN_NAMES = ("SWAPPED_IMAGE","FACE_MODEL","ORIGINAL_IMAGE")
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/Face Swap"
|
||||
|
||||
def __init__(self):
|
||||
# self.face_helper = None
|
||||
@@ -601,7 +602,7 @@ class LoadFaceModel:
|
||||
RETURN_TYPES = ("FACE_MODEL","STRING")
|
||||
RETURN_NAMES = ("FACE_MODEL","FACE_MODEL_NAME")
|
||||
FUNCTION = "load_model"
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/Face Swap"
|
||||
|
||||
def load_model(self, face_model):
|
||||
self.face_model = face_model
|
||||
@@ -635,7 +636,7 @@ class ReActorWeight:
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/Face Swap"
|
||||
|
||||
def set_weight(self, input_image, faceswap_weight, face_model=None, source_image=None):
|
||||
|
||||
@@ -739,7 +740,7 @@ class BuildFaceModel:
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/Face Swap"
|
||||
|
||||
def build_face_model(self, image: Image.Image, det_size=(640, 640)):
|
||||
logging.StreamHandler.terminator = "\n"
|
||||
@@ -869,7 +870,7 @@ class SaveFaceModel:
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/Face Swap"
|
||||
|
||||
def save_model(self, save_mode, face_model_name, select_face_index, image=None, face_model=None, det_size=(640, 640)):
|
||||
if save_mode and image is not None:
|
||||
@@ -910,7 +911,7 @@ class RestoreFace:
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/PostProcessing"
|
||||
|
||||
def execute(self, image, model, visibility, codeformer_weight, facedetection):
|
||||
result = reactor.restore_face(
|
||||
@@ -941,7 +942,7 @@ class RestoreFaceAdvanced:
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/PostProcessing"
|
||||
|
||||
def execute(
|
||||
self, image, model, visibility, codeformer_weight, facedetection, face_selection, sort_by="area", reverse_order=False, take_start=0, take_count=1
|
||||
@@ -1224,7 +1225,7 @@ class MaskHelper:
|
||||
RETURN_TYPES = ("IMAGE","MASK","IMAGE","IMAGE")
|
||||
RETURN_NAMES = ("IMAGE","MASK","MASK_PREVIEW","SWAPPED_FACE")
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/Masking"
|
||||
|
||||
def execute(self, image, swapped_image, bbox_model_name, bbox_threshold, bbox_dilation, bbox_crop_factor, bbox_drop_size, sam_model_name, sam_dilation, sam_threshold, bbox_expansion, mask_hint_threshold, mask_hint_use_negative, morphology_operation, morphology_distance, blur_radius, sigma_factor, mask_optional=None):
|
||||
device = model_management.get_torch_device()
|
||||
@@ -1527,7 +1528,7 @@ class ImageDublicator:
|
||||
RETURN_NAMES = ("IMAGES",)
|
||||
OUTPUT_IS_LIST = (True,)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/Utils"
|
||||
|
||||
def execute(self, image, count):
|
||||
images = [image for i in range(count)]
|
||||
@@ -1545,7 +1546,7 @@ class ImageRGBA2RGB:
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/Utils"
|
||||
|
||||
def execute(self, image):
|
||||
out = rgba2rgb_tensor(image)
|
||||
@@ -1576,7 +1577,7 @@ class MakeFaceModelBatch:
|
||||
RETURN_NAMES = ("FACE_MODELS",)
|
||||
FUNCTION = "execute"
|
||||
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/Face Swap"
|
||||
|
||||
def execute(self, **kwargs):
|
||||
if len(kwargs) > 0:
|
||||
@@ -1609,7 +1610,7 @@ class ReActorOptions:
|
||||
|
||||
RETURN_TYPES = ("OPTIONS",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/Face Swap"
|
||||
|
||||
def execute(self,input_faces_order, input_faces_index, detect_gender_input, source_faces_order, source_faces_index, detect_gender_source, console_log_level, restore_swapped_only):
|
||||
options: dict = {
|
||||
@@ -1641,7 +1642,7 @@ class ReActorFaceBoost:
|
||||
|
||||
RETURN_TYPES = ("FACE_BOOST",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/Face Swap"
|
||||
|
||||
def execute(self,enabled,boost_model,interpolation,visibility,codeformer_weight,restore_with_main_after):
|
||||
face_boost: dict = {
|
||||
@@ -1666,7 +1667,7 @@ class ReActorUnload:
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/Utils"
|
||||
|
||||
def execute(self, trigger):
|
||||
unload_all_models()
|
||||
@@ -1686,7 +1687,7 @@ class ReActorFaceSimilarity:
|
||||
RETURN_TYPES = ("FLOAT", "STRING")
|
||||
RETURN_NAMES = ("similarity_float", "similarity_text")
|
||||
FUNCTION = "compare_faces"
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/Utils"
|
||||
|
||||
def compare_faces(self, image1, image2):
|
||||
|
||||
@@ -1747,6 +1748,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImageRGBA2RGB": ImageRGBA2RGB,
|
||||
"ReActorUnload": ReActorUnload,
|
||||
"DLSS5FrameEnhancer": DLSS5FrameEnhancer,
|
||||
"ReActorCinematicEnhancer": ReActorCinematicEnhancer,
|
||||
}
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
@@ -1770,4 +1772,5 @@ NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"ImageRGBA2RGB": "Convert RGBA to RGB 🌌 ReActor",
|
||||
"ReActorUnload": "Unload ReActor Models 🌌 ReActor",
|
||||
"DLSS5FrameEnhancer": "DLSS5 Frame Enhancer 🌌 ReActor",
|
||||
"ReActorCinematicEnhancer": "Cinematic Enhancer 🌌 ReActor",
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class DLSS5FrameEnhancer:
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("enhanced_image",)
|
||||
FUNCTION = "enhance"
|
||||
CATEGORY = "🌌 ReActor"
|
||||
CATEGORY = "🌌 ReActor/PostProcessing"
|
||||
DESCRIPTION = (
|
||||
"Requirements:\n"
|
||||
"- NVIDIA display driver >= 616.x\n"
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
import os
|
||||
import torch
|
||||
import numpy as np
|
||||
import cv2
|
||||
import folder_paths
|
||||
from pathlib import Path
|
||||
from PIL import Image, ImageEnhance, ImageCms
|
||||
import comfy.model_management as model_management
|
||||
from scripts.reactor_logger import logger
|
||||
from r_modules.shared import state
|
||||
from reactor_utils import (
|
||||
progress_bar,
|
||||
progress_bar_reset
|
||||
)
|
||||
try:
|
||||
from colour.io.luts.iridas_cube import read_LUT_IridasCube
|
||||
AVAILABLE = True
|
||||
except Exception as e:
|
||||
AVAILABLE = False
|
||||
logger.error(f"{e}, ReActorCinematicEnhancer is not available, please install `colour` library: `pip install colour-science`")
|
||||
|
||||
folder_paths.add_model_folder_path("luts", os.path.join(folder_paths.models_dir, "luts"))
|
||||
|
||||
# Профили для HDR (создаются один раз при импорте для экономии ресурсов)
|
||||
sRGB_profile = ImageCms.createProfile('sRGB')
|
||||
Lab_profile = ImageCms.createProfile('LAB')
|
||||
|
||||
def get_luts():
|
||||
luts = folder_paths.get_filename_list("luts")
|
||||
luts = [x for x in luts if x.lower().endswith(".cube") or x.lower().endswith(".spi1d") or x.lower().endswith(".spi3d") or x.lower().endswith(".spimtx") or x.lower().endswith(".csp")]
|
||||
luts.sort(key=str.lower)
|
||||
luts.insert(0, "none")
|
||||
return luts
|
||||
|
||||
class ReActorCinematicEnhancer:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE", ),
|
||||
# --- Global Blend ---
|
||||
"intensity": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "0..1, def: 0.5"}),
|
||||
# --- LUT ---
|
||||
"lut_file": (get_luts(),),
|
||||
"lut_strength": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.1, "tooltip": "0..1, def: 0.5"}),
|
||||
"lut_gamma_correction": ("BOOLEAN", { "default": True }),
|
||||
# --- Film Effect ---
|
||||
"film_grain_method": (["advanced","fast"], {"default": "advanced"}),
|
||||
"film_grain_power": ("FLOAT", {"default": 0.1, "min": 0, "max": 1, "step": 0.01, "tooltip": "0..1, def: 0.1"}),
|
||||
"film_grain_scale": ("FLOAT", {"default": 1, "min": 0.1, "max": 10, "step": 0.1, "tooltip": "0.1..1, def: 0.1"}),
|
||||
"film_grain_sat": ("FLOAT", {"default": 0.15, "min": 0, "max": 1, "step": 0.01, "tooltip": "(advanced only) 0..1, def: 0.15"}),
|
||||
"film_grain_shadows": ("FLOAT", {"default": 0.2, "min": 0, "max": 1, "step": 0.01, "tooltip": "(advanced only) 0..1, def: 0.2"}),
|
||||
"film_grain_highs": ("FLOAT", {"default": 0.2, "min": 0, "max": 1, "step": 0.01, "tooltip": "(advanced only) 0..1, def: 0.2"}),
|
||||
"film_blur_strength": ("INT", {"default": 1, "min": 0, "max": 256, "step": 1, "tooltip": "(by depth_map) 0..256, def: 1"}),
|
||||
"film_focal_depth": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 1, "step": 0.01, "tooltip": "(by depth_map) 0..1, def: 0.9"}),
|
||||
"film_focus_spread": ("FLOAT", {"default": 2.2, "min": 0.1, "max": 8, "step": 0.1, "tooltip": "(by depth_map) 0..8, def: 2.2"}),
|
||||
"film_vignette_intensity": ("FLOAT", {"default": 0.1, "min": 0, "max": 1, "step": 0.01, "tooltip": "0..1, def: 0.1"}),
|
||||
# --- Color Correct ---
|
||||
"cc_temperature": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 5, "tooltip": "-100..100, def: 0"}),
|
||||
"cc_hue": ("FLOAT", {"default": 0, "min": -90, "max": 90, "step": 5, "tooltip": "-90..90, def: 0"}),
|
||||
"cc_brightness": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 5, "tooltip": "-100..100, def: 0"}),
|
||||
"cc_contrast": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 5, "tooltip": "-100..100, def: 0"}),
|
||||
"cc_saturation": ("FLOAT", {"default": 0, "min": -100, "max": 100, "step": 5, "tooltip": "-100..100, def: 0"}),
|
||||
"cc_gamma": ("FLOAT", {"default": 1, "min": 0.2, "max": 2.2, "step": 0.1, "tooltip": "0.2..2.2, def: 1.0"}),
|
||||
# --- HDR Effects ---
|
||||
"hdr_intensity": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 5.0, "step": 0.01, "tooltip": "0..5, def: 0.5"}),
|
||||
"hdr_shadow_intensity": ("FLOAT", {"default": 0.25, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "0..1, def: 0.25"}),
|
||||
"hdr_highlight_intensity": ("FLOAT", {"default": 0.75, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "0..1, def: 0.75"}),
|
||||
"hdr_contrast": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "0..1, def: 0.1"}),
|
||||
"hdr_enhance_color": ("FLOAT", {"default": 0.25, "min": 0.0, "max": 1.0, "step": 0.01, "tooltip": "0..1, def: 0.25"}),
|
||||
},
|
||||
"optional": {
|
||||
"depth_map": ("IMAGE",),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("image",)
|
||||
FUNCTION = "execute"
|
||||
CATEGORY = "🌌 ReActor/PostProcessing"
|
||||
DESCRIPTION = (
|
||||
"Requirements:\n"
|
||||
"- pip install colour-science\n"
|
||||
"- put LUT-files in models/luts\n"
|
||||
"depth_map is optional"
|
||||
)
|
||||
|
||||
def execute(self, image, intensity, lut_file, lut_strength, lut_gamma_correction,
|
||||
film_grain_method, film_grain_power, film_grain_scale, film_grain_sat, film_grain_shadows, film_grain_highs,film_blur_strength, film_focal_depth, film_focus_spread, film_vignette_intensity,
|
||||
cc_temperature, cc_hue, cc_brightness, cc_contrast, cc_saturation, cc_gamma,
|
||||
hdr_intensity, hdr_shadow_intensity, hdr_highlight_intensity, hdr_contrast, hdr_enhance_color,
|
||||
depth_map=None):
|
||||
|
||||
if not AVAILABLE:
|
||||
logger.error("ReActorCinematicEnhancer is not available, please install `colour` library: `pip install colour-science`")
|
||||
return (image,)
|
||||
|
||||
# 1. Загрузка LUT
|
||||
|
||||
if lut_file != "none":
|
||||
lut_file_path = folder_paths.get_full_path("luts", lut_file)
|
||||
lut = read_LUT_IridasCube(lut_file_path) if lut_file_path and Path(lut_file_path).exists() else None
|
||||
if lut:
|
||||
lut.name = lut_file
|
||||
logger.status(f"LUT {lut_file} selected")
|
||||
else:
|
||||
lut = None
|
||||
|
||||
batch_size = image.shape[0]
|
||||
result_batch = []
|
||||
|
||||
pbar = progress_bar(batch_size)
|
||||
|
||||
logger.status(f"Enhancing...")
|
||||
|
||||
for b in range(batch_size):
|
||||
|
||||
if state.interrupted or model_management.processing_interrupted():
|
||||
logger.status("Interrupted by User")
|
||||
break
|
||||
|
||||
# --- БАЗА: NumPy (0-1) ---
|
||||
orig_np = image[b].cpu().numpy().copy()
|
||||
img_np = orig_np.copy()
|
||||
|
||||
# --- 1. LUT ---
|
||||
if lut:
|
||||
lut_img = img_np.copy()
|
||||
if lut_gamma_correction:
|
||||
lut_img = np.clip(lut_img ** (1/2.2), 0, 1)
|
||||
lut_img = lut.apply(lut_img)
|
||||
if lut_gamma_correction:
|
||||
lut_img = np.clip(lut_img ** 2.2, 0, 1)
|
||||
|
||||
if lut_strength < 1.0:
|
||||
img_np = lut_strength * lut_img + (1 - lut_strength) * img_np
|
||||
else:
|
||||
img_np = lut_img
|
||||
|
||||
# Подготовка маски глубины (если есть)
|
||||
depth_np = None
|
||||
if depth_map is not None:
|
||||
d_tensor = depth_map[b if depth_map.shape[0] > 1 else 0].cpu().numpy()
|
||||
if d_tensor.shape[:2] != img_np.shape[:2]:
|
||||
d_tensor = cv2.resize(d_tensor, (img_np.shape[1], img_np.shape[0]), interpolation=cv2.INTER_LINEAR)
|
||||
# Если глубина RGB, берем один канал
|
||||
depth_np = d_tensor[:,:,0] if d_tensor.ndim == 3 else d_tensor
|
||||
|
||||
# --- 2. FILM EFFECT ---
|
||||
# Виньетка (OpenCV)
|
||||
if film_vignette_intensity > 0:
|
||||
h, w = img_np.shape[:2]
|
||||
Y, X = np.ogrid[:h, :w]
|
||||
center_y, center_x = h / 2, w / 2
|
||||
dist_from_center = np.sqrt((X - center_x)**2 + (Y - center_y)**2)
|
||||
max_dist = np.sqrt(center_x**2 + center_y**2)
|
||||
vignette = 1 - (dist_from_center / max_dist) * film_vignette_intensity
|
||||
img_np = np.clip(img_np * vignette[..., np.newaxis], 0, 1)
|
||||
|
||||
# Глубинное размытие (пошаговая реализация)
|
||||
if film_blur_strength > 0 and depth_np is not None:
|
||||
# Нормализуем разницу глубины относительно точки фокуса
|
||||
depth_diff = np.abs(depth_np - film_focal_depth) * film_focus_spread
|
||||
depth_diff = np.clip(depth_diff, 0, 1)
|
||||
|
||||
# Пошаговый блендинг (5 шагов для плавности)
|
||||
steps = 5
|
||||
blurred_fg = np.zeros_like(img_np)
|
||||
for step in range(steps + 1):
|
||||
step_blur = int((film_blur_strength * step / steps) // 2 * 2 + 1)
|
||||
if step_blur > 1:
|
||||
blur_layer = cv2.GaussianBlur(img_np, (step_blur, step_blur), 0)
|
||||
else:
|
||||
blur_layer = img_np
|
||||
|
||||
# Маска весов для текущего шага
|
||||
mask = np.clip(1.0 - np.abs(depth_diff * steps - step), 0, 1)
|
||||
blurred_fg += blur_layer * mask[..., np.newaxis]
|
||||
img_np = blurred_fg
|
||||
|
||||
# --- 3. COLOR CORRECT ---
|
||||
fg_img = Image.fromarray((img_np * 255).astype(np.uint8))
|
||||
|
||||
# Яркость и Контраст (PIL)
|
||||
if cc_brightness != 0:
|
||||
fg_img = ImageEnhance.Brightness(fg_img).enhance(1 + (cc_brightness / 100))
|
||||
if cc_contrast != 0:
|
||||
fg_img = ImageEnhance.Contrast(fg_img).enhance(1 + (cc_contrast / 100))
|
||||
|
||||
fg_cv = np.array(fg_img).astype(np.float32) / 255.0
|
||||
|
||||
# Температура
|
||||
temp_val = cc_temperature / 100
|
||||
if temp_val > 0:
|
||||
fg_cv[:, :, 0] = np.clip(fg_cv[:, :, 0] * (1 + temp_val), 0, 1)
|
||||
fg_cv[:, :, 1] = np.clip(fg_cv[:, :, 1] * (1 + temp_val * 0.4), 0, 1)
|
||||
elif temp_val < 0:
|
||||
fg_cv[:, :, 2] = np.clip(fg_cv[:, :, 2] * (1 - temp_val), 0, 1)
|
||||
|
||||
# Гамма
|
||||
if cc_gamma != 1.0:
|
||||
fg_cv = np.clip(np.power(fg_cv, cc_gamma), 0, 1)
|
||||
|
||||
# HSV для Hue и Saturation
|
||||
if cc_hue != 0 or cc_saturation != 0:
|
||||
hsv = cv2.cvtColor((fg_cv * 255).astype(np.uint8), cv2.COLOR_RGB2HSV).astype(np.float32)
|
||||
if cc_hue != 0:
|
||||
hsv[:, :, 0] = (hsv[:, :, 0] + (cc_hue / 2)) % 180 # В OpenCV Hue до 180
|
||||
if cc_saturation != 0:
|
||||
hsv[:, :, 1] = np.clip(hsv[:, :, 1] * (1 + cc_saturation / 100), 0, 255)
|
||||
fg_cv = cv2.cvtColor(hsv.astype(np.uint8), cv2.COLOR_HSV2RGB).astype(np.float32) / 255.0
|
||||
|
||||
img_np = fg_cv
|
||||
|
||||
# --- 4. FILM GRAIN ---
|
||||
if film_grain_power > 0:
|
||||
|
||||
# --- 4.1 ADVANCED NATIVE GRAIN ---
|
||||
if film_grain_method == "advanced":
|
||||
# 1) Генерируем базовый цветной шум
|
||||
noise_rgb = np.random.normal(0, film_grain_power, img_np.shape).astype(np.float32)
|
||||
|
||||
# 2) Генерируем монохромный шум (усреднение по каналам)
|
||||
noise_mono = np.mean(noise_rgb, axis=-1, keepdims=True)
|
||||
|
||||
# 3) Смешиваем цветность зерна (grain_sat)
|
||||
noise = noise_rgb * film_grain_sat + noise_mono * (1.0 - film_grain_sat)
|
||||
|
||||
# 4) Масштабирование зерна
|
||||
if film_grain_scale != 1.0:
|
||||
h, w = img_np.shape[:2]
|
||||
small_noise = cv2.resize(noise, (int(w / film_grain_scale), int(h / film_grain_scale)))
|
||||
noise = cv2.resize(small_noise, (w, h), interpolation=cv2.INTER_LINEAR)
|
||||
|
||||
# 5) Кинематографичная маскировка по яркости (Luminance)
|
||||
# Вычисляем воспринимаемую яркость пикселей
|
||||
lum = 0.299 * img_np[:,:,0] + 0.587 * img_np[:,:,1] + 0.114 * img_np[:,:,2]
|
||||
lum = lum[..., np.newaxis]
|
||||
|
||||
# Создаем карту весов шума:
|
||||
# Средние тона (0.5) получают вес 1.0.
|
||||
# Тени (0.0) получают вес film_grain_shadows.
|
||||
# Блики (1.0) получают вес film_grain_highs.
|
||||
grain_mask = np.where(
|
||||
lum < 0.5,
|
||||
film_grain_shadows + (1.0 - film_grain_shadows) * (lum * 2.0), # Плавный переход от теней к средним
|
||||
1.0 - (1.0 - film_grain_highs) * ((lum - 0.5) * 2.0) # Плавный переход от средних к бликам
|
||||
)
|
||||
|
||||
# Накладываем шум с учетом яркостной маски
|
||||
img_np = np.clip(img_np + noise * grain_mask, 0, 1)
|
||||
|
||||
# --- 4.2 FAST GRAIN ---
|
||||
else:
|
||||
noise = np.random.normal(0, film_grain_power, img_np.shape).astype(np.float32)
|
||||
# Масштабирование шума
|
||||
if film_grain_scale != 1.0:
|
||||
h, w = img_np.shape[:2]
|
||||
small_noise = cv2.resize(noise, (int(w / film_grain_scale), int(h / film_grain_scale)))
|
||||
noise = cv2.resize(small_noise, (w, h), interpolation=cv2.INTER_LINEAR)
|
||||
img_np = np.clip(img_np + noise * 0.5, 0, 1)
|
||||
|
||||
# --- 5. HDR EFFECTS ---
|
||||
if hdr_intensity > 0:
|
||||
final_pil = Image.fromarray((img_np * 255).astype(np.uint8))
|
||||
|
||||
# Основная математика тонирования LAB
|
||||
img_lab = ImageCms.profileToProfile(final_pil, sRGB_profile, Lab_profile, outputMode='LAB')
|
||||
luminance, a, b = img_lab.split()
|
||||
base = np.array(luminance, dtype=np.float32)
|
||||
|
||||
scaled_shadow = (hdr_shadow_intensity ** 2) * hdr_intensity
|
||||
scaled_highlight = (hdr_highlight_intensity ** 2) * hdr_intensity
|
||||
|
||||
shadow_mask = np.clip((1 - (base / 255)) ** 2, 0, 1)
|
||||
highlight_mask = np.clip((base / 255) ** 2, 0, 1)
|
||||
|
||||
adj_shadows = np.clip(base * (1 - shadow_mask * scaled_shadow), 0, 255)
|
||||
adj_highlights = np.clip(base + (255 - base) * highlight_mask * scaled_highlight, 0, 255)
|
||||
|
||||
adj_lum = np.clip(adj_shadows + adj_highlights - base, 0, 255)
|
||||
final_lum = np.clip(base * (1 - hdr_intensity) + adj_lum * hdr_intensity, 0, 255).astype(np.uint8)
|
||||
|
||||
merged_lab = Image.merge('LAB', (Image.fromarray(final_lum), a, b))
|
||||
final_pil = ImageCms.profileToProfile(merged_lab, Lab_profile, sRGB_profile, outputMode='RGB')
|
||||
|
||||
if hdr_contrast > 0:
|
||||
final_pil = ImageEnhance.Contrast(final_pil).enhance(1 + hdr_contrast)
|
||||
if hdr_enhance_color > 0:
|
||||
final_pil = ImageEnhance.Color(final_pil).enhance(1 + hdr_enhance_color * 0.2)
|
||||
|
||||
# Возвращаем HDR в рабочий массив для финального смешивания
|
||||
img_np = np.array(final_pil).astype(np.float32) / 255.0
|
||||
|
||||
# --- 6. GLOBAL INTENSITY BLEND ---
|
||||
if intensity < 1.0:
|
||||
img_np = orig_np * (1.0 - intensity) + img_np * intensity
|
||||
|
||||
# Возврат в тензор
|
||||
out_tensor = torch.from_numpy(img_np)
|
||||
result_batch.append(out_tensor)
|
||||
|
||||
pbar.update(1)
|
||||
|
||||
progress_bar_reset(pbar)
|
||||
|
||||
return (torch.stack(result_batch),)
|
||||
@@ -4,3 +4,4 @@ opencv-python>=4.7.0.72
|
||||
numpy
|
||||
segment_anything
|
||||
ultralytics
|
||||
colour-science
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
app_title = "ReActor Node for ComfyUI"
|
||||
version_flag = "v0.7.1"
|
||||
version_flag = "v0.7.2-b1"
|
||||
|
||||
COLORS = {
|
||||
"CYAN": "\033[0;36m", # CYAN
|
||||
|
||||
Reference in New Issue
Block a user