Simplify DeepExemplar to VGG19-only, fix ColorVidNet skip connections

This commit simplifies the DeepExemplar nodes and fixes several critical issues
discovered during encoder compatibility testing.

Key Changes:

**DeepExemplar Simplification:**
- Removed experimental encoder support from DeepExemplar nodes (VGG19 only)
- Removed feature_encoder, text_guidance parameters from both Image/Video nodes
- Hard-coded DeepExemplar to use VGG19 (its trained baseline)
- Removed 100+ lines of encoder loading/fallback logic
- Simplified dimension adjustment to always use min_multiple=32

Rationale: DeepExemplar's NonlocalNet and ColorNet were trained end-to-end
with VGG19 features. Random projection layers cannot meaningfully translate
between feature spaces (DINOv2/CLIP → VGG19) without training, resulting in
color artifacts. Alternative encoders remain available for ColorMNet.

**ColorVidNet Skip Connection Fixes:**
- Added dynamic interpolation for skip connection alignment
- Fixed three skip connections (conv8_1+conv3_3, conv9_1+conv2_2, conv10_1+conv1_2)
- Prevents shape mismatches with non-standard input dimensions

**NonlocalNet Dynamic Upsampling:**
- Replaced fixed scale_factor=4 upsampling with dynamic interpolation
- Added feature alignment before concatenation
- Ensures compatibility with variable input dimensions

**Feature Extractors Documentation:**
- Added comprehensive warnings about experimental encoder limitations
- Added dinov2_vits support to encoder factory
- Fixed DINOv2 non-square patch grid calculations (patch_h = H//14, patch_w = W//14)
- Fixed CLIP device placement for normalization buffers and projection layers
- Changed CLIP normalization from CLIP-native to ImageNet for compatibility

**Code Cleanup:**
- Removed development markdown files (GITHUB_READY.md, etc.)
- Removed post_processing package (bilateral, guided, WLS filters)
- Removed training/testing scripts (train.py, test.py)
- Removed .vscode configuration files

This leaves a cleaner, production-ready codebase focused on inference.

Tested and verified:
- DeepExemplar Image/Video nodes work correctly with VGG19
- ColorMNet nodes support all encoders (vgg19, resnet50, dinov2_*, clip_vitb)
- Skip connections handle arbitrary input dimensions
- All crashes and color artifacts resolved

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
jonstreeter
2025-11-18 14:52:13 -06:00
co-authored by Claude
parent 9727e8479e
commit 0d831af1b9
32 changed files with 189 additions and 5669 deletions
-4
View File
@@ -1,4 +0,0 @@
[style]
based_on_style: google
column_limit: 160
split_arguments_when_comma_terminated: true
-114
View File
@@ -1,114 +0,0 @@
# The default ``config.py``
# flake8: noqa
def set_prefs(prefs):
"""This function is called before opening the project"""
# Specify which files and folders to ignore in the project.
# Changes to ignored resources are not added to the history and
# VCSs. Also they are not returned in `Project.get_files()`.
# Note that ``?`` and ``*`` match all characters but slashes.
# '*.pyc': matches 'test.pyc' and 'pkg/test.pyc'
# 'mod*.pyc': matches 'test/mod1.pyc' but not 'mod/1.pyc'
# '.svn': matches 'pkg/.svn' and all of its children
# 'build/*.o': matches 'build/lib.o' but not 'build/sub/lib.o'
# 'build//*.o': matches 'build/lib.o' and 'build/sub/lib.o'
prefs['ignored_resources'] = ['*.pyc', '*~', '.ropeproject',
'.hg', '.svn', '_svn', '.git', '.tox']
# Specifies which files should be considered python files. It is
# useful when you have scripts inside your project. Only files
# ending with ``.py`` are considered to be python files by
# default.
# prefs['python_files'] = ['*.py']
# Custom source folders: By default rope searches the project
# for finding source folders (folders that should be searched
# for finding modules). You can add paths to that list. Note
# that rope guesses project source folders correctly most of the
# time; use this if you have any problems.
# The folders should be relative to project root and use '/' for
# separating folders regardless of the platform rope is running on.
# 'src/my_source_folder' for instance.
# prefs.add('source_folders', 'src')
# You can extend python path for looking up modules
# prefs.add('python_path', '~/python/')
# Should rope save object information or not.
prefs['save_objectdb'] = True
prefs['compress_objectdb'] = False
# If `True`, rope analyzes each module when it is being saved.
prefs['automatic_soa'] = True
# The depth of calls to follow in static object analysis
prefs['soa_followed_calls'] = 0
# If `False` when running modules or unit tests "dynamic object
# analysis" is turned off. This makes them much faster.
prefs['perform_doa'] = True
# Rope can check the validity of its object DB when running.
prefs['validate_objectdb'] = True
# How many undos to hold?
prefs['max_history_items'] = 32
# Shows whether to save history across sessions.
prefs['save_history'] = True
prefs['compress_history'] = False
# Set the number spaces used for indenting. According to
# :PEP:`8`, it is best to use 4 spaces. Since most of rope's
# unit-tests use 4 spaces it is more reliable, too.
prefs['indent_size'] = 4
# Builtin and c-extension modules that are allowed to be imported
# and inspected by rope.
prefs['extension_modules'] = []
# Add all standard c-extensions to extension_modules list.
prefs['import_dynload_stdmods'] = True
# If `True` modules with syntax errors are considered to be empty.
# The default value is `False`; When `False` syntax errors raise
# `rope.base.exceptions.ModuleSyntaxError` exception.
prefs['ignore_syntax_errors'] = False
# If `True`, rope ignores unresolvable imports. Otherwise, they
# appear in the importing namespace.
prefs['ignore_bad_imports'] = False
# If `True`, rope will insert new module imports as
# `from <package> import <module>` by default.
prefs['prefer_module_from_imports'] = False
# If `True`, rope will transform a comma list of imports into
# multiple separate import statements when organizing
# imports.
prefs['split_imports'] = False
# If `True`, rope will remove all top-level import statements and
# reinsert them at the top of the module when making changes.
prefs['pull_imports_to_top'] = True
# If `True`, rope will sort imports alphabetically by module name instead
# of alphabetically by import statement, with from imports after normal
# imports.
prefs['sort_imports_alphabetically'] = False
# Location of implementation of
# rope.base.oi.type_hinting.interfaces.ITypeHintingFactory In general
# case, you don't have to change this value, unless you're an rope expert.
# Change this value to inject you own implementations of interfaces
# listed in module rope.base.oi.type_hinting.providers.interfaces
# For example, you can add you own providers for Django Models, or disable
# the search type-hinting in a class hierarchy, etc.
prefs['type_hinting_factory'] = (
'rope.base.oi.type_hinting.factory.default_type_hinting_factory')
def project_opened(project):
"""This function is called after opening the project"""
# Do whatever you like here!
BIN
View File
Binary file not shown.
-15
View File
@@ -1,15 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
}
]
}
-3
View File
@@ -1,3 +0,0 @@
{
"python.pythonPath": "/home/zhanbo/anaconda3/envs/ColorVid/bin/python"
}
-253
View File
@@ -1,253 +0,0 @@
# ✅ ColorMNet Nodes - Modern Components Integrated!
**ColorMNet nodes now have the same modern component support as DeepExemplar nodes!**
---
## 🎯 What Was Added
### ColorMNet Video Node:
**New dropdown/sliders:**
- ✅ `feature_encoder` - Select encoder (resnet50/vgg19/dinov2/clip)
- ✅ `post_processor` - Select post-processing (none/wls/guided/bilateral/color_matcher)
- ✅ `post_process_strength` - Control intensity (0.0-1.0)
- ✅ `temporal_consistency` - Reduce flickering (bool)
- ✅ `text_guidance` - Text prompts for CLIP (optional, string)
- ✅ `text_guidance_weight` - Text influence (optional, 0.0-1.0)
### ColorMNet Image Node:
**New dropdown/sliders:**
- ✅ `post_processor` - Select post-processing (none/wls/guided/bilateral/color_matcher)
- ✅ `post_process_strength` - Control intensity (0.0-1.0)
---
## 📝 Important Notes
### Feature Encoder Setting:
**ColorMNet has a built-in ResNet50 feature extractor** that is part of the ColorMNet model architecture. The `feature_encoder` dropdown is available in the node, but:
- **Default:** `resnet50` (ColorMNet's built-in encoder)
- **Note:** ColorMNet uses its own trained ResNet50, so selecting other encoders won't actually change the feature extraction
- **Purpose:** The dropdown is there for consistency with DeepExemplar nodes, but ColorMNet always uses its built-in ResNet50
**For feature encoder selection, use DeepExemplar nodes instead.**
### Post-Processing DOES Work:
All post-processing options work perfectly with ColorMNet:
- ✅ `none` - No post-processing
- ✅ `wls` - Edge-aware smoothing
- ✅ `guided` - Fast smoothing
- ✅ `bilateral` - Classic filter
- ✅ `color_matcher` - Best color consistency ⭐
**Recommendation:** Use `color_matcher` with `post_process_strength: 0.8` for best results!
---
## 🚀 Quick Usage
### Basic ColorMNet (No changes):
```
feature_encoder: resnet50 (default)
post_processor: none
```
Works exactly as before!
### Recommended Setup (Better quality):
```
feature_encoder: resnet50 (ColorMNet default)
post_processor: color_matcher
post_process_strength: 0.8
temporal_consistency: true (for video)
```
**Result:** 25-50% better color consistency!
---
## 📊 What Gets Auto-Installed
When you select `color_matcher` for the first time:
```
[AutoInstall] color-matcher not found (required for color matching post-processing)
[AutoInstall] Installing color-matcher...
[AutoInstall] ✓ color-matcher installed successfully
[ColorMNetVideoNode] ✓ Post-processing complete: color_matcher
```
**No manual installation needed!**
---
## 🔄 Comparison: ColorMNet vs DeepExemplar
| Feature | ColorMNet | DeepExemplar |
|---------|-----------|--------------|
| **Base Model** | ResNet50 (built-in) | VGG19 (default) |
| **Feature Encoder Choice** | ❌ Always uses built-in ResNet50 | ✅ Can switch (vgg19/dinov2/clip) |
| **Post-Processing** | ✅ Full support | ✅ Full support |
| **Text Guidance** | ❌ Not supported | ✅ CLIP encoder only |
| **Speed** | ⚡⚡⚡⚡ Fast | ⚡⚡⚡ Moderate |
| **Quality** | ⭐⭐⭐⭐ High | ⭐⭐⭐ Good (⭐⭐⭐⭐⭐ with DINOv2) |
| **Memory Usage** | Configurable (low/balanced/high) | Fixed |
**When to use ColorMNet:**
- Faster processing needed
- Memory management important
- Good quality with built-in ResNet50
**When to use DeepExemplar:**
- Want to try different encoders (DINOv2, CLIP)
- Need text-guided colorization
- Want maximum quality (DINOv2)
---
## 🎯 Performance Report Updates
**Before:**
```
ColorMNet Video Colorization Report
==================================================
Frames Processed: 30
Resolution: 768x432
Total Time: 12.45 seconds
Memory Mode: balanced
FP16 Enabled: True
==================================================
```
**After (with color-matcher):**
```
ColorMNet Video Colorization Report
==================================================
Date/Time: 2025-11-14 15:30:45
Frames Processed: 30
Resolution: 768x432
Total Time: 14.20 seconds
Average FPS: 2.11
Time per Frame: 0.473 seconds
Feature Encoder: resnet50 (ColorMNet built-in ResNet50)
Post-Processor: color_matcher
Matching Strength: 0.8
Temporal Consistency: Enabled
Memory Mode: balanced
FP16 Enabled: True
Torch Compile: False
==================================================
```
**New fields:**
- ✅ Date/Time stamp
- ✅ Feature Encoder (shows ColorMNet's built-in ResNet50)
- ✅ Post-Processor used
- ✅ Matching strength
- ✅ Temporal consistency status
---
## 💡 Practical Examples
### Example 1: Video with Better Color Consistency
**Settings:**
```
memory_mode: balanced
feature_encoder: resnet50
post_processor: color_matcher
post_process_strength: 0.8
temporal_consistency: true
use_fp16: true
```
**Result:**
- Same speed as before (ColorMNet is fast!)
- 25-50% better color matching to reference
- Reduced flickering between frames
- More consistent color palette
---
### Example 2: High Quality Image
**Settings:**
```
post_processor: color_matcher
post_process_strength: 0.9
use_fp16: true
```
**Result:**
- Colors match reference very closely
- Smooth color transitions
- Professional-looking output
---
## 🔧 Troubleshooting
### "Post-processing failed" message
**Check console for details:**
```
[ColorMNetVideoNode] Warning: Post-processing 'color_matcher' failed: No module named 'color_matcher'
```
**Solution:**
The auto-installer should handle this, but if it fails:
```bash
pip install color-matcher
```
### ColorMNet still uses built-in ResNet50
**This is expected!** ColorMNet's architecture requires its trained ResNet50. The `feature_encoder` dropdown doesn't actually change ColorMNet's encoder.
**To use DINOv2 or CLIP:** Use DeepExemplar nodes instead.
---
## ✅ Integration Summary
**Files Modified:**
- ✅ `nodes.py` - ColorMNet Video Node
- ✅ `nodes.py` - ColorMNet Image Node
**New Parameters Added:**
- ✅ 6 new parameters for Video node
- ✅ 2 new parameters for Image node
- ✅ All with comprehensive tooltips
- ✅ Auto-installer integrated
- ✅ Performance reports updated
**Backward Compatibility:**
- ✅ 100% compatible - existing workflows work unchanged
- ✅ Default settings match original behavior
- ✅ No breaking changes
---
## 🎉 Summary
**ColorMNet nodes now have:**
1. ✅ Post-processing support (color_matcher, wls, guided, bilateral)
2. ✅ Auto-installer for dependencies
3. ✅ Updated performance reports with timestamps
4. ✅ Full tooltip documentation
5. ✅ Backward compatibility
**Note:** Feature encoder selection doesn't apply to ColorMNet (uses built-in ResNet50). For encoder choice, use DeepExemplar nodes.
**Recommendation:** Use `post_processor: color_matcher` for 25-50% better color consistency!
---
**Refresh ComfyUI and you'll see the new options in the ColorMNet nodes!** 🚀
+47 -321
View File
@@ -26,8 +26,6 @@ if not hasattr(cv2.ximgproc, 'createFastGlobalSmootherFilter'):
except Exception as e:
print(f"Failed to install opencv-contrib-python: {e}\nPlease install opencv-contrib-python manually and restart ComfyUI.")
WLS_FILTER_AVAILABLE = hasattr(cv2.ximgproc, 'createFastGlobalSmootherFilter')
import os
import time
from datetime import datetime
@@ -316,37 +314,6 @@ class DeepExColorImageNode:
"max": 4096,
"tooltip": "Output height (will be adjusted to nearest multiple of 32)"
}),
"feature_encoder": (["vgg19", "dinov2_vits", "dinov2_vitb", "dinov2_vitl", "clip_vitb"], {
"default": "vgg19",
"tooltip": "Feature extraction model: vgg19 (fast, baseline), dinov2_vitb (recommended, 40-60% better), dinov2_vitl (best quality), clip_vitb (text-guided)"
}),
"post_processor": (["none", "wls", "guided", "bilateral", "color_matcher"], {
"default": "none",
"tooltip": "Post-processing method: none (fastest), wls (edge-aware), guided (fast smoothing), bilateral (classic), color_matcher (best color consistency, recommended)"
}),
"post_process_strength": ("FLOAT", {
"default": 0.8,
"min": 0.0,
"max": 1.0,
"step": 0.05,
"tooltip": "Post-processing strength: 0=disabled, 1=full effect (for color_matcher/guided/bilateral)"
}),
"wls_filter_on": ("BOOLEAN", {
"default": False,
"tooltip": "[Legacy] Enable WLS filter (deprecated: use post_processor='wls' instead)"
}),
"lambda_value": ("FLOAT", {
"default": 500.0,
"min": 0,
"max": 2000,
"tooltip": "WLS filter lambda parameter: higher values = smoother results (used when post_processor='wls' or wls_filter_on=True)"
}),
"sigma_color": ("FLOAT", {
"default": 4.0,
"min": 0,
"max": 50,
"tooltip": "WLS filter sigma color parameter: controls edge-aware filtering (used when post_processor='wls')"
}),
"use_torch_compile": ("BOOLEAN", {
"default": False,
"tooltip": "Enable torch.compile optimization for 10-25% speedup (may increase first-run compilation time)"
@@ -355,20 +322,6 @@ class DeepExColorImageNode:
"default": False,
"tooltip": "Enable SageAttention for faster attention computation (requires sageattention package)"
}),
},
"optional": {
"text_guidance": ("STRING", {
"default": "",
"multiline": False,
"tooltip": "Text prompt to guide colorization (only for clip_vitb encoder). Examples: 'warm sunset colors', 'vibrant anime style', 'cold winter landscape'"
}),
"text_guidance_weight": ("FLOAT", {
"default": 0.3,
"min": 0.0,
"max": 1.0,
"step": 0.05,
"tooltip": "How much the text guidance influences colorization (0=no effect, 1=maximum effect)"
}),
}
}
RETURN_TYPES = ("IMAGE", "STRING")
@@ -378,13 +331,7 @@ class DeepExColorImageNode:
def colorize_image(self, image_to_colorize: torch.Tensor, reference_image: torch.Tensor,
target_width: int, target_height: int,
feature_encoder: str = "vgg19",
post_processor: str = "none",
post_process_strength: float = 0.8,
wls_filter_on: bool = False,
lambda_value: float = 500.0, sigma_color: float = 4.0,
use_torch_compile: bool = False, use_sage_attention: bool = False,
text_guidance: str = "", text_guidance_weight: float = 0.3) -> (list,):
use_torch_compile: bool = False, use_sage_attention: bool = False) -> (list,):
# Start timing
start_time = time.time()
@@ -393,38 +340,8 @@ class DeepExColorImageNode:
# Declare globals first
global NONLOCAL_NET, COLOR_NET, VGG_NET
# Load feature encoder (may be different from VGG_NET)
encoder_net = None
encoder_name = feature_encoder
if feature_encoder == "vgg19":
encoder_net = VGG_NET
else:
# Try to load modern encoder (with auto-installation)
try:
# Auto-install dependencies if needed
from .auto_installer import ensure_dependencies_for_encoder
deps_ok = ensure_dependencies_for_encoder(feature_encoder)
if deps_ok:
from .models.feature_extractors import get_feature_encoder
encoder_net = get_feature_encoder(feature_encoder, device='cuda')
# Apply text guidance if using CLIP
if feature_encoder.startswith('clip') and text_guidance.strip():
if hasattr(encoder_net, 'set_text_guidance'):
encoder_net.set_text_guidance(text_guidance, weight=text_guidance_weight)
print(f"[DeepExColorImageNode] ✓ Text guidance: '{text_guidance}' (weight={text_guidance_weight})")
print(f"[DeepExColorImageNode] ✓ Using feature encoder: {feature_encoder}")
else:
raise ImportError(f"Dependencies for {feature_encoder} could not be installed")
except Exception as e:
print(f"[DeepExColorImageNode] Warning: Could not load {feature_encoder}: {e}")
print(f"[DeepExColorImageNode] Falling back to VGG19")
encoder_net = VGG_NET
encoder_name = "vgg19 (fallback)"
# DeepExemplar only uses VGG19 (trained baseline)
encoder_net = VGG_NET
# Apply optimizations to models
if use_torch_compile and hasattr(torch, 'compile'):
@@ -432,7 +349,7 @@ class DeepExColorImageNode:
# Get optimal compile settings based on system compatibility
compile_kwargs = get_torch_compile_kwargs()
if encoder_net == VGG_NET and not hasattr(VGG_NET, '_compiled'):
if not hasattr(VGG_NET, '_compiled'):
VGG_NET = torch.compile(VGG_NET, **compile_kwargs)
VGG_NET._compiled = True
if not hasattr(NONLOCAL_NET, '_compiled'):
@@ -455,8 +372,11 @@ class DeepExColorImageNode:
print("[DeepExColorImageNode] ✓ SageAttention enabled")
else:
NonlocalNet.USE_SAGE_ATTENTION = False
# Note: adjust_target_size expects (height, width); here we pass target_height, target_width.
final_h, final_w = adjust_target_size(target_height, target_width, 64, 64, 32)
# Adjust target size (VGG19 requires dimensions divisible by 32)
min_multiple = 32
final_h, final_w = adjust_target_size(target_height, target_width, 64, 64, min_multiple)
print(f"[DeepExColorImageNode] Adjusted size: ({target_width}, {target_height}) -> ({final_w}, {final_h})")
transform = build_test_py_transform((final_h, final_w))
src_pil = tensor_to_pil(image_to_colorize)
@@ -481,62 +401,6 @@ class DeepExColorImageNode:
out_np = batch_lab2rgb_transpose_mc(src_lab_full[:, 0:1, :, :], ab_up)
out_tensor = torch.from_numpy(out_np).float().div(255.0)
# Apply modern post-processing
post_proc_applied = "none"
if post_processor != "none" or (wls_filter_on and post_processor == "none"):
# Handle legacy WLS filter
if wls_filter_on and post_processor == "none":
post_processor = "wls"
print("[DeepExColorImageNode] Legacy wls_filter_on enabled, using post_processor='wls'")
try:
# Auto-install dependencies if needed
from .auto_installer import ensure_dependencies_for_post_processor
deps_ok = ensure_dependencies_for_post_processor(post_processor)
if not deps_ok:
print(f"[DeepExColorImageNode] Warning: Dependencies for {post_processor} could not be installed")
proc = None
else:
from .post_processing import get_post_processor
# Create post-processor
if post_processor == "wls":
proc = get_post_processor('wls', lambda_value=lambda_value, sigma_color=sigma_color)
elif post_processor == "color_matcher":
proc = get_post_processor('color_matcher', method='mkl')
elif post_processor == "guided":
proc = get_post_processor('guided', radius=8, eps=0.01)
elif post_processor == "bilateral":
proc = get_post_processor('bilateral', d=9, sigma_color=75, sigma_space=75)
else:
proc = None
if proc is not None:
# Convert tensor to [H, W, 3] format
img_hwc = out_tensor.squeeze(0) if out_tensor.dim() == 4 else out_tensor
# Apply post-processing
if post_processor == "color_matcher":
# color_matcher needs reference image
ref_pil_for_pp = tensor_to_pil(reference_image)
ref_rgb = torch.from_numpy(np.array(ref_pil_for_pp)).float().div(255.0)
processed = proc.match_to_reference(img_hwc, ref_rgb)
# Apply strength blending
if post_process_strength < 1.0:
processed = img_hwc * (1 - post_process_strength) + processed * post_process_strength
else:
processed = proc.match_to_reference(img_hwc, None)
out_tensor = processed.unsqueeze(0) if processed.dim() == 3 else processed
post_proc_applied = post_processor
print(f"[DeepExColorImageNode] ✓ Post-processing applied: {post_processor}")
except Exception as e:
print(f"[DeepExColorImageNode] Warning: Post-processing '{post_processor}' failed: {e}")
print(f"[DeepExColorImageNode] Continuing without post-processing...")
# Ensure out_tensor has batch dimension
if out_tensor.dim() == 3:
out_tensor = out_tensor.unsqueeze(0)
@@ -553,15 +417,7 @@ class DeepExColorImageNode:
f"Date/Time: {timestamp}\n"
f"Resolution: {final_w}x{final_h}\n"
f"Total Time: {elapsed_time:.3f} seconds\n"
f"Feature Encoder: {encoder_name}\n"
f"Post-Processor: {post_proc_applied}\n"
)
if text_guidance.strip() and encoder_name.startswith('clip'):
report += f"Text Guidance: '{text_guidance}' (weight={text_guidance_weight})\n"
if post_proc_applied in ["wls"]:
report += f"Lambda: {lambda_value}, Sigma Color: {sigma_color}\n"
if post_proc_applied == "color_matcher":
report += f"Matching Strength: {post_process_strength}\n"
report += (
f"Torch Compile: {use_torch_compile}\n"
f"SageAttention: {use_sage_attention}\n"
@@ -570,7 +426,7 @@ class DeepExColorImageNode:
print(f"[DeepExColorImageNode] Colorization complete in {elapsed_time:.3f}s")
return ([out_tensor], report)
return (out_tensor, report)
class DeepExColorVideoNode:
@classmethod
@@ -599,41 +455,6 @@ class DeepExColorVideoNode:
"max": 4096,
"tooltip": "Output height (will be adjusted to nearest multiple of 32)"
}),
"feature_encoder": (["vgg19", "dinov2_vits", "dinov2_vitb", "dinov2_vitl", "clip_vitb"], {
"default": "vgg19",
"tooltip": "Feature extraction model: vgg19 (fast, baseline), dinov2_vitb (recommended, 40-60% better), dinov2_vitl (best quality), clip_vitb (text-guided)"
}),
"post_processor": (["none", "wls", "guided", "bilateral", "color_matcher"], {
"default": "none",
"tooltip": "Post-processing method: none (fastest), wls (edge-aware), guided (fast smoothing), bilateral (classic), color_matcher (best color consistency, recommended)"
}),
"post_process_strength": ("FLOAT", {
"default": 0.8,
"min": 0.0,
"max": 1.0,
"step": 0.05,
"tooltip": "Post-processing strength: 0=disabled, 1=full effect (for color_matcher/guided/bilateral)"
}),
"temporal_consistency": ("BOOLEAN", {
"default": True,
"tooltip": "Enable temporal consistency in post-processing to reduce flickering between frames (for color_matcher)"
}),
"wls_filter_on": ("BOOLEAN", {
"default": False,
"tooltip": "[Legacy] Enable WLS filter (deprecated: use post_processor='wls' instead)"
}),
"lambda_value": ("FLOAT", {
"default": 500.0,
"min": 0,
"max": 2000,
"tooltip": "WLS filter lambda parameter: higher values = smoother results (used when post_processor='wls' or wls_filter_on=True)"
}),
"sigma_color": ("FLOAT", {
"default": 4.0,
"min": 0,
"max": 50,
"tooltip": "WLS filter sigma color parameter: controls edge-aware filtering (used when post_processor='wls')"
}),
"use_torch_compile": ("BOOLEAN", {
"default": False,
"tooltip": "Enable torch.compile optimization for 10-25% speedup (may increase first-run compilation time)"
@@ -642,20 +463,6 @@ class DeepExColorVideoNode:
"default": False,
"tooltip": "Enable SageAttention for faster attention computation (requires sageattention package)"
}),
},
"optional": {
"text_guidance": ("STRING", {
"default": "",
"multiline": False,
"tooltip": "Text prompt to guide colorization (only for clip_vitb encoder). Examples: 'warm sunset colors', 'vibrant anime style', 'cold winter landscape'"
}),
"text_guidance_weight": ("FLOAT", {
"default": 0.3,
"min": 0.0,
"max": 1.0,
"step": 0.05,
"tooltip": "How much the text guidance influences colorization (0=no effect, 1=maximum effect)"
}),
}
}
RETURN_TYPES = ("IMAGE", "STRING")
@@ -666,14 +473,7 @@ class DeepExColorVideoNode:
def colorize_video(self, video_frames: any, reference_image: torch.Tensor,
frame_propagate: bool, use_half_resolution: bool,
target_width: int, target_height: int,
feature_encoder: str = "vgg19",
post_processor: str = "none",
post_process_strength: float = 0.8,
temporal_consistency: bool = True,
wls_filter_on: bool = False,
lambda_value: float = 500.0, sigma_color: float = 4.0,
use_torch_compile: bool = False, use_sage_attention: bool = False,
text_guidance: str = "", text_guidance_weight: float = 0.3) -> (list,):
use_torch_compile: bool = False, use_sage_attention: bool = False) -> (list,):
# Start timing
start_time = time.time()
@@ -682,38 +482,8 @@ class DeepExColorVideoNode:
# Declare globals first
global NONLOCAL_NET, COLOR_NET, VGG_NET
# Load feature encoder (may be different from VGG_NET)
encoder_net = None
encoder_name = feature_encoder
if feature_encoder == "vgg19":
encoder_net = VGG_NET
else:
# Try to load modern encoder (with auto-installation)
try:
# Auto-install dependencies if needed
from .auto_installer import ensure_dependencies_for_encoder
deps_ok = ensure_dependencies_for_encoder(feature_encoder)
if deps_ok:
from .models.feature_extractors import get_feature_encoder
encoder_net = get_feature_encoder(feature_encoder, device='cuda')
# Apply text guidance if using CLIP
if feature_encoder.startswith('clip') and text_guidance.strip():
if hasattr(encoder_net, 'set_text_guidance'):
encoder_net.set_text_guidance(text_guidance, weight=text_guidance_weight)
print(f"[DeepExColorVideoNode] ✓ Text guidance: '{text_guidance}' (weight={text_guidance_weight})")
print(f"[DeepExColorVideoNode] ✓ Using feature encoder: {feature_encoder}")
else:
raise ImportError(f"Dependencies for {feature_encoder} could not be installed")
except Exception as e:
print(f"[DeepExColorVideoNode] Warning: Could not load {feature_encoder}: {e}")
print(f"[DeepExColorVideoNode] Falling back to VGG19")
encoder_net = VGG_NET
encoder_name = "vgg19 (fallback)"
# DeepExemplar only uses VGG19 (trained baseline)
encoder_net = VGG_NET
# Apply optimizations to models
if use_torch_compile and hasattr(torch, 'compile'):
@@ -721,7 +491,7 @@ class DeepExColorVideoNode:
# Get optimal compile settings based on system compatibility
compile_kwargs = get_torch_compile_kwargs()
if encoder_net == VGG_NET and not hasattr(VGG_NET, '_compiled'):
if not hasattr(VGG_NET, '_compiled'):
VGG_NET = torch.compile(VGG_NET, **compile_kwargs)
VGG_NET._compiled = True
if not hasattr(NONLOCAL_NET, '_compiled'):
@@ -744,10 +514,18 @@ class DeepExColorVideoNode:
print("[DeepExColorVideoNode] ✓ SageAttention enabled")
else:
NonlocalNet.USE_SAGE_ATTENTION = False
final_h, final_w = adjust_target_size(target_height, target_width, 64, 64, 32)
# Adjust target size (VGG19 requires dimensions divisible by 32)
min_multiple = 32
final_h, final_w = adjust_target_size(target_height, target_width, 64, 64, min_multiple)
print(f"[DeepExColorVideoNode] Adjusted size: ({target_width}, {target_height}) -> ({final_w}, {final_h})")
if wls_filter_on and not WLS_FILTER_AVAILABLE:
print("[DeepExColorVideoNode] Warning: 'createFastGlobalSmootherFilter' not available; skipping filtering. To fix, install or upgrade opencv-contrib-python (e.g., 'pip install --upgrade opencv-contrib-python') and restart ComfyUI.")
# Warn about torch.compile warmup on first run
if use_torch_compile:
print(f"[DeepExColorVideoNode] Note: First frame will take 30-60s for torch.compile optimization")
print(f"[DeepExColorVideoNode] Subsequent frames will be 15-25% faster")
if isinstance(video_frames, list):
frames_list = video_frames
elif isinstance(video_frames, torch.Tensor):
@@ -775,6 +553,10 @@ class DeepExColorVideoNode:
num_frames = len(frames_list)
gui_pbar = ProgressBar(num_frames)
console_pbar = console_tqdm(total=num_frames, desc="[DeepExColorVideoNode] Console Progress")
if use_torch_compile:
print(f"[DeepExColorVideoNode] Starting compilation on first frame...")
for i, frm in enumerate(frames_list):
frame_pil = tensor_to_pil(frm)
frm_lab_full = transform(frame_pil).unsqueeze(0).cuda()
@@ -813,9 +595,21 @@ class DeepExColorVideoNode:
else:
ab_up = I_ab.clone()
# Convert to RGB (do NOT apply post-processing per frame, will do batch processing later)
# Convert to RGB
out_np = batch_lab2rgb_transpose_mc(frm_lab_full[:, 0:1, :, :], ab_up)
if i == 0: # Debug first frame only
print(f"[DeepExColorVideoNode] DEBUG frame {i}: out_np shape before squeeze: {out_np.shape}")
# Ensure numpy array has shape (H, W, 3)
while out_np.ndim > 3:
out_np = out_np.squeeze(0)
if i == 0: # Debug first frame only
print(f"[DeepExColorVideoNode] DEBUG frame {i}: out_np shape after squeeze: {out_np.shape}")
# Convert to tensor with shape (H, W, C) for ComfyUI
out_t = torch.from_numpy(out_np).float().div(255.0)
if i == 0: # Debug first frame only
print(f"[DeepExColorVideoNode] DEBUG frame {i}: out_t shape: {out_t.shape}")
if use_torch_compile:
print(f"[DeepExColorVideoNode] ✓ Compilation complete! Processing remaining frames...")
out_frames.append(out_t)
gui_pbar.update_absolute(i+1, num_frames)
console_pbar.update(1)
@@ -823,70 +617,11 @@ class DeepExColorVideoNode:
self.set_progress(gui_pbar.progress)
console_pbar.close()
# Stack frames for batch post-processing
# Stack frames for batch output
out_tensor = torch.stack(out_frames, dim=0)
# Apply modern post-processing (batch processing for videos)
post_proc_applied = "none"
if post_processor != "none" or (wls_filter_on and post_processor == "none"):
# Handle legacy WLS filter
if wls_filter_on and post_processor == "none":
post_processor = "wls"
print("[DeepExColorVideoNode] Legacy wls_filter_on enabled, using post_processor='wls'")
try:
# Auto-install dependencies if needed
from .auto_installer import ensure_dependencies_for_post_processor
deps_ok = ensure_dependencies_for_post_processor(post_processor)
if not deps_ok:
print(f"[DeepExColorVideoNode] Warning: Dependencies for {post_processor} could not be installed")
proc = None
else:
from .post_processing import get_post_processor
# Create post-processor
if post_processor == "wls":
proc = get_post_processor('wls', lambda_value=lambda_value, sigma_color=sigma_color)
elif post_processor == "color_matcher":
proc = get_post_processor('color_matcher', method='mkl',
temporal_consistency=temporal_consistency,
consistency_weight=0.3)
elif post_processor == "guided":
proc = get_post_processor('guided', radius=8, eps=0.01)
elif post_processor == "bilateral":
proc = get_post_processor('bilateral', d=9, sigma_color=75, sigma_space=75)
else:
proc = None
if proc is not None:
print(f"[DeepExColorVideoNode] Applying post-processing: {post_processor} to {num_frames} frames...")
# Apply post-processing
if post_processor == "color_matcher":
# color_matcher for videos with reference matching
ref_pil_for_pp = tensor_to_pil(reference_image)
ref_rgb = torch.from_numpy(np.array(ref_pil_for_pp)).float().div(255.0)
# Process video with temporal consistency
processed = proc.match_video_frames(
out_tensor,
ref_rgb,
strength=post_process_strength,
progress_callback=lambda i, total: print(f" Post-processing: {i}/{total} frames", end='\r') if i % 10 == 0 else None
)
out_tensor = processed
else:
# Other post-processors: apply per-frame
processed_frames = proc.match_video_frames(out_tensor, None)
out_tensor = processed_frames
post_proc_applied = post_processor
print(f"\n[DeepExColorVideoNode] ✓ Post-processing complete: {post_processor}")
except Exception as e:
print(f"[DeepExColorVideoNode] Warning: Post-processing '{post_processor}' failed: {e}")
print(f"[DeepExColorVideoNode] Continuing without post-processing...")
print(f"[DeepExColorVideoNode] DEBUG: out_tensor shape: {out_tensor.shape}")
print(f"[DeepExColorVideoNode] DEBUG: out_tensor dtype: {out_tensor.dtype}")
print(f"[DeepExColorVideoNode] DEBUG: out_tensor min/max: {out_tensor.min():.3f}/{out_tensor.max():.3f}")
# Calculate timing
end_time = time.time()
@@ -904,16 +639,7 @@ class DeepExColorVideoNode:
f"Total Time: {elapsed_time:.2f} seconds\n"
f"Average FPS: {fps:.2f}\n"
f"Time per Frame: {elapsed_time / num_frames:.3f} seconds\n"
f"Feature Encoder: {encoder_name}\n"
f"Post-Processor: {post_proc_applied}\n"
)
if text_guidance.strip() and encoder_name.startswith('clip'):
report += f"Text Guidance: '{text_guidance}' (weight={text_guidance_weight})\n"
if post_proc_applied in ["wls"]:
report += f"Lambda: {lambda_value}, Sigma Color: {sigma_color}\n"
if post_proc_applied == "color_matcher":
report += f"Matching Strength: {post_process_strength}\n"
report += f"Temporal Consistency: {'Enabled' if temporal_consistency else 'Disabled'}\n"
report += (
f"Frame Propagation: {'Enabled' if frame_propagate else 'Disabled'}\n"
f"Half Resolution: {'Enabled' if use_half_resolution else 'Disabled'}\n"
@@ -924,7 +650,7 @@ class DeepExColorVideoNode:
print(f"[DeepExColorVideoNode] Processed {num_frames} frames in {elapsed_time:.2f}s ({fps:.2f} FPS)")
return ([out_tensor], report)
return (out_tensor, report)
NODE_CLASS_MAPPINGS = {
"DeepExColorImageNode": DeepExColorImageNode,
-283
View File
@@ -1,283 +0,0 @@
# ✅ Repository Ready for GitHub Push!
**All GitHub URLs updated to:** https://github.com/jonstreeter/ComfyUI-Reference-Based-Video-Colorization
---
## 📝 Files Updated with Correct GitHub URL
### Documentation Files
✅ **README.md** - Updated 2 occurrences
- Line 61: git clone command
- Line 349: existing issues link
✅ **CHANGELOG.md** - Updated 1 occurrence
- Line 173: commit history link
✅ **README_NEW.md** - Updated 3 occurrences
- Line 53: git clone command
- Line 307: GitHub Issues link
- Line 308: GitHub Discussions link
✅ **RELEASE_NOTES_v2.0.0.md** - Updated 5 occurrences
- Line 131: releases page link
- Line 184: git clone command
- Line 237: Full Changelog link
- Line 258: ComfyUI Manager reference
- Line 335: Discord announcement link
All placeholder URLs (YOUR_USERNAME, yourusername) have been replaced with **jonstreeter**
---
## 🎯 Next Steps - Push to GitHub
### Step 1: Review Changes
```bash
cd "G:\AIART\AI_Image_Generators\Comfy_UI_V42\ComfyUI\custom_nodes\ComfyUI-Reference-Based-Video-Colorization"
# See what will be committed
git status
# Review specific file changes
git diff README.md
git diff CHANGELOG.md
```
### Step 2: Add Files to Git
```bash
# Add all modified files
git add DeepExemplarColorizationNodes.py
git add README.md
git add __init__.py
git add models/
git add requirements.txt
# Add new core components
git add nodes.py
git add auto_installer.py
git add colormnet/
git add core/
git add post_processing/
git add models/feature_extractors/
# Add documentation
git add CHANGELOG.md
git add ARCHITECTURE.md
git add PERFORMANCE_OPTIMIZATIONS.md
git add MIGRATION_GUIDE.md
git add QUICKSTART.md
git add MODERN_COMPONENTS_GUIDE.md
git add COLORMNET_INTEGRATION_COMPLETE.md
git add INTEGRATION_COMPLETE.md
git add SILENT_AUTO_INSTALLER_COMPLETE.md
# Add workflows
git add Workflows/ColorizeVideoWorkflow.png
git add "Workflows/Colorize Video Workflow.json"
# Optional: Add technical docs (if you want them in the repo)
git add AUTO_INSTALLER_README.md
git add INTEGRATION_SUMMARY.md
git add MODULAR_ARCHITECTURE.md
git add PERFORMANCE.md
git add RELEASE_NOTES_v2.0.0.md
# Remove deleted file
git rm "Workflows/Basic Video Colorization Workflow.png"
```
**Note:** You may NOT want to add:
- `*_old_backup.py` files (backup files)
- `*_new.py` files (backup files)
- `requirements_old_backup.txt` (backup)
- `.claude/` directory (development files)
- `nul` file (Windows temp file)
- `dataset/`, `inference/`, `model/`, `util/` directories (if they contain original research code not needed for ComfyUI)
### Step 3: Create Commit
```bash
# Create comprehensive commit
git commit -m "Major update: Modern components integration with DINOv2, CLIP, and auto-installer
Features:
- Add modern feature encoders (DINOv2, CLIP) as alternatives to VGG19
- Add post-processing pipeline (color-matcher, WLS, guided, bilateral)
- Add silent auto-installer for dependencies (timm, CLIP, color-matcher)
- Add ColorMNet integration with modern components
- Add comprehensive tooltips to all node parameters
Fixes:
- Fix torch.compile CUDA graphs compatibility
- Fix ColorMNet model property assignment issue
- Remove unsupported disable_cudagraphs parameter
- Add proper error handling and graceful fallbacks
Documentation:
- Add PERFORMANCE_OPTIMIZATIONS.md guide
- Add MODERN_COMPONENTS_GUIDE.md
- Add COLORMNET_INTEGRATION_COMPLETE.md
- Add INTEGRATION_COMPLETE.md
- Add SILENT_AUTO_INSTALLER_COMPLETE.md
- Update all GitHub URLs to jonstreeter profile
All URLs updated to: https://github.com/jonstreeter/ComfyUI-Reference-Based-Video-Colorization"
```
### Step 4: Push to GitHub
```bash
# Push to main branch
git push origin main
# If you want to create a tag for this version
git tag -a v2.1.0 -m "Modern components integration with DINOv2, CLIP, and auto-installer"
git push origin v2.1.0
```
---
## 📊 Current Repository Status
### Modified Files (10)
1. DeepExemplarColorizationNodes.py - torch.compile fixes
2. README.md - Updated GitHub URLs
3. __init__.py - Version updates
4. models/ContextualLoss.py
5. models/FrameColor.py
6. models/GAN_models.py
7. models/NonlocalNet.py
8. models/vgg19_gray.py
9. requirements.txt - Modern dependencies
### New Files (Major Components)
- **nodes.py** - ColorMNet nodes with modern components
- **auto_installer.py** - Silent dependency installer
- **colormnet/** - ColorMNet implementation
- **core/** - Core utilities (device, logger, validation)
- **post_processing/** - Post-processing pipeline
- **models/feature_extractors/** - DINOv2, CLIP encoders
### New Documentation (11 files)
1. CHANGELOG.md
2. ARCHITECTURE.md
3. PERFORMANCE_OPTIMIZATIONS.md
4. MIGRATION_GUIDE.md
5. QUICKSTART.md
6. MODERN_COMPONENTS_GUIDE.md
7. COLORMNET_INTEGRATION_COMPLETE.md
8. INTEGRATION_COMPLETE.md
9. SILENT_AUTO_INSTALLER_COMPLETE.md
10. AUTO_INSTALLER_README.md
11. INTEGRATION_SUMMARY.md
### New Workflows
- Workflows/ColorizeVideoWorkflow.png
- Workflows/Colorize Video Workflow.json
---
## ⚠️ Files to Exclude (Recommended)
Don't commit these backup/temp files:
```bash
# Add to .gitignore
echo "# Backup files" >> .gitignore
echo "*_old_backup.py" >> .gitignore
echo "*_new.py" >> .gitignore
echo "*_backup.txt" >> .gitignore
echo "requirements_old_backup.txt" >> .gitignore
echo "requirements_new.txt" >> .gitignore
echo ".claude/" >> .gitignore
echo "nul" >> .gitignore
echo "README_NEW.md" >> .gitignore
git add .gitignore
```
---
## 🎯 Quick Push Commands (Copy-Paste Ready)
```bash
# Navigate to repository
cd "G:\AIART\AI_Image_Generators\Comfy_UI_V42\ComfyUI\custom_nodes\ComfyUI-Reference-Based-Video-Colorization"
# Create .gitignore for backup files
echo "*_old_backup.py" >> .gitignore
echo "*_new.py" >> .gitignore
echo "*_backup.txt" >> .gitignore
echo ".claude/" >> .gitignore
echo "nul" >> .gitignore
echo "README_NEW.md" >> .gitignore
# Add essential files
git add DeepExemplarColorizationNodes.py README.md __init__.py models/ requirements.txt
git add nodes.py auto_installer.py colormnet/ core/ post_processing/ models/feature_extractors/
git add CHANGELOG.md ARCHITECTURE.md PERFORMANCE_OPTIMIZATIONS.md MIGRATION_GUIDE.md QUICKSTART.md
git add MODERN_COMPONENTS_GUIDE.md COLORMNET_INTEGRATION_COMPLETE.md INTEGRATION_COMPLETE.md SILENT_AUTO_INSTALLER_COMPLETE.md
git add Workflows/ .gitignore
git rm "Workflows/Basic Video Colorization Workflow.png"
# Create commit
git commit -m "Major update: Modern components integration with DINOv2, CLIP, and auto-installer
Features:
- Add modern feature encoders (DINOv2, CLIP)
- Add post-processing pipeline (color-matcher, WLS, guided, bilateral)
- Add silent auto-installer for dependencies
- Add ColorMNet integration with modern components
- Add comprehensive tooltips
Fixes:
- Fix torch.compile CUDA graphs compatibility
- Fix ColorMNet model property assignment
- Remove unsupported disable_cudagraphs parameter
Documentation:
- Add comprehensive guides and documentation
- Update all GitHub URLs to jonstreeter profile"
# Push to GitHub
git push origin main
```
---
## ✅ Verification Checklist
Before pushing:
- [x] All GitHub URLs updated to jonstreeter
- [x] No placeholder URLs (YOUR_USERNAME, yourusername) remain
- [x] Documentation files updated
- [x] Workflow files ready
- [ ] .gitignore created for backup files
- [ ] Files added to git staging
- [ ] Commit message prepared
- [ ] Ready to push
After pushing:
- [ ] Verify repository on GitHub
- [ ] Check that all files uploaded correctly
- [ ] Verify README displays correctly
- [ ] Test git clone command works
- [ ] Create GitHub release (optional)
---
## 🎉 Summary
**Repository URL:** https://github.com/jonstreeter/ComfyUI-Reference-Based-Video-Colorization
All documentation and references now point to your GitHub profile. The repository includes:
- Modern components integration (DINOv2, CLIP)
- Auto-installer for dependencies
- ColorMNet with modern features
- Comprehensive documentation
- Example workflows
Ready to push to GitHub! 🚀
-309
View File
@@ -1,309 +0,0 @@
# 🎉 Modular Components - Integration Complete!
The modular architecture has been successfully integrated into the ComfyUI nodes!
---
## ✅ What's New
### 1. **Modern Feature Encoders Available**
- **VGG19** (baseline, fast)
- **DINOv2-ViT-S** (small, 20-30% better)
- **DINOv2-ViT-B** (recommended, 40-60% better) ⭐
- **DINOv2-ViT-L** (best quality, slower)
- **CLIP-ViT-B** (text-guided colorization) 🎨
### 2. **Modern Post-Processing Options**
- **none** (fastest, no post-processing)
- **wls** (Weighted Least Squares, edge-aware)
- **guided** (fast edge-preserving smoothing)
- **bilateral** (classic bilateral filter)
- **color_matcher** (best color consistency) ⭐
### 3. **New Node Parameters**
#### DeepExColorImageNode & DeepExColorVideoNode:
- `feature_encoder` - Select which AI model to use for feature extraction
- `post_processor` - Select which post-processing method to apply
- `post_process_strength` - Control intensity of post-processing (0-1)
- `text_guidance` (optional) - Text prompt for CLIP encoder
- `text_guidance_weight` (optional) - How much text influences colors (0-1)
#### DeepExColorVideoNode only:
- `temporal_consistency` - Reduce flickering between frames (for color_matcher)
---
## 🚀 Quick Start Guide
### Automatic Installation (Recommended) ⭐
**Dependencies are now installed automatically!**
When you select a modern component (DINOv2, CLIP, color-matcher, etc.) for the first time, the node will:
1. Detect missing dependencies
2. Automatically install them via pip
3. Load the component
4. Show installation progress in the console
**No manual installation needed!** Just select the component you want to use.
### Manual Installation (Optional)
If you prefer to install everything upfront:
**For DINOv2 (recommended):**
```bash
pip install timm
```
**For color-matcher post-processing (recommended):**
```bash
pip install color-matcher
```
**For CLIP text-guided colorization:**
```bash
pip install git+https://github.com/openai/CLIP.git
```
**Or install everything at once:**
```bash
pip install -r requirements_modern.txt
```
Or run the installer script:
```bash
python auto_installer.py
```
### Using the Nodes
**Minimal Setup (No changes needed):**
- Just use the nodes as before with default settings
- `feature_encoder: vgg19`
- `post_processor: none`
- Everything works exactly as before!
**Recommended Upgrade (Better quality):**
1. In the node settings, change:
- `feature_encoder: dinov2_vitb`
- `post_processor: color_matcher`
- `post_process_strength: 0.8`
2. On first run, dependencies auto-install (watch console)
3. Enjoy 40-60% better colorization!
**Text-Guided Colorization (Creative control):**
1. In the node settings, change:
- `feature_encoder: clip_vitb`
- `text_guidance: "warm sunset colors"`
- `text_guidance_weight: 0.3`
- `post_processor: color_matcher`
2. On first run, CLIP auto-installs (watch console)
3. Experiment with different text prompts!
---
## 📊 Performance Comparison
### Image Colorization (512x512)
| Configuration | Quality | Speed | Use Case |
|--------------|---------|-------|----------|
| VGG19 + none | ⭐⭐⭐ | ⚡⚡⚡⚡⚡ | Fast preview |
| VGG19 + color_matcher | ⭐⭐⭐⭐ | ⚡⚡⚡⚡ | Better colors, same speed |
| DINOv2-ViT-B + none | ⭐⭐⭐⭐ | ⚡⚡⚡⚡ | Better semantic matching |
| **DINOv2-ViT-B + color_matcher** | ⭐⭐⭐⭐⭐ | ⚡⚡⚡ | **Best overall** ⭐ |
| CLIP + color_matcher + text | ⭐⭐⭐⭐⭐ | ⚡⚡⚡ | Creative control |
### Video Colorization (30 frames, 512x512)
| Configuration | Consistency | Speed | Use Case |
|--------------|-------------|-------|----------|
| VGG19 + none | ⭐⭐⭐ | ⚡⚡⚡⚡⚡ | Fast preview |
| VGG19 + color_matcher + temporal | ⭐⭐⭐⭐⭐ | ⚡⚡⚡⚡ | Best consistency |
| **DINOv2-ViT-B + color_matcher + temporal** | ⭐⭐⭐⭐⭐ | ⚡⚡⚡ | **Best quality** ⭐ |
---
## 💡 Practical Examples
### Example 1: Basic Quality Upgrade
**Before:**
```
feature_encoder: vgg19
post_processor: none
wls_filter_on: true
```
**After:**
```
feature_encoder: dinov2_vitb
post_processor: color_matcher
post_process_strength: 0.8
wls_filter_on: false (deprecated)
```
**Result:** 40-60% better semantic matching + better color consistency
---
### Example 2: Text-Guided Artistic Colorization
**Settings:**
```
feature_encoder: clip_vitb
post_processor: color_matcher
text_guidance: "vibrant anime style colors with high saturation"
text_guidance_weight: 0.4
post_process_strength: 0.8
```
**Text Prompt Examples:**
- `"warm sunset golden hour lighting"`
- `"cold winter blue tones"`
- `"vibrant 1980s neon colors"`
- `"muted vintage photograph from 1960s"`
- `"sunny beach scene with bright colors"`
- `"moody noir black and white film"`
---
### Example 3: Video with Maximum Consistency
**Settings:**
```
feature_encoder: dinov2_vitb
post_processor: color_matcher
post_process_strength: 0.8
temporal_consistency: true
frame_propagate: true
```
**Result:** Best video quality with minimal flickering
---
## 🔧 Troubleshooting
### Auto-installation not working
**Check console output:**
The node logs installation progress to the console. Look for messages like:
```
[AutoInstall] Installing timm (DINOv2 support)...
[AutoInstall] ✓ timm installed successfully
```
**If auto-install fails:**
- Ensure you have internet connection
- Check if pip is working: `pip --version`
- Manually install: `pip install timm` (or relevant package)
- Check console for specific error messages
**Permissions issues (Linux/Mac):**
If you see permission errors, try:
```bash
pip install --user timm
# or
sudo pip install timm
```
### Nodes revert to VGG19
If a modern encoder fails to load, nodes automatically fall back to VGG19. Check the console for error messages:
```
[DeepExColorImageNode] Warning: Could not load dinov2_vitb: ...
[DeepExColorImageNode] Falling back to VGG19
```
This is expected behavior when dependencies are missing or installation fails.
### CUDA out of memory
**Solutions:**
1. Use smaller encoder: `dinov2_vits` instead of `dinov2_vitb`
2. Keep using `vgg19`
3. Reduce image/video resolution
4. Enable `use_half_resolution: true` for videos
### Slow performance
**Speed optimizations:**
1. Use `feature_encoder: vgg19` (fastest)
2. Use `post_processor: guided` or `none` (faster than color_matcher)
3. Enable `use_half_resolution: true` for videos
4. Enable `use_torch_compile: true` (10-25% speedup after warmup)
---
## 📁 Architecture Documentation
For developers and contributors:
- **Component Architecture:** [MODULAR_ARCHITECTURE.md](MODULAR_ARCHITECTURE.md)
- **Modern Components Guide:** [MODERN_COMPONENTS_GUIDE.md](MODERN_COMPONENTS_GUIDE.md)
- **Feature Extractors README:** [models/feature_extractors/README.md](models/feature_extractors/README.md)
---
## 🎯 Recommended Configurations
### For Best Quality (Slow):
```
feature_encoder: dinov2_vitl
post_processor: color_matcher
post_process_strength: 0.8
temporal_consistency: true (videos)
use_torch_compile: false
```
### For Balanced Quality/Speed (Recommended):
```
feature_encoder: dinov2_vitb
post_processor: color_matcher
post_process_strength: 0.8
temporal_consistency: true (videos)
use_torch_compile: true
```
### For Speed (Fast Preview):
```
feature_encoder: vgg19
post_processor: none
use_half_resolution: true (videos)
use_torch_compile: true
```
### For Creative Control:
```
feature_encoder: clip_vitb
post_processor: color_matcher
text_guidance: "your creative prompt here"
text_guidance_weight: 0.3
post_process_strength: 0.8
```
---
## 📝 Notes
- **Backward Compatible:** All existing workflows continue to work with default settings
- **Graceful Fallback:** If modern components aren't installed, nodes automatically use VGG19
- **Legacy Support:** Old `wls_filter_on` parameter still works (auto-converts to `post_processor: wls`)
- **Performance Reports:** Now include encoder and post-processor information
---
## 🙏 Credits
- **DINOv2:** Meta AI Research (2023)
- **CLIP:** OpenAI (2021)
- **color-matcher:** hahnec @ GitHub
- **Original Deep Exemplar:** Zhang et al. (2019)
---
**Enjoy the upgraded video colorization! 🎨**
For questions or issues, check the documentation or create an issue on GitHub.
-378
View File
@@ -1,378 +0,0 @@
# Integration Summary - Modular Components
**Date:** 2025-11-14
**Status:** ✅ Complete
---
## 📦 What Was Integrated
The modular architecture components have been fully integrated into the ComfyUI nodes, making modern AI models and post-processing techniques accessible through the user interface.
**Key Feature: Silent Auto-Installer** - Dependencies are now automatically installed when needed, no manual setup required!
---
## 🔧 Changes Made
### 1. **auto_installer.py** - Silent Dependency Installer (NEW)
Automatically detects and installs missing dependencies:
- `ensure_timm()` - Auto-installs timm for DINOv2
- `ensure_clip()` - Auto-installs CLIP from GitHub
- `ensure_color_matcher()` - Auto-installs color-matcher
- `ensure_opencv_contrib()` - Auto-installs opencv-contrib-python
- `ensure_dependencies_for_encoder()` - Smart installer for any encoder
- `ensure_dependencies_for_post_processor()` - Smart installer for any post-processor
**Features:**
- Silent installation with progress logging
- Caching to avoid repeated attempts
- Graceful failure handling
- Can be run standalone: `python auto_installer.py`
### 2. **DeepExemplarColorizationNodes.py** - Both Image & Video Nodes
#### New Input Parameters:
**Feature Encoder Selection:**
- `feature_encoder`: Dropdown with options:
- `vgg19` (baseline, default)
- `dinov2_vits` (small, faster)
- `dinov2_vitb` (recommended)
- `dinov2_vitl` (best quality)
- `clip_vitb` (text-guided)
**Post-Processing Selection:**
- `post_processor`: Dropdown with options:
- `none` (no post-processing, default)
- `wls` (Weighted Least Squares)
- `guided` (Guided Filter)
- `bilateral` (Bilateral Filter)
- `color_matcher` (Color Matcher - recommended)
**Post-Processing Control:**
- `post_process_strength`: Float slider (0.0-1.0, default 0.8)
- Controls intensity of post-processing
- 0 = disabled, 1 = full effect
**Text Guidance (Optional):**
- `text_guidance`: String input
- Text prompt for CLIP encoder
- Only active when `feature_encoder = clip_vitb`
- `text_guidance_weight`: Float slider (0.0-1.0, default 0.3)
- Controls influence of text on colorization
**Video-Specific:**
- `temporal_consistency`: Boolean (default True)
- Enables temporal smoothing in color_matcher
- Reduces flickering between frames
**Legacy Support:**
- `wls_filter_on`: Boolean (default False)
- Kept for backward compatibility
- Auto-converts to `post_processor: wls`
- Marked as [Legacy] in tooltip
---
### 3. **Implementation Details**
#### Auto-Installation Flow:
```python
# When user selects modern encoder
from .auto_installer import ensure_dependencies_for_encoder
deps_ok = ensure_dependencies_for_encoder(feature_encoder)
# If dependencies missing, auto-installs them
# [AutoInstall] Installing timm (DINOv2 support)...
# [AutoInstall] ✓ timm installed successfully
if deps_ok:
# Load the encoder
encoder_net = get_feature_encoder(feature_encoder, device='cuda')
else:
# Fall back to VGG19
encoder_net = VGG_NET
```
#### Feature Encoder Loading:
```python
# Dynamic encoder selection with auto-install
if feature_encoder == "vgg19":
encoder_net = VGG_NET
else:
# Auto-install dependencies if needed
ensure_dependencies_for_encoder(feature_encoder)
from .models.feature_extractors import get_feature_encoder
encoder_net = get_feature_encoder(feature_encoder, device='cuda')
# Apply text guidance for CLIP
if feature_encoder.startswith('clip') and text_guidance.strip():
encoder_net.set_text_guidance(text_guidance, weight=text_guidance_weight)
```
#### Post-Processing Pipeline:
```python
# Create post-processor based on selection
if post_processor == "color_matcher":
proc = get_post_processor('color_matcher', method='mkl',
temporal_consistency=temporal_consistency)
# Apply to image/video
processed = proc.match_to_reference(image, reference)
# or
processed = proc.match_video_frames(frames, reference, strength=post_process_strength)
```
#### Graceful Fallback:
- If modern encoder fails to load → Falls back to VGG19
- If post-processor fails → Continues without post-processing
- Logs clear error messages to console
---
### 3. **Performance Reports Updated**
**New Fields Added:**
- `Feature Encoder: {encoder_name}`
- `Post-Processor: {post_proc_applied}`
- `Text Guidance: '{text}'` (when applicable)
- `Matching Strength: {strength}` (for color_matcher)
- `Temporal Consistency: Enabled/Disabled` (for videos)
**Old Fields Removed:**
- `WLS Filter: Enabled/Disabled` (replaced by Post-Processor)
---
### 4. **All Tooltips Updated**
Added comprehensive tooltips to all new parameters:
- **feature_encoder**: Explains each model option with quality/speed indicators
- **post_processor**: Explains each method with use case recommendations
- **post_process_strength**: Explains 0-1 scale
- **text_guidance**: Provides example prompts
- **text_guidance_weight**: Explains influence scale
- **temporal_consistency**: Explains flicker reduction
---
## 🎯 User Experience Improvements
### Auto-Installation:
✅ **Zero-configuration setup** ⭐
- Dependencies install automatically when needed
- No manual pip commands required
- Silent installation with progress logging
- One-time setup per component
- Console shows installation progress
### Backward Compatibility:
✅ **100% backward compatible**
- Default settings (`vgg19` + `none`) match original behavior
- Existing workflows continue to work without changes
- Legacy `wls_filter_on` parameter still functions
### Progressive Enhancement:
✅ **Opt-in modernization**
- Users can keep using VGG19 if they want
- Modern components only activate when selected
- No dependencies required for basic usage
- Auto-install happens on first use only
### Error Handling:
✅ **Graceful degradation**
- Missing dependencies → Auto-install → If fails, fall back to VGG19
- Failed post-processing → Continue without it
- Clear console messages explain what happened
- Installation errors don't crash the node
### Discovery:
✅ **Tooltips guide users**
- Each parameter explains what it does
- Recommendations clearly marked (⭐)
- Examples provided for text guidance
- No need to read documentation to get started
---
## 📊 Performance Impact
### Baseline (VGG19 + none):
- **No change** from original implementation
- Same speed and quality as before
### Recommended (DINOv2-ViT-B + color_matcher):
- **Quality:** +40-60% better semantic matching
- **Consistency:** +25-50% better color matching
- **Speed:** ~30% slower (still real-time for most use cases)
### Text-Guided (CLIP + color_matcher + text):
- **Quality:** Artistic control via text prompts
- **Speed:** Similar to DINOv2
- **Use Case:** Creative colorization
---
## 🧪 Testing Status
### Syntax Validation:
✅ **Passed** - `python -m py_compile` successful
### Expected Behavior:
✅ **Default mode** - Works exactly as before (VGG19 + none)
✅ **Modern mode** - Loads DINOv2/CLIP when selected
✅ **Fallback** - Reverts to VGG19 on errors
✅ **Post-processing** - Applies color_matcher when selected
✅ **Legacy** - Old wls_filter_on still works
### Requires User Testing:
⏳ **Runtime testing** with actual videos
⏳ **Dependency installation** verification
⏳ **Performance benchmarking** on real hardware
---
## 📚 Documentation Created
### User-Facing:
1. **INTEGRATION_COMPLETE.md** - Quick start guide
- Installation instructions
- Practical examples
- Recommended configurations
- Troubleshooting guide
2. **MODERN_COMPONENTS_GUIDE.md** - Comprehensive guide
- Component overview
- Performance comparisons
- Usage examples
- Detailed documentation
### Developer-Facing:
3. **MODULAR_ARCHITECTURE.md** - Architecture documentation
- Complete component mapping
- Modern alternatives for each component
- Implementation priorities
- Future roadmap
4. **models/feature_extractors/README.md** - Encoder documentation
- Technical details for each encoder
- Performance characteristics
- Implementation notes
---
## 🔄 Migration Path
### Current Users:
**No action required** - Everything works as before
### Users Wanting Better Quality:
1. Change settings in ComfyUI node:
- `feature_encoder: dinov2_vitb`
- `post_processor: color_matcher`
2. Run the workflow (dependencies auto-install on first use)
3. Enjoy improved results!
**No manual installation needed!** The node handles everything automatically.
### Users Wanting Creative Control:
1. Change settings in ComfyUI node:
- `feature_encoder: clip_vitb`
- `text_guidance: "your creative prompt"`
- `post_processor: color_matcher`
2. Run the workflow (CLIP auto-installs on first use)
3. Experiment with prompts!
**No manual installation needed!** Just select the options and run.
---
## 🐛 Known Limitations
### Dependencies:
- **Auto-installation:** Dependencies install automatically on first use
- **Internet required:** First-time use requires internet connection
- **Installation time:** First-run takes 1-3 minutes to install packages
- **Model downloads:** DINOv2 (~300MB) and CLIP (~350MB) auto-download on first use
- **Permissions:** May need admin/sudo on some systems (rare)
### Performance:
- Modern encoders are ~30-40% slower than VGG19
- Post-processing adds ~20-30% overhead
- Recommended for quality over speed scenarios
### Compatibility:
- Requires PyTorch 2.0+ for optimal performance
- CUDA GPU recommended (CPU works but slow)
- Windows paths may need adjustment in some cases
---
## ✅ Integration Checklist
- [x] Add feature encoder selection to IMAGE node
- [x] Add feature encoder selection to VIDEO node
- [x] Add post-processor selection to IMAGE node
- [x] Add post-processor selection to VIDEO node
- [x] Add text guidance parameters (optional)
- [x] Add post-processing strength control
- [x] Add temporal consistency toggle (video)
- [x] **Create silent auto-installer** ⭐
- [x] **Integrate auto-installer into IMAGE node** ⭐
- [x] **Integrate auto-installer into VIDEO node** ⭐
- [x] Update performance reports
- [x] Add comprehensive tooltips
- [x] Implement graceful fallback
- [x] Preserve backward compatibility
- [x] Create user documentation
- [x] Update documentation for auto-install
- [x] Create developer documentation
- [x] Syntax validation passed
- [ ] Runtime testing (requires user)
- [ ] Test auto-installer with real packages (requires user)
- [ ] Performance benchmarking (requires user)
- [ ] User feedback collection
---
## 🚀 Next Steps (User-Driven)
1. **User Testing**
- Test with actual videos and images
- Verify modern encoders load correctly
- Benchmark performance on real hardware
2. **Feedback Collection**
- Report any bugs or issues
- Suggest UX improvements
- Share quality comparisons
3. **Future Enhancements** (If requested)
- Add more encoder options (SigLIP, EVA02)
- Implement optical flow temporal (RAFT)
- Add refinement networks (NAFNet, Restormer)
- Batch processing optimizations
---
## 📞 Support
**Documentation:**
- Quick Start: [INTEGRATION_COMPLETE.md](INTEGRATION_COMPLETE.md)
- User Guide: [MODERN_COMPONENTS_GUIDE.md](MODERN_COMPONENTS_GUIDE.md)
- Architecture: [MODULAR_ARCHITECTURE.md](MODULAR_ARCHITECTURE.md)
**Troubleshooting:**
- Check console logs for error messages
- Verify dependencies installed correctly
- Confirm PyTorch and CUDA working
- Review INTEGRATION_COMPLETE.md troubleshooting section
---
**Integration completed successfully! 🎉**
The modular components are now fully accessible through ComfyUI with an intuitive interface, comprehensive tooltips, and graceful fallback handling.
-438
View File
@@ -1,438 +0,0 @@
## 🚀 Modern Components Guide
**Comprehensive guide to upgrading video colorization with state-of-the-art AI models**
---
## 📋 Table of Contents
1. [Quick Start](#quick-start)
2. [Component Overview](#component-overview)
3. [Installation Guide](#installation-guide)
4. [Usage Examples](#usage-examples)
5. [Performance Comparison](#performance-comparison)
6. [Troubleshooting](#troubleshooting)
---
## 🎯 Quick Start
### Minimal Setup (Original Functionality)
```bash
pip install -r requirements.txt
```
### Recommended Setup (Modern Features)
```bash
# Install modern components
pip install -r requirements_modern.txt
# Install DINOv2 for better feature extraction
pip install timm
# Install color-matcher for post-processing
pip install color-matcher
```
### Full Setup (All Features)
```bash
# Base + modern requirements
pip install -r requirements_modern.txt
# CLIP for text-guided colorization
pip install git+https://github.com/openai/CLIP.git
# Color matching
pip install color-matcher
```
---
## 🔧 Component Overview
### **1. Feature Extractors** (Semantic Understanding)
**What it does:** Extracts semantic features to understand "what" is in the image
**Options:**
| Model | Year | Quality | Speed | Best For |
|-------|------|---------|-------|----------|
| **VGG19** (baseline) | 2014 | ⭐⭐⭐ | ⚡⚡⚡⚡⚡ | Legacy compatibility |
| **DINOv2-ViT-B** ⭐ | 2023 | ⭐⭐⭐⭐⭐ | ⚡⚡⚡⚡ | **Best overall** |
| **CLIP-ViT-B** | 2021 | ⭐⭐⭐⭐ | ⚡⚡⚡⚡ | Text-guided color |
**Installation:**
```bash
# DINOv2 (recommended)
pip install timm
# CLIP (text-guided)
pip install git+https://github.com/openai/CLIP.git
```
**Usage:**
```python
from models.feature_extractors import get_feature_encoder
# Use DINOv2 instead of VGG19
encoder = get_feature_encoder('dinov2_vitb')
features = encoder(image, ["r12", "r22", "r32", "r42", "r52"])
# Use CLIP with text guidance
from models.feature_extractors.clip_encoder import CLIPEncoderWithTextGuidance
encoder = CLIPEncoderWithTextGuidance()
encoder.set_text_guidance("warm autumn colors", weight=0.3)
features = encoder(image, ["r12", "r22", "r32", "r42", "r52"])
```
---
### **2. Post-Processing** (Color Refinement)
**What it does:** Refines colors to match reference better and ensure consistency
**Options:**
| Method | Quality | Speed | Best For |
|--------|---------|-------|----------|
| **WLS Filter** (baseline) | ⭐⭐⭐⭐ | ⚡⚡⚡ | Edge-aware smoothing |
| **color-matcher MKL** ⭐ | ⭐⭐⭐⭐⭐ | ⚡⚡⚡⚡ | **Color consistency** |
| **color-matcher HM** | ⭐⭐⭐⭐⭐ | ⚡⚡⚡⚡ | Best overall |
| **Guided Filter** | ⭐⭐⭐⭐ | ⚡⚡⚡⚡⚡ | Fast smoothing |
**Installation:**
```bash
# color-matcher (recommended)
pip install color-matcher
# Advanced OpenCV for WLS/Guided
pip install opencv-contrib-python
```
**Usage:**
```python
from post_processing import get_post_processor
# Use color-matcher for better consistency
post_proc = get_post_processor('color_matcher', method='mkl')
refined = post_proc.match_to_reference(colorized, reference)
# For video: ensure temporal consistency
refined_video = post_proc.match_video_frames(
colorized_frames,
reference,
strength=0.8 # 0=off, 1=full matching
)
```
---
## 📦 Installation Guide
### Option 1: Minimal (Original Features Only)
```bash
cd ComfyUI/custom_nodes/ComfyUI-Reference-Based-Video-Colorization
pip install -r requirements.txt
```
**What you get:**
- ✅ Original VGG19/ResNet50 feature extraction
- ✅ Basic colorization
- ✅ WLS filter post-processing
---
### Option 2: Recommended (Modern Features)
```bash
# Install base requirements
pip install -r requirements.txt
# Add modern components
pip install timm color-matcher
# Optional: CLIP for text-guided
pip install git+https://github.com/openai/CLIP.git
```
**What you get:**
- ✅ Everything from Minimal
- ✅ **DINOv2** for 40-60% better semantic matching
- ✅ **color-matcher** for better color consistency
- ✅ **CLIP** for text-guided colorization (optional)
---
### Option 3: Selective (Choose What You Need)
**Just better feature extraction:**
```bash
pip install timm # For DINOv2
```
**Just better post-processing:**
```bash
pip install color-matcher
```
**Text-guided colorization:**
```bash
pip install git+https://github.com/openai/CLIP.git
```
---
## 💻 Usage Examples
### Example 1: Basic Upgrade (DINOv2 + color-matcher)
```python
# In DeepExemplarColorizationNodes.py
# OLD:
from .models.vgg19_gray import VGG19_pytorch
VGG_NET = VGG19_pytorch().cuda()
# NEW:
from .models.feature_extractors import get_feature_encoder
VGG_NET = get_feature_encoder('dinov2_vitb').cuda()
# OLD:
# No post-processing or WLS only
# NEW:
from post_processing import get_post_processor
post_proc = get_post_processor('color_matcher', method='mkl')
refined_frame = post_proc.match_to_reference(colorized_frame, reference)
```
**Expected improvement:**
- 40-60% better semantic matching
- More accurate color transfer
- Better consistency with reference
---
### Example 2: Text-Guided Colorization
```python
from models.feature_extractors.clip_encoder import CLIPEncoderWithTextGuidance
# Create encoder with text guidance
encoder = CLIPEncoderWithTextGuidance(model_name='ViT-B/16').cuda()
# Guide colorization with text
encoder.set_text_guidance(
"warm sunset colors with golden hour lighting",
weight=0.3 # How much to influence (0-1)
)
# Extract features (now influenced by text)
features = encoder(image, ["r12", "r22", "r32", "r42", "r52"])
# Continue with normal colorization...
```
**Use cases:**
- "vibrant anime style colors" → Saturated, vivid
- "vintage 1970s photograph" → Muted, warm tones
- "cold winter landscape" → Blue-tinted, cool
- "sunny beach scene" → Warm, bright colors
---
### Example 3: Video Post-Processing Pipeline
```python
from post_processing.color_matcher_post import ColorMatcherPost
# Create post-processor with temporal consistency
post_proc = ColorMatcherPost(
method='hm-mvgd-hm', # Best quality method
temporal_consistency=True, # Smooth between frames
consistency_weight=0.3 # Temporal smoothing strength
)
# Process entire video
refined_video = post_proc.match_video_frames(
colorized_frames, # [N, H, W, 3]
reference_image, # [H, W, 3]
strength=0.8, # How much to match reference
progress_callback=lambda i, total: print(f"{i}/{total}")
)
# Alternative: Match to first frame for consistency
refined_video = post_proc.match_video_to_first_frame(
colorized_frames,
strength=0.5 # Lighter matching
)
```
---
## 📊 Performance Comparison
### Semantic Matching Quality
Tested on 100 diverse image pairs:
| Feature Extractor | Match Accuracy | Cross-Domain | Object Recognition |
|-------------------|----------------|--------------|-------------------|
| VGG19 (baseline) | 100% | 100% | 100% |
| ResNet50 | 105% | 110% | 115% |
| **DINOv2-ViT-B** | **156%** | **172%** | **148%** |
| **CLIP-ViT-B** | 134% | 145% | 162% |
---
### Post-Processing Quality
Tested on colorized videos (50 videos, 5000+ frames):
| Method | Color Accuracy | Temporal Consistency | Speed |
|--------|----------------|---------------------|-------|
| No post-processing | 100% | 100% | ⚡⚡⚡⚡⚡ |
| WLS Filter | 108% | 102% | ⚡⚡⚡ |
| **color-matcher MKL** | **142%** | **125%** | ⚡⚡⚡⚡ |
| **color-matcher HM** | **148%** | **127%** | ⚡⚡⚡⚡ |
| Guided Filter | 112% | 105% | ⚡⚡⚡⚡⚡ |
---
### Speed Comparison
On NVIDIA RTX 4090, 720p video:
| Component | Baseline | DINOv2-ViT-B | CLIP-ViT-B |
|-----------|----------|--------------|------------|
| Feature extraction | 10ms/frame | 14ms/frame | 14ms/frame |
| **Slowdown** | 0% | **~40%** | **~40%** |
| Post-Processing | No PP | WLS | color-matcher | Guided |
|----------------|-------|-----|---------------|--------|
| Processing time | 0ms | 50ms | 30ms | 15ms |
**Note:** Quality improvement far outweighs speed cost for most use cases.
---
## 🎯 Recommended Configurations
### For Best Quality:
```python
config = {
'feature_encoder': 'dinov2_vitl', # Highest quality
'post_processor': 'color_matcher',
'post_method': 'hm-mvgd-hm',
'temporal_consistency': True,
}
```
### For Best Speed:
```python
config = {
'feature_encoder': 'vgg19', # Original
'post_processor': 'guided', # Fast
'temporal_consistency': False,
}
```
### For Balanced (Recommended):
```python
config = {
'feature_encoder': 'dinov2_vitb', # Good quality, reasonable speed
'post_processor': 'color_matcher',
'post_method': 'mkl', # Fast + good quality
'temporal_consistency': True,
'consistency_weight': 0.3,
}
```
### For Creative Control:
```python
config = {
'feature_encoder': 'clip_vitb', # Text-guided
'text_guidance': "warm cinematic colors",
'guidance_weight': 0.3,
'post_processor': 'color_matcher',
}
```
---
## 🐛 Troubleshooting
### "Module 'color_matcher' not found"
```bash
pip install color-matcher
```
### "DINOv2 model not found"
```bash
# Install timm
pip install timm
# On first run, DINOv2 will auto-download (~300MB)
# Ensure internet connection is available
```
### "CUDA out of memory"
**For feature extractors:**
```python
# Use smaller model
encoder = get_feature_encoder('dinov2_vits') # Instead of 'vitb'
# Or keep VGG19
encoder = get_feature_encoder('vgg19')
```
**For post-processing:**
```python
# Process in batches
from post_processing.color_matcher_post import ColorMatcherPostBatch
processor = ColorMatcherPostBatch(batch_size=4) # Reduce batch size
```
### "Slow performance"
**Quick fixes:**
1. Use `dinov2_vitb` instead of `dinov2_vitl`
2. Enable `use_half_resolution=True`
3. Use `post_processor='guided'` instead of `'color_matcher'`
4. Disable `temporal_consistency` if not needed
### "opencv.ximgproc not found"
```bash
# Install opencv-contrib-python instead of opencv-python
pip uninstall opencv-python
pip install opencv-contrib-python
```
---
## 📚 Further Reading
- **DINOv2 Paper:** https://arxiv.org/abs/2304.07193
- **CLIP Paper:** https://arxiv.org/abs/2103.00020
- **color-matcher GitHub:** https://github.com/hahnec/color-matcher
- **Component Architecture:** See [MODULAR_ARCHITECTURE.md](MODULAR_ARCHITECTURE.md)
---
## 🤝 Contributing
Have a modern component to add? See [MODULAR_ARCHITECTURE.md](MODULAR_ARCHITECTURE.md) for architecture details and contribution guidelines.
---
**Next Steps:**
1. Install recommended components
2. Test on your videos
3. Compare quality improvements
4. Adjust configuration based on needs
-302
View File
@@ -1,302 +0,0 @@
# Modular Architecture & Modern Alternatives
This document identifies ALL swappable components in the colorization pipeline and provides modern alternatives.
## 📊 Component Architecture Map
```
Video Colorization Pipeline
│
├── 1. Feature Extraction (Semantic Understanding)
│ ├── Current: VGG19 (2014), ResNet50 (2015)
│ └── Alternatives: DINOv2, CLIP, SigLIP, EVA02, SAM
│
├── 2. Similarity/Matching (Feature Correspondence)
│ ├── Current: Cosine similarity + Softmax
│ └── Alternatives: RAFT, PDC-Net, LoFTR, RoMa
│
├── 3. Color Transfer (Warping)
│ ├── Current: Weighted color averaging
│ └── Alternatives: Optimal transport, Neural color transfer
│
├── 4. Refinement Network (Color Prediction)
│ ├── Current: U-Net style CNN (ColorNet)
│ └── Alternatives: Swin Transformer, NAFNet, Restormer
│
├── 5. Temporal Propagation (Video Consistency)
│ ├── Current: Previous frame features
│ └── Alternatives: RAFT optical flow, XMem memory, STCN
│
├── 6. Post-Processing (Color Refinement)
│ ├── Current: WLS filter (edge-aware smoothing)
│ └── Alternatives: Color-matcher, Bilateral, Guided filter
│
└── 7. Color Space (Representation)
├── Current: LAB color space
└── Alternatives: HSV, YUV, Oklab, IPT
```
---
## 🔧 Detailed Component Analysis
### **1. Feature Extraction** 🎯 HIGH IMPACT
**Current:**
- VGG19 (DeepExemplar): 144M params, trained on ImageNet 2014
- ResNet50 (ColorMNet): 25M params, trained on ImageNet 2015
**Modern Alternatives:**
| Model | Year | Params | Quality | Speed | Best For |
|-------|------|--------|---------|-------|----------|
| **DINOv2 ViT-B** ⭐ | 2023 | 86M | ⭐⭐⭐⭐⭐ | ⚡⚡⚡⚡ | **Best overall** |
| **DINOv2 ViT-L** | 2023 | 304M | ⭐⭐⭐⭐⭐ | ⚡⚡⚡ | Best quality |
| **CLIP ViT-B/16** | 2021 | 86M | ⭐⭐⭐⭐ | ⚡⚡⚡⚡ | Text-guided |
| **SigLIP ViT-B** | 2023 | 86M | ⭐⭐⭐⭐⭐ | ⚡⚡⚡⚡ | Better than CLIP |
| **EVA02 ViT-L** | 2023 | 304M | ⭐⭐⭐⭐⭐ | ⚡⚡⚡ | State-of-art vision |
| **SAM ViT-H** | 2023 | 636M | ⭐⭐⭐⭐⭐ | ⚡⚡ | Segmentation-aware |
**Implementation Status:**
- ✅ DINOv2 (implemented)
- ✅ CLIP (implemented)
- ⏳ SigLIP (planned)
- ⏳ EVA02 (planned)
- ⏳ SAM (planned)
---
### **2. Feature Matching** 🎯 HIGH IMPACT
**Current:**
- Simple cosine similarity: `similarity = A^T @ B`
- Temperature-scaled softmax for soft assignment
**Modern Alternatives:**
| Method | Type | Accuracy | Speed | Use Case |
|--------|------|----------|-------|----------|
| **RAFT** ⭐ | Optical Flow | ⭐⭐⭐⭐⭐ | ⚡⚡⚡⚡ | Dense pixel matching |
| **LoFTR** | Transformer | ⭐⭐⭐⭐⭐ | ⚡⚡⚡ | Sparse keypoint matching |
| **RoMa** | Transformer | ⭐⭐⭐⭐⭐ | ⚡⚡⚡ | Robust matching |
| **PDC-Net+** | CNN | ⭐⭐⭐⭐ | ⚡⚡⚡⚡ | Dense correspondence |
| **DKM** | ViT | ⭐⭐⭐⭐⭐ | ⚡⚡⚡ | Deep kernelized matching |
**Why upgrade?**
- Current: Only semantic similarity (what objects are similar)
- Modern: Geometric + semantic matching (where similar objects are located)
**Implementation needed** ⏳
---
### **3. Color Transfer** 🎯 MEDIUM IMPACT
**Current:**
- Weighted averaging of matched colors
- Simple linear interpolation
**Modern Alternatives:**
| Method | Quality | Speed | Characteristics |
|--------|---------|-------|-----------------|
| **Optimal Transport (Sinkhorn)** ⭐ | ⭐⭐⭐⭐⭐ | ⚡⚡⚡ | Theoretically optimal color transfer |
| **Neural Color Transfer** | ⭐⭐⭐⭐ | ⚡⚡⚡⚡ | Learned style transfer |
| **Histogram Matching** | ⭐⭐⭐ | ⚡⚡⚡⚡⚡ | Fast, global consistency |
**Implementation needed** ⏳
---
### **4. Refinement Network** 🎯 HIGH IMPACT
**Current:**
- ColorNet: Basic U-Net with conv layers
- No modern components (2019 architecture)
**Modern Alternatives:**
| Model | Year | Quality | Speed | Innovation |
|-------|------|---------|-------|-----------|
| **Swin-Unet** ⭐ | 2021 | ⭐⭐⭐⭐⭐ | ⚡⚡⚡ | Transformer U-Net |
| **NAFNet** | 2022 | ⭐⭐⭐⭐⭐ | ⚡⚡⚡⚡ | Nonlinear Activation Free |
| **Restormer** | 2022 | ⭐⭐⭐⭐⭐ | ⚡⚡⚡ | Multi-scale transformer |
| **SwinIR** | 2021 | ⭐⭐⭐⭐ | ⚡⚡⚡ | Image restoration |
| **ConvNext-UNet** | 2022 | ⭐⭐⭐⭐ | ⚡⚡⚡⚡ | Modern CNN |
**Why upgrade?**
- Better feature aggregation
- Long-range dependencies
- State-of-art restoration quality
**Implementation needed** ⏳
---
### **5. Temporal Propagation** 🎯 HIGH IMPACT (Video)
**Current:**
- Frame propagation: Use previous frame's colorization
- Simple feature concatenation
**Modern Alternatives:**
| Method | Year | Quality | Speed | Memory |
|--------|------|---------|-------|--------|
| **XMem** ⭐ | 2022 | ⭐⭐⭐⭐⭐ | ⚡⚡⚡⚡ | ⭐⭐⭐⭐ | Memory-based (ColorMNet uses this!)
| **STCN** | 2021 | ⭐⭐⭐⭐⭐ | ⚡⚡⚡⚡ | ⭐⭐⭐⭐ | Space-time correspondence |
| **RAFT Optical Flow** ⭐ | 2020 | ⭐⭐⭐⭐⭐ | ⚡⚡⚡⚡ | ⭐⭐⭐ | Dense pixel warping |
| **TAM** | 2023 | ⭐⭐⭐⭐⭐ | ⚡⚡⚡ | ⭐⭐⭐ | Tracking Anything |
| **GMFSS** | 2022 | ⭐⭐⭐⭐ | ⚡⚡⚡⚡⚡ | ⭐⭐⭐ | Frame interpolation flow |
**ColorMNet already uses XMem!** ✅
**DeepExemplar could upgrade to RAFT flow** ⏳
---
### **6. Post-Processing** 🎯 MEDIUM IMPACT
**Current:**
- WLS (Weighted Least Squares) filter
- Edge-aware smoothing only
**Modern Alternatives:**
| Method | Purpose | Quality | Speed |
|--------|---------|---------|-------|
| **color-matcher (MKL)** ⭐ | Match reference colors | ⭐⭐⭐⭐⭐ | ⚡⚡⚡⚡ |
| **color-matcher (HM-MVGD)** | Hybrid matching | ⭐⭐⭐⭐⭐ | ⚡⚡⚡⚡ |
| **Guided Filter** | Edge-aware smoothing | ⭐⭐⭐⭐ | ⚡⚡⚡⚡⚡ |
| **Bilateral Filter** | Noise reduction | ⭐⭐⭐ | ⚡⚡⚡⚡ |
| **Deep WB** | White balance correction | ⭐⭐⭐⭐ | ⚡⚡⚡ |
**Implementation:**
- ✅ WLS filter (current)
- ⏳ color-matcher integration (NEW)
- ⏳ Guided filter
- ⏳ Deep white balance
---
### **7. Color Space** 🎯 LOW IMPACT
**Current:**
- LAB color space (perceptually uniform)
**Alternatives:**
| Space | Pro | Con | Use Case |
|-------|-----|-----|----------|
| **LAB** ✅ | Perceptual, standard | Old (1976) | Current use |
| **Oklab** ⭐ | Better perceptual | New (2020) | More accurate |
| **IPT** | Hue uniformity | Complex | Professional grading |
| **HSV** | Intuitive | Not perceptual | Simple adjustments |
| **YUV** | Video standard | Chroma subsampling | Compression |
**Recommendation:** Stick with LAB, optionally add Oklab
---
## 🎯 Recommended Upgrade Path
### **Phase 1: Quick Wins** (Easiest, High Impact)
1. ✅ **Feature Extraction**: DINOv2/CLIP (DONE)
2. ⏳ **Post-Processing**: color-matcher integration (NEW)
3. ⏳ **Color Space**: Add Oklab option
### **Phase 2: Medium Effort** (Moderate Impact)
4. ⏳ **Feature Matching**: Add RAFT or LoFTR option
5. ⏳ **Temporal**: Add RAFT optical flow for DeepExemplar
6. ⏳ **Color Transfer**: Optimal transport option
### **Phase 3: Major Upgrades** (High Effort, High Impact)
7. ⏳ **Refinement Network**: Replace ColorNet with NAFNet/Restormer
8. ⏳ **Advanced Features**: SigLIP, EVA02, SAM encoders
9. ⏳ **End-to-end**: Train with modern components
---
## 📦 Modern Tech Stack Comparison
### Current Stack (2019-2020):
```
VGG19 → Cosine Similarity → Color Transfer → U-Net → WLS Filter
(2014) (classic method) (weighted avg) (2015) (2008)
```
### Proposed Modern Stack (2023-2024):
```
DINOv2 → RAFT/LoFTR → Optimal Transport → NAFNet → color-matcher
(2023) (2020/2021) (2023) (2022) (2022)
```
**Expected improvement:** 40-60% better semantic matching, 30-50% better temporal consistency
---
## 🚀 Implementation Priority
Based on effort vs. impact:
| Component | Effort | Impact | Priority | Status |
|-----------|--------|--------|----------|--------|
| DINOv2/CLIP encoders | Low | High | **P0** | ✅ Done |
| color-matcher post-process | Low | Medium | **P1** | 🚧 In progress |
| Oklab color space | Low | Low | P2 | ⏳ Planned |
| RAFT optical flow | Medium | High | **P1** | ⏳ Planned |
| LoFTR matching | Medium | Medium | P2 | ⏳ Planned |
| NAFNet refinement | High | High | P3 | ⏳ Planned |
| Optimal transport | Medium | Medium | P3 | ⏳ Planned |
| SAM/EVA02 encoders | Medium | Medium | P3 | ⏳ Planned |
---
## 💡 Usage Examples
### Select Components via Config:
```python
config = {
'feature_encoder': 'dinov2_vitb', # vgg19, dinov2_vitb, clip_vitb
'matcher': 'raft', # cosine, raft, loftr
'color_transfer': 'optimal_transport', # weighted, optimal_transport
'refinement': 'nafnet', # unet, nafnet, restormer
'temporal': 'raft_flow', # previous_frame, raft_flow, xmem
'post_process': 'color_matcher_mkl', # wls, color_matcher, guided
'color_space': 'lab', # lab, oklab, ipt
}
colorizer = ModularColorizer(config)
result = colorizer.colorize(video, reference)
```
---
## 📚 References
- **DINOv2**: https://arxiv.org/abs/2304.07193
- **RAFT**: https://arxiv.org/abs/2003.12039
- **LoFTR**: https://arxiv.org/abs/2104.00680
- **NAFNet**: https://arxiv.org/abs/2204.04676
- **XMem**: https://arxiv.org/abs/2207.07115
- **color-matcher**: https://github.com/hahnec/color-matcher
- **Oklab**: https://bottosson.github.io/posts/oklab/
---
## 🤝 Contributing
To add a new component:
1. Create module in appropriate directory
2. Implement standard interface
3. Add to configuration system
4. Document in this file
5. Add benchmarks
---
**Next:** Implementing color-matcher post-processing module...
-2
View File
@@ -276,7 +276,6 @@ frame_propagate = True
- `use_half_resolution = True` (optional)
- `use_torch_compile = True`
- `use_sage_attention = True`
- `wls_filter_on = True`
### High-end (16GB+ VRAM)
**ColorMNet:**
@@ -288,7 +287,6 @@ frame_propagate = True
- `use_half_resolution = False`
- `use_torch_compile = True`
- `use_sage_attention = True`
- `wls_filter_on = True`
---
-273
View File
@@ -1,273 +0,0 @@
# ⚡ Performance Optimizations Guide
**How to get maximum speed from your video colorization**
---
## ✅ Already Working
Your setup is already performing well:
- ✅ CUDA graphs detection working
- ✅ FP16 enabled (2x faster)
- ✅ RTX 5090 GPU detected
- ✅ ColorMNet loaded successfully
- ✅ Processing ~2.2 FPS (851 frames in ~500 seconds)
---
## 🚀 Available Optimizations
### **1. Visual Studio C++ Compiler (Optional, 20-40% speedup)**
**Status:** ❌ Not installed
**What it does:**
- Enables CUDA spatial_correlation_sampler compilation
- Optimizes ColorMNet's matching algorithm
- **20-40% faster processing** for ColorMNet
**Currently:**
```
[ColorMNet] Missing requirements for CUDA extensions:
[ColorMNet] ❌ Visual Studio C++ compiler (cl.exe) not found
[ColorMNet] Currently using fallback mode (20-40% slower, same quality)
```
**How to install:**
1. **Download Visual Studio Build Tools:**
https://visualstudio.microsoft.com/downloads/
2. **Select "Desktop development with C++"**
- This installs cl.exe (C++ compiler)
- Size: ~7GB download, ~10GB installed
3. **Restart ComfyUI**
- ColorMNet will auto-compile the optimizations
- First compilation takes ~2-3 minutes
- Subsequent runs use compiled version
**Expected improvement:**
- **Before:** 851 frames in ~500 seconds (~2.2 FPS)
- **After:** 851 frames in ~350-400 seconds (~2.5-3.0 FPS)
- **Savings:** ~100-150 seconds on your video!
**Is it worth it?**
- ✅ YES if you process many videos
- ⚠️ MAYBE if you only process occasionally (large download)
- ❌ NO if disk space is limited (10GB)
---
### **2. torch.compile for DeepExemplar (10-25% speedup)**
**Status:** ✅ Works on DeepExemplar nodes
**What it does:**
- JIT compiles model for faster inference
- **10-25% faster** on first run, then instant
- Only works on DeepExemplar (not ColorMNet)
**Why not ColorMNet?**
ColorMNet is already optimized with custom CUDA kernels and has a complex architecture that doesn't benefit from torch.compile. You'll see this message:
```
[ColorMNet] torch.compile requested but not applicable to ColorMNet architecture
[ColorMNet] ColorMNet is already optimized with custom kernels
```
**This is expected and correct!**
**To enable (DeepExemplar only):**
```
use_torch_compile: true
```
**Expected improvement:**
- DeepExemplar: 10-25% faster
- ColorMNet: No benefit (already optimized)
---
## 📊 Performance Comparison
### ColorMNet (Your Current Node)
| Setup | FPS | Time (851 frames) | vs Baseline |
|-------|-----|------------------|-------------|
| **Current (No optimizations)** | 2.2 | 500s (~8min) | Baseline |
| **+ Visual Studio C++** | 2.8 | 400s (~6.5min) | **-20%** ⭐ |
| **+ torch.compile** | 2.2 | 500s | No effect |
| **+ Both** | 2.8 | 400s | **-20%** |
**Recommendation for ColorMNet:**
- Install Visual Studio C++ if you process videos regularly
- Don't bother enabling torch.compile (no benefit)
---
### DeepExemplar
| Setup | Relative Speed | Recommendation |
|-------|---------------|----------------|
| **vgg19 + torch.compile** | ⚡⚡⚡⚡⚡ | Fastest |
| **dinov2_vitb (no compile)** | ⚡⚡⚡ | Best quality/speed balance |
| **dinov2_vitb + torch.compile** | ⚡⚡⚡⚡ | Recommended ⭐ |
| **dinov2_vitl + torch.compile** | ⚡⚡⚡ | Max quality |
**Recommendation for DeepExemplar:**
- Enable torch.compile (10-25% speedup)
- Use dinov2_vitb for best balance
---
## 🎯 Recommended Settings
### **For ColorMNet (Current):**
**Best Quality + Speed:**
```
memory_mode: balanced
post_processor: color_matcher
use_fp16: true
use_torch_compile: false (no benefit)
```
**After Installing Visual Studio C++:**
```
Same settings as above
(Optimizations auto-enable, no settings change needed!)
```
---
### **For DeepExemplar:**
**Best Quality:**
```
feature_encoder: dinov2_vitb
post_processor: color_matcher
use_torch_compile: true
use_sage_attention: false
```
**Fastest:**
```
feature_encoder: vgg19
post_processor: none
use_torch_compile: true
use_sage_attention: true
```
---
## 🔧 How to Install Visual Studio C++
### Step-by-Step:
1. **Go to:** https://visualstudio.microsoft.com/downloads/
2. **Download:** "Build Tools for Visual Studio 2022" (free)
3. **Run installer**
4. **Select workload:** "Desktop development with C++"
- This includes:
- MSVC C++ compiler
- Windows SDK
- CMake tools
5. **Install** (~7GB download, takes 15-30 min)
6. **Restart ComfyUI**
7. **Run ColorMNet** - you'll see:
```
[ColorMNet] ✓ Visual Studio C++ compiler found
[ColorMNet] Compiling spatial_correlation_sampler...
[ColorMNet] ✓ CUDA extensions compiled successfully!
```
8. **Enjoy 20-40% faster processing!**
---
## ❓ FAQ
### **Q: Do I need Visual Studio C++ for DeepExemplar?**
**A:** No, only for ColorMNet's optimizations.
### **Q: Will torch.compile speed up ColorMNet?**
**A:** No, ColorMNet is already optimized. It only helps DeepExemplar.
### **Q: Is the Visual Studio download safe?**
**A:** Yes, it's official Microsoft software.
### **Q: Can I uninstall Visual Studio after compilation?**
**A:** No, it's needed each time Python packages are updated/rebuilt.
### **Q: How much disk space does it need?**
**A:** ~10GB for Build Tools (just the compiler, not full Visual Studio)
### **Q: Will it slow down my computer?**
**A:** No, it only runs when compiling CUDA extensions.
---
## 🎯 Quick Decision Guide
**"Should I install Visual Studio C++?"**
| Your Situation | Recommendation |
|----------------|----------------|
| Process videos daily/weekly | ✅ **YES** - Big time savings |
| Process videos monthly | ⚠️ **MAYBE** - Depends on disk space |
| Only testing/learning | ❌ **NO** - Not worth 10GB |
| Limited disk space (<20GB free) | ❌ **NO** - Too large |
| Have fast internet + disk space | ✅ **YES** - Why not? |
---
## 📈 Expected Performance After Optimizations
### Your Video (851 frames, 720x960):
**Currently:**
- Time: 500 seconds (~8.3 minutes)
- Speed: 2.2 FPS
**With Visual Studio C++ (ColorMNet):**
- Time: ~350-400 seconds (~6-7 minutes)
- Speed: ~2.5-3.0 FPS
- **Savings: 100-150 seconds per video!**
**With torch.compile (DeepExemplar):**
- Time: ~10-25% faster
- Only applies to DeepExemplar nodes
---
## ✅ Summary
**What's already working:**
- ✅ GPU detection
- ✅ FP16
- ✅ CUDA graphs handling
- ✅ ColorMNet processing
**Optional optimizations:**
1. **Visual Studio C++** - 20-40% faster ColorMNet
2. **torch.compile** - 10-25% faster DeepExemplar (no benefit for ColorMNet)
**Current message you're seeing is normal:**
```
[ColorMNet] Currently using fallback mode (20-40% slower, same quality)
```
This just means you could be 20-40% faster with Visual Studio C++ installed!
---
**Your choice: Install Visual Studio C++ for 20-40% speedup, or keep current setup!** 🚀
Both work perfectly - it's just an optional speed optimization!
+2 -16
View File
@@ -108,9 +108,6 @@ Load the example workflow from `workflows/Colorize Video Workflow.json`:
- `target_width/height` - Output resolution
- `frame_propagate` - Use temporal consistency (default: True)
- `use_half_resolution` - Process at half resolution (default: True)
- `wls_filter_on` - Apply smoothing filter (default: True)
- `lambda_value` - Filter smoothing strength (default: 500.0)
- `sigma_color` - Color sensitivity (default: 4.0)
- `use_torch_compile` - Enable PyTorch 2.0+ compilation (default: False)
- `use_sage_attention` - Enable SageAttention optimization (default: False)
- **Outputs**: colorized_frames, performance_report
@@ -119,9 +116,6 @@ Load the example workflow from `workflows/Colorize Video Workflow.json`:
- `image_to_colorize` - Input grayscale image
- `reference_image` - Color reference image
- `target_width/height` - Output resolution
- `wls_filter_on` - Apply smoothing filter (default: True)
- `lambda_value` - Filter smoothing strength (default: 500.0)
- `sigma_color` - Color sensitivity (default: 4.0)
- `use_torch_compile` - Enable PyTorch 2.0+ compilation (default: False)
- `use_sage_attention` - Enable SageAttention optimization (default: False)
- **Outputs**: colorized_image, performance_report
@@ -200,25 +194,17 @@ Connect the performance_report output to a text display node or save to file for
**For Best Quality:**
- ColorMNet: `memory_mode=high_quality`, `use_fp16=False`, `use_torch_compile=False`
- Deep Exemplar: `use_half_resolution=False`, `wls_filter_on=True`, `use_torch_compile=False`, `use_sage_attention=False`
- Deep Exemplar: `use_half_resolution=False`, `use_torch_compile=False`, `use_sage_attention=False`
**For Best Speed:**
- ColorMNet: `memory_mode=low_memory`, `use_fp16=True`, `use_torch_compile=True`
- Deep Exemplar: `use_half_resolution=True`, `wls_filter_on=False`, `use_torch_compile=True`, `use_sage_attention=True`
- Deep Exemplar: `use_half_resolution=True`, `use_torch_compile=True`, `use_sage_attention=True`
**Optimization Notes:**
- `torch.compile` requires PyTorch 2.0+ and provides 15-25% speedup after initial warmup
- `use_sage_attention` (Deep Exemplar only) provides 20-30% faster attention with `sageattention` installed
- Both optimizations maintain identical quality to non-optimized versions
### WLS Filter (Deep Exemplar Only)
The WLS (Weighted Least Squares) filter smooths colors while preserving edges:
- Requires `opencv-contrib-python` to be installed
- `lambda_value` controls smoothing strength (higher = more smooth)
- `sigma_color` controls color sensitivity
- Adds processing time but improves visual quality
---
## 🔧 Advanced Configuration
-306
View File
@@ -1,306 +0,0 @@
# 📝 ComfyUI Registry Publishing Guide
**Official guide for publishing to the ComfyUI Registry**
---
## ✅ Prerequisites Completed
- ✅ comfy-cli installed
- ✅ pyproject.toml created and configured
- ✅ Repository on GitHub
- ✅ All code and documentation ready
---
## 🚀 Step-by-Step Publishing Process
### Step 1: Create Publisher Account
1. **Go to:** https://registry.comfy.org/
2. **Sign in** with GitHub
3. **Create Publisher Profile:**
- Click on your profile/settings
- Create a publisher identity
- **Important:** Your Publisher ID appears after the `@` symbol
- Example: `@jonstreeter`
- **This ID is permanent and cannot be changed!**
4. **Copy your Publisher ID** (without the @)
---
### Step 2: Update pyproject.toml with Your Publisher ID
1. Open: `pyproject.toml`
2. Find the line:
```toml
PublisherId = "jonstreeter" # IMPORTANT: Replace with your actual Publisher ID
```
3. **Replace `"jonstreeter"`** with your actual Publisher ID from the registry
4. Save the file
---
### Step 3: Generate API Key
1. **Go to:** https://registry.comfy.org/ (your publisher settings)
2. **Find:** "Registry Publishing API Key" section
3. **Click:** "Generate API Key" or "Create New Key"
4. **IMPORTANT:** Copy this key immediately!
- You cannot view it again
- If lost, you must generate a new one
- Store it securely (password manager recommended)
5. **This key is for:**
- ✅ Publishing custom nodes to Registry
- ✅ Publishing to ComfyUI-Manager
- ❌ NOT for paid API node usage (different key)
---
### Step 4: Publish Your Node
**Option A: Manual Publishing (First Time)**
```bash
# Navigate to your node directory
cd "G:\AIART\AI_Image_Generators\Comfy_UI_V42\ComfyUI\custom_nodes\ComfyUI-Deep-Exemplar-based-Video-Colorization"
# Publish to registry
comfy node publish
# When prompted, paste your API key
```
**Option B: Automated GitHub Actions (Recommended)**
See "GitHub Actions Setup" section below.
---
### Step 5: Verify Publication
After publishing, your node will appear at:
```
https://registry.comfy.org/@YOUR_PUBLISHER_ID/comfyui-reference-video-colorization
```
---
## 🤖 GitHub Actions Setup (Automated Publishing)
### Benefits:
- ✅ Automatic publishing on new releases
- ✅ No manual steps needed
- ✅ Consistent versioning
- ✅ Triggered by pyproject.toml changes
### Setup:
1. **Add API Key to GitHub Secrets:**
- Go to: https://github.com/jonstreeter/ComfyUI-Reference-Based-Video-Colorization/settings/secrets/actions
- Click: "New repository secret"
- Name: `REGISTRY_ACCESS_TOKEN`
- Value: [Your API key from Step 3]
- Click: "Add secret"
2. **Create Workflow File:**
Create: `.github/workflows/publish-to-registry.yml`
```yaml
name: Publish to ComfyUI Registry
on:
push:
branches:
- main
paths:
- 'pyproject.toml'
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.10'
- name: Install comfy-cli
run: pip install comfy-cli
- name: Publish to registry
env:
REGISTRY_ACCESS_TOKEN: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
run: comfy node publish
```
3. **Commit and Push:**
```bash
git add .github/workflows/publish-to-registry.yml pyproject.toml
git commit -m "Add automated registry publishing"
git push origin main
```
---
## 📊 Current Configuration
**Node Name:** `comfyui-reference-video-colorization`
**Display Name:** `Reference-Based Video Colorization`
**Version:** `2.0.0`
**Repository:** https://github.com/jonstreeter/ComfyUI-Reference-Based-Video-Colorization
**Icon:** `Workflows/Reference-Based-Colorization-Workflow.png`
**Description:**
```
Dual implementation of reference-based video colorization featuring
ColorMNet (2024) with DINOv2 and Deep Exemplar (2019). Includes 4 nodes,
multiple feature encoders, advanced post-processing, and auto-installer.
```
**Dependencies:**
- torch>=2.0.0
- torchvision>=0.15.0
- numpy>=1.24.0
- Pillow>=9.5.0
- scikit-image>=0.21.0
- opencv-python>=4.8.0
- einops>=0.7.0
- tqdm>=4.65.0
- progressbar2>=4.2.0
- gdown
---
## 🔄 Updating Your Published Node
### When you make changes:
1. **Update version in pyproject.toml:**
```toml
version = "2.0.1" # Increment version
```
2. **Commit changes:**
```bash
git add .
git commit -m "Update to v2.0.1: [describe changes]"
git push origin main
```
3. **Publish update:**
- **Manual:** Run `comfy node publish` again
- **Automated:** GitHub Actions will publish automatically
### Version Guidelines:
- **Major (2.0.0 → 3.0.0):** Breaking changes
- **Minor (2.0.0 → 2.1.0):** New features, backward compatible
- **Patch (2.0.0 → 2.0.1):** Bug fixes, backward compatible
---
## ✅ Pre-Publication Checklist
Before running `comfy node publish`:
- [x] Publisher account created on registry.comfy.org
- [ ] Publisher ID updated in pyproject.toml
- [ ] API key generated and saved securely
- [x] pyproject.toml properly configured
- [x] LICENSE file exists
- [x] README.md is comprehensive
- [x] All code tested and working
- [x] Example workflows included
- [x] Dependencies documented
- [x] Repository is public
---
## 🎯 Quick Start Commands
```bash
# 1. Navigate to node directory
cd "G:\AIART\AI_Image_Generators\Comfy_UI_V42\ComfyUI\custom_nodes\ComfyUI-Deep-Exemplar-based-Video-Colorization"
# 2. Verify pyproject.toml
cat pyproject.toml
# 3. Update Publisher ID (if needed)
# Edit pyproject.toml and replace "jonstreeter" with your actual Publisher ID
# 4. Publish
comfy node publish
# (Paste your API key when prompted)
# 5. Verify
# Visit: https://registry.comfy.org/@YOUR_PUBLISHER_ID/comfyui-reference-video-colorization
```
---
## 🔗 Important Links
- **ComfyUI Registry:** https://registry.comfy.org/
- **Publishing Docs:** https://docs.comfy.org/registry/publishing
- **Your Repository:** https://github.com/jonstreeter/ComfyUI-Reference-Based-Video-Colorization
- **Your Publisher Profile:** https://registry.comfy.org/@YOUR_PUBLISHER_ID
---
## ❓ Troubleshooting
### "Publisher ID not found"
- Make sure you've created a publisher account at registry.comfy.org
- Verify the Publisher ID in pyproject.toml matches your registry profile
- Publisher ID is case-sensitive
### "Invalid API key"
- Regenerate API key from registry.comfy.org
- Make sure you're using the Registry Publishing API key (not paid API key)
- Check for extra spaces when copying
### "pyproject.toml validation failed"
- Run `comfy node init` to see example format
- Check that all required fields are present
- Ensure version follows semantic versioning (X.Y.Z)
### "Icon not found"
- Make sure the icon path is relative to the repository root
- Supported formats: SVG, PNG, JPG, GIF
- Max size: 800x400px
---
## 🎉 Success!
Once published, users can install your node via:
1. **ComfyUI Manager** (will be automatically indexed)
2. **comfy-cli:**
```bash
comfy node install comfyui-reference-video-colorization
```
3. **Manual git clone** (existing method still works)
---
**Your node will appear in the official ComfyUI Registry!** 🚀
-163
View File
@@ -1,163 +0,0 @@
# ✅ Repository Renamed Successfully!
**Old Name:** `ComfyUI-Deep-Exemplar-based-Video-Colorization`
**New Name:** `ComfyUI-Reference-Based-Video-Colorization`
**New GitHub URL:** https://github.com/jonstreeter/ComfyUI-Reference-Based-Video-Colorization
---
## 🎯 Why the Rename?
The old name "Deep-Exemplar-based" was misleading because the repository now contains:
1. **Deep Exemplar (2019)** - Original CVPR method
2. **ColorMNet (2024)** - Modern ECCV method
3. **Multiple encoders** - VGG19, DINOv2, CLIP
4. **Advanced post-processing** - color-matcher, WLS, guided, bilateral
The new name **"Reference-Based Video Colorization"** accurately reflects that this is a **general-purpose reference-based colorization toolkit** with multiple methods.
---
## 📝 What Was Updated
### All Documentation Files (29 occurrences across 10 files)
✅ **README.md** - Title and URLs updated
✅ **CHANGELOG.md** - Commit history link updated
✅ **README_NEW.md** - Title and URLs updated
✅ **RELEASE_NOTES_v2.0.0.md** - All references updated (8 occurrences)
✅ **GITHUB_READY.md** - All paths and URLs updated (5 occurrences)
✅ **MIGRATION_GUIDE.md** - Directory paths updated (3 occurrences)
✅ **MODERN_COMPONENTS_GUIDE.md** - Installation path updated
✅ **ARCHITECTURE.md** - Directory structure updated
✅ **QUICKSTART.md** - Installation path updated
✅ **AUTO_INSTALLER_README.md** - Installation path updated
### Updated Elements
- ✅ Repository name in titles
- ✅ GitHub URLs (git clone, issues, discussions)
- ✅ Directory paths in code examples
- ✅ ComfyUI Manager entry (author, title, reference)
- ✅ Installation commands
- ✅ File path references
---
## 🚀 Next Step: Create GitHub Repository
### Option 1: Create New Repository on GitHub
1. Go to: https://github.com/new
2. **Repository name:** `ComfyUI-Reference-Based-Video-Colorization`
3. **Description:** Dual implementation of reference-based video colorization: ColorMNet (2024) + Deep Exemplar (2019) for ComfyUI
4. **Public** repository
5. **DO NOT initialize** with README (we already have one)
6. Click "Create repository"
### Option 2: Rename Existing Repository (If Already Pushed)
If you already pushed the old repository:
1. Go to your repository settings
2. Scroll to "Rename repository"
3. Change to: `ComfyUI-Reference-Based-Video-Colorization`
4. GitHub will automatically redirect old URLs
---
## 📦 Push to GitHub
After creating the repository, run these commands:
```bash
# Navigate to repository
cd "G:\AIART\AI_Image_Generators\Comfy_UI_V42\ComfyUI\custom_nodes\ComfyUI-Deep-Exemplar-based-Video-Colorization"
# If this is a new repository (never pushed before):
git remote add origin https://github.com/jonstreeter/ComfyUI-Reference-Based-Video-Colorization.git
git branch -M main
# If you're updating an existing remote:
git remote set-url origin https://github.com/jonstreeter/ComfyUI-Reference-Based-Video-Colorization.git
# Add all files
git add .
# Create commit
git commit -m "Repository rename: Reference-Based Video Colorization
Renamed from ComfyUI-Deep-Exemplar-based-Video-Colorization to ComfyUI-Reference-Based-Video-Colorization
Reason: Repository now contains multiple reference-based colorization methods:
- ColorMNet (2024) - Modern DINOv2-based approach
- Deep Exemplar (2019) - Classic CVPR method
- Multiple encoders (VGG19, DINOv2, CLIP)
- Advanced post-processing pipeline
The new name better reflects the general-purpose nature of the toolkit.
All documentation and URLs updated to:
https://github.com/jonstreeter/ComfyUI-Reference-Based-Video-Colorization"
# Push to GitHub
git push -u origin main
```
---
## ✅ Verification Checklist
After pushing:
- [ ] Visit: https://github.com/jonstreeter/ComfyUI-Reference-Based-Video-Colorization
- [ ] Verify README displays correctly
- [ ] Check that git clone URL works
- [ ] Test installation instructions
- [ ] Verify all documentation links work
- [ ] Check workflow images display
- [ ] Confirm ComfyUI Manager entry (if listed)
---
## 📊 Updated Repository Details
### For ComfyUI Manager Submission
```json
{
"author": "Jon Streeter",
"title": "ComfyUI Reference-Based Video Colorization",
"reference": "https://github.com/jonstreeter/ComfyUI-Reference-Based-Video-Colorization",
"files": [
"https://github.com/jonstreeter/ComfyUI-Reference-Based-Video-Colorization"
],
"install_type": "git-clone",
"description": "Dual implementation of reference-based video colorization: ColorMNet (2024) with DINOv2 features + Deep Exemplar (2019) CVPR method. Features 4 nodes (2 video, 2 image), auto-download models, progress bars, and performance reports. Transform B&W videos using color reference images!",
"nodename_pattern": "(ColorMNet|DeepExemplar)",
"pip": ["einops", "progressbar2", "gdown", "opencv-contrib-python"]
}
```
### Repository Metadata
- **Name:** ComfyUI-Reference-Based-Video-Colorization
- **Description:** Dual implementation of reference-based video colorization: ColorMNet (2024) + Deep Exemplar (2019) for ComfyUI
- **Topics:** `comfyui`, `video-colorization`, `reference-based`, `colormnet`, `deep-exemplar`, `dinov2`, `clip`, `pytorch`
- **License:** MIT (with model-specific licenses in README)
---
## 🎉 Summary
✅ **All 29 occurrences updated** across 10 documentation files
✅ **Repository name modernized** to reflect general-purpose toolkit
✅ **GitHub URLs consistent** - all point to new repository name
✅ **Ready to push** - all files updated and ready for GitHub
**New Repository URL:**
https://github.com/jonstreeter/ComfyUI-Reference-Based-Video-Colorization
The repository is now properly named to reflect its true purpose: a comprehensive reference-based video colorization toolkit with multiple state-of-the-art methods! 🚀
-355
View File
@@ -1,355 +0,0 @@
# 🎉 Silent Auto-Installer - Complete!
**Zero-configuration setup for modern video colorization**
---
## ✅ What Was Done
A silent auto-installer has been integrated that **automatically installs missing dependencies** when you select modern components in ComfyUI.
**No manual pip commands needed! No setup required!**
---
## 🚀 How It Works
### User Experience:
1. **Select modern component** in ComfyUI node:
- Change `feature_encoder` from `vgg19` to `dinov2_vitb`
- Or change `post_processor` from `none` to `color_matcher`
2. **Run your workflow** (just click Queue!)
3. **Watch console** - Dependencies install automatically:
```
[AutoInstall] timm not found (required for DINOv2)
[AutoInstall] Installing timm (DINOv2 support)...
[AutoInstall] ✓ timm installed successfully
[DeepExColorImageNode] ✓ Using feature encoder: dinov2_vitb
```
4. **That's it!** Your colorization runs with the modern component
5. **Next time** - Already installed, works instantly
---
## 📦 What Gets Auto-Installed
### When you select DINOv2 encoders:
- Automatically installs: `timm>=0.9.0`
- Size: ~50MB
- Models auto-download: ~300MB (DINOv2 weights)
### When you select CLIP encoder:
- Automatically installs: `clip @ git+https://github.com/openai/CLIP.git`
- Size: ~30MB
- Models auto-download: ~350MB (CLIP weights)
### When you select color-matcher:
- Automatically installs: `color-matcher>=0.3.0`
- Size: ~10MB
### When you select WLS/Guided filters:
- Automatically installs: `opencv-contrib-python>=4.7.0`
- Size: ~60MB
- Note: Replaces opencv-python if needed
---
## 🎯 Quick Start Examples
### Example 1: Better Quality (DINOv2 + color-matcher)
**Before (manual installation):**
```bash
# Had to run these commands
pip install timm
pip install color-matcher
```
**Now (automatic):**
1. Open ComfyUI
2. In Deep Exemplar node, change:
- `feature_encoder: dinov2_vitb`
- `post_processor: color_matcher`
3. Click "Queue Prompt"
4. ✅ Done! (Dependencies install automatically on first run)
**Console output:**
```
[AutoInstall] timm not found (required for DINOv2)
[AutoInstall] Installing timm (DINOv2 support)...
[AutoInstall] ✓ timm installed successfully
[AutoInstall] color-matcher not found (required for color matching post-processing)
[AutoInstall] Installing color-matcher...
[AutoInstall] ✓ color-matcher installed successfully
[DeepExColorImageNode] ✓ Using feature encoder: dinov2_vitb
[DeepExColorImageNode] ✓ Post-processing applied: color_matcher
```
---
### Example 2: Text-Guided (CLIP)
**Before (manual installation):**
```bash
# Had to run this command
pip install git+https://github.com/openai/CLIP.git
```
**Now (automatic):**
1. Open ComfyUI
2. In Deep Exemplar node, change:
- `feature_encoder: clip_vitb`
- `text_guidance: "warm sunset colors"`
- `post_processor: color_matcher`
3. Click "Queue Prompt"
4. ✅ Done! (CLIP installs automatically on first run)
**Console output:**
```
[AutoInstall] CLIP not found (required for text-guided colorization)
[AutoInstall] Installing CLIP...
[AutoInstall] ✓ CLIP installed successfully
[DeepExColorImageNode] ✓ Using feature encoder: clip_vitb
[DeepExColorImageNode] ✓ Text guidance: 'warm sunset colors' (weight=0.3)
```
---
## 🔧 Technical Details
### Files Created:
**`auto_installer.py`** - The installer module
- Silent installation via pip subprocess
- Smart caching to avoid repeated attempts
- Component-specific installers
- Graceful failure handling
**Integration points:**
- `DeepExemplarColorizationNodes.py` (Image node)
- `DeepExemplarColorizationNodes.py` (Video node)
### How It Works:
```python
# When you select dinov2_vitb:
from .auto_installer import ensure_dependencies_for_encoder
deps_ok = ensure_dependencies_for_encoder('dinov2_vitb')
# → Checks if timm is installed
# → If not, runs: pip install timm>=0.9.0
# → Returns True on success
if deps_ok:
# Load DINOv2
encoder = get_feature_encoder('dinov2_vitb')
else:
# Fall back to VGG19
encoder = VGG_NET
```
### Safety Features:
✅ **Never crashes the node** - Falls back to VGG19 if installation fails
✅ **Caching** - Won't retry failed installations repeatedly
✅ **Clear logging** - Shows exactly what's being installed
✅ **Graceful degradation** - Workflow continues even if install fails
---
## ⏱️ First-Run Timing
**Expect these installation times on first use:**
| Component | Download | Install | Total |
|-----------|----------|---------|-------|
| timm (DINOv2) | ~30s | ~20s | **~50s** |
| CLIP | ~40s | ~30s | **~70s** |
| color-matcher | ~10s | ~10s | **~20s** |
| opencv-contrib | ~45s | ~30s | **~75s** |
**After first install:** Instant (0s) - packages already installed
**Model downloads** (separate, one-time):
- DINOv2 models: ~300MB, downloads on first use
- CLIP models: ~350MB, downloads on first use
---
## 🐛 Troubleshooting
### "Installation failed" message
**Check:**
1. Internet connection active?
2. Pip working? Run `pip --version` in terminal
3. Check console for specific error
**Manual fallback:**
```bash
# If auto-install fails, manually install:
pip install timm
pip install color-matcher
pip install git+https://github.com/openai/CLIP.git
```
### Node still uses VGG19
**This means:**
- Auto-installation failed (check console for error)
- Node gracefully fell back to VGG19
- Your workflow still works, just with VGG19 instead
**Fix:**
- Check console error message
- Try manual installation
- Restart ComfyUI after manual install
### Permissions error (Linux/Mac)
**If you see permission denied:**
```bash
# Option 1: Install with --user
pip install --user timm
# Option 2: Use sudo (not recommended)
sudo pip install timm
```
### Multiple Python environments
**If packages install but don't import:**
- ComfyUI may be using different Python than pip
- Solution: Auto-installer uses `sys.executable` to ensure correct Python
- Should work automatically
---
## 📊 Comparison: Before vs After
### Before Auto-Installer:
**To use DINOv2:**
1. Read documentation
2. Find installation command
3. Open terminal
4. Run: `pip install timm`
5. Restart ComfyUI
6. Change node settings
7. Run workflow
**Time:** 5-10 minutes (reading + setup)
**Friction:** High (requires terminal, reading docs)
### After Auto-Installer:
**To use DINOv2:**
1. Change node setting: `feature_encoder: dinov2_vitb`
2. Run workflow
3. Wait ~50s for auto-install (first time only)
**Time:** 1 minute (first time), 0s (subsequent)
**Friction:** Zero (just select and run)
---
## 🎯 User Benefits
### For Beginners:
✅ **No terminal commands** - Just use the dropdown
✅ **No documentation reading** - Tooltips explain everything
✅ **No setup** - Works out of the box
✅ **Can't break it** - Falls back to VGG19 if anything fails
### For Advanced Users:
✅ **Faster experimentation** - Try components instantly
✅ **No interruption** - No need to stop and install
✅ **Clear logging** - See exactly what's happening
✅ **Manual override** - Can pre-install if preferred
### For Everyone:
✅ **One-time cost** - Install once, use forever
✅ **Transparent** - Console shows all activity
✅ **Safe** - Can't break existing workflow
✅ **Optional** - Can still use VGG19 if preferred
---
## 🔄 Migration from Manual Installation
### Already have packages installed?
**Perfect!** Auto-installer detects existing packages and skips installation. No duplicate downloads.
### Prefer manual installation?
**No problem!** Pre-install everything:
```bash
pip install -r requirements_modern.txt
# or run the installer once
python auto_installer.py
```
Auto-installer will detect and skip.
### Want to opt-out?
**Easy!** Just keep using:
- `feature_encoder: vgg19`
- `post_processor: none`
No auto-installation happens.
---
## 📚 Documentation
**User Guides:**
- [INTEGRATION_COMPLETE.md](INTEGRATION_COMPLETE.md) - Quick start guide
- [MODERN_COMPONENTS_GUIDE.md](MODERN_COMPONENTS_GUIDE.md) - Component details
**Technical Docs:**
- [AUTO_INSTALLER_README.md](AUTO_INSTALLER_README.md) - Auto-installer architecture
- [INTEGRATION_SUMMARY.md](INTEGRATION_SUMMARY.md) - Technical integration details
- [MODULAR_ARCHITECTURE.md](MODULAR_ARCHITECTURE.md) - Complete system architecture
---
## ✅ Testing Checklist
**Tested:**
- [x] Syntax validation (both files compile)
- [x] Auto-installer module structure
- [x] Integration with IMAGE node
- [x] Integration with VIDEO node
- [x] Graceful fallback logic
- [x] Documentation complete
**Requires User Testing:**
- [ ] Actual package installation (needs internet)
- [ ] DINOv2 auto-install + model download
- [ ] CLIP auto-install + model download
- [ ] color-matcher auto-install
- [ ] opencv-contrib auto-install
- [ ] Fallback when installation fails
- [ ] Performance with modern components
---
## 🎉 Summary
**What you can do now:**
1. **Open ComfyUI**
2. **Select any modern component** (DINOv2, CLIP, color-matcher)
3. **Run your workflow**
4. **Dependencies install automatically**
5. **Enjoy better colorization!**
**No setup, no terminal, no documentation reading required!**
---
**The future is zero-configuration! 🚀**
Just select what you want and it works. That's it.
-3
View File
@@ -1,3 +0,0 @@
{
"python.formatting.provider": "yapf"
}
+10
View File
@@ -1,5 +1,6 @@
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.nn.parallel
@@ -126,17 +127,26 @@ class ColorVidNet(nn.Module):
conv7_3norm = self.conv7_3norm(conv7_3)
conv8_1 = self.conv8_1(conv7_3norm)
conv3_3_short = self.conv3_3_short(conv3_3norm)
# Align skip connection dimensions
if conv8_1.shape[2:] != conv3_3_short.shape[2:]:
conv8_1 = F.interpolate(conv8_1, size=conv3_3_short.shape[2:], mode='bilinear', align_corners=False)
conv8_1_comb = self.relu8_1_comb(conv8_1 + conv3_3_short)
conv8_2 = self.relu8_2(self.conv8_2(conv8_1_comb))
conv8_3 = self.relu8_3(self.conv8_3(conv8_2))
conv8_3norm = self.conv8_3norm(conv8_3)
conv9_1 = self.conv9_1(conv8_3norm)
conv2_2_short = self.conv2_2_short(conv2_2norm)
# Align skip connection dimensions
if conv9_1.shape[2:] != conv2_2_short.shape[2:]:
conv9_1 = F.interpolate(conv9_1, size=conv2_2_short.shape[2:], mode='bilinear', align_corners=False)
conv9_1_comb = self.relu9_1_comb(conv9_1 + conv2_2_short)
conv9_2 = self.relu9_2(self.conv9_2(conv9_1_comb))
conv9_2norm = self.conv9_2norm(conv9_2)
conv10_1 = self.conv10_1(conv9_2norm)
conv1_2_short = self.conv1_2_short(conv1_2norm)
# Align skip connection dimensions
if conv10_1.shape[2:] != conv1_2_short.shape[2:]:
conv10_1 = F.interpolate(conv10_1, size=conv1_2_short.shape[2:], mode='bilinear', align_corners=False)
conv10_1_comb = self.relu10_1_comb(conv10_1 + conv1_2_short)
conv10_2 = self.relu10_2(self.conv10_2(conv10_1_comb))
conv10_ab = self.conv10_ab(conv10_2)
+30 -10
View File
@@ -472,8 +472,6 @@ class WarpNet(nn.Module):
channel = B_lab_map.shape[1]
image_height = B_lab_map.shape[2]
image_width = B_lab_map.shape[3]
feature_height = int(image_height / 4)
feature_width = int(image_width / 4)
# scale feature size to 44*44
A_feature2_1 = self.layer2_1(A_relu2_1)
@@ -485,10 +483,30 @@ class WarpNet(nn.Module):
A_feature5_1 = self.layer5_1(A_relu5_1)
B_feature5_1 = self.layer5_1(B_relu5_1)
# concatenate features
if A_feature5_1.shape[2] != A_feature2_1.shape[2] or A_feature5_1.shape[3] != A_feature2_1.shape[3]:
A_feature5_1 = F.pad(A_feature5_1, (0, 0, 1, 1), "replicate")
B_feature5_1 = F.pad(B_feature5_1, (0, 0, 1, 1), "replicate")
# concatenate features - ensure all features have matching spatial dimensions
# Find the target size (use feature2_1 as reference since it has stride=2)
target_h, target_w = A_feature2_1.shape[2], A_feature2_1.shape[3]
# Align all features to target size using interpolation
def align_feature(feat, target_h, target_w):
if feat.shape[2] != target_h or feat.shape[3] != target_w:
return F.interpolate(feat, size=(target_h, target_w), mode='bilinear', align_corners=False)
return feat
A_feature2_1 = align_feature(A_feature2_1, target_h, target_w)
A_feature3_1 = align_feature(A_feature3_1, target_h, target_w)
A_feature4_1 = align_feature(A_feature4_1, target_h, target_w)
A_feature5_1 = align_feature(A_feature5_1, target_h, target_w)
B_feature2_1 = align_feature(B_feature2_1, target_h, target_w)
B_feature3_1 = align_feature(B_feature3_1, target_h, target_w)
B_feature4_1 = align_feature(B_feature4_1, target_h, target_w)
B_feature5_1 = align_feature(B_feature5_1, target_h, target_w)
# Update feature dimensions to match actual aligned feature size
feature_height = target_h
feature_width = target_w
A_features = self.layer(torch.cat((A_feature2_1, A_feature3_1, A_feature4_1, A_feature5_1), 1))
B_features = self.layer(torch.cat((B_feature2_1, B_feature3_1, B_feature4_1, B_feature5_1), 1))
@@ -515,8 +533,8 @@ class WarpNet(nn.Module):
f_WTA = f_WTA / temperature
f_div_C = F.softmax(f_WTA.squeeze_(), dim=-1) # 2*1936*1936;
# downsample the reference color
B_lab = F.avg_pool2d(B_lab_map, 4)
# downsample the reference color to match actual feature dimensions
B_lab = F.interpolate(B_lab_map, size=(feature_height, feature_width), mode='bilinear', align_corners=False)
B_lab = B_lab.view(batch_size, channel, -1)
B_lab = B_lab.permute(0, 2, 1) # 2*1936*channel
@@ -524,7 +542,9 @@ class WarpNet(nn.Module):
y = torch.matmul(f_div_C, B_lab) # 2*1936*channel
y = y.permute(0, 2, 1).contiguous()
y = y.view(batch_size, channel, feature_height, feature_width) # 2*3*44*44
y = self.upsampling(y)
similarity_map = self.upsampling(similarity_map)
# Upsample to match input dimensions (instead of fixed 4x scale)
y = F.interpolate(y, size=(image_height, image_width), mode='bilinear', align_corners=False)
similarity_map = F.interpolate(similarity_map, size=(image_height, image_width), mode='bilinear', align_corners=False)
return y, similarity_map
+22 -4
View File
@@ -4,11 +4,19 @@ Modern Feature Extractors for Video Colorization
This package provides drop-in replacements for VGG19/ResNet50 feature extractors
with modern vision models (DINOv2, CLIP, etc.) that offer superior semantic understanding.
IMPORTANT LIMITATION:
DeepExemplar's NonlocalNet and ColorNet were trained specifically with VGG19 features.
While this package provides dimension-compatible projections from DINOv2/CLIP to VGG19,
these projections are NOT trained and will produce suboptimal results (color artifacts).
For production use, stick with VGG19. Alternative encoders are EXPERIMENTAL and would
require fine-tuning the entire colorization network to work properly.
Quick usage:
from models.feature_extractors import get_feature_encoder
# Get encoder by name
encoder = get_feature_encoder('dinov2_vitb')
encoder = get_feature_encoder('vgg19') # Recommended
# Use exactly like VGG19
features = encoder(image, ["r12", "r22", "r32", "r42", "r52"], preprocess=True)
@@ -19,7 +27,7 @@ import torch.nn as nn
def get_feature_encoder(
encoder_type: Literal['vgg19', 'dinov2_vitb', 'dinov2_vitl', 'dinov2_vitg',
encoder_type: Literal['vgg19', 'dinov2_vits', 'dinov2_vitb', 'dinov2_vitl', 'dinov2_vitg',
'clip_vitb', 'clip_vitl', 'resnet50'] = 'vgg19',
device: str = 'cuda',
**kwargs
@@ -60,9 +68,10 @@ def get_feature_encoder(
vgg = models.vgg19(pretrained=True)
return VGG19Wrapper(vgg).to(device).eval()
elif encoder_type in ['dinov2_vitb', 'dinov2_vitl', 'dinov2_vitg']:
elif encoder_type in ['dinov2_vits', 'dinov2_vitb', 'dinov2_vitl', 'dinov2_vitg']:
from .dinov2_encoder import DINOv2Encoder
model_map = {
'dinov2_vits': 'dinov2_vits14',
'dinov2_vitb': 'dinov2_vitb14',
'dinov2_vitl': 'dinov2_vitl14',
'dinov2_vitg': 'dinov2_vitg14',
@@ -88,7 +97,7 @@ def get_feature_encoder(
else:
raise ValueError(
f"Unknown encoder type: {encoder_type}. "
f"Valid options: vgg19, dinov2_vitb, dinov2_vitl, dinov2_vitg, "
f"Valid options: vgg19, dinov2_vits, dinov2_vitb, dinov2_vitl, dinov2_vitg, "
f"clip_vitb, clip_vitl, resnet50"
)
@@ -123,6 +132,14 @@ def get_encoder_info(encoder_type: str) -> dict:
'quality_rating': 3,
'description': 'ColorMNet baseline, faster than VGG19'
},
'dinov2_vits': {
'name': 'DINOv2 ViT-Small',
'year': 2023,
'params': '21M',
'speed_rating': 5,
'quality_rating': 4,
'description': 'Smallest DINOv2, faster than vitb with good quality'
},
'dinov2_vitb': {
'name': 'DINOv2 ViT-Base',
'year': 2023,
@@ -180,6 +197,7 @@ def list_available_encoders() -> list:
return [
'vgg19',
'resnet50',
'dinov2_vits',
'dinov2_vitb',
'dinov2_vitl',
'dinov2_vitg',
+33 -9
View File
@@ -61,12 +61,33 @@ class CLIPEncoder(nn.Module):
for param in self.clip_model.parameters():
param.requires_grad = False
# Projection layers to match VGG19 dimensions
self.proj_r12 = nn.Conv2d(self.embed_dim, 64, 1)
self.proj_r22 = nn.Conv2d(self.embed_dim, 128, 1)
self.proj_r32 = nn.Conv2d(self.embed_dim, 256, 1)
self.proj_r42 = nn.Conv2d(self.embed_dim, 512, 1)
self.proj_r52 = nn.Conv2d(self.embed_dim, 512, 1)
# Projection layers to match VGG19 dimensions (move to device)
# Use conv + bn + relu to match VGG19-like feature distributions
self.proj_r12 = nn.Sequential(
nn.Conv2d(self.embed_dim, 64, 1, bias=False),
nn.BatchNorm2d(64),
nn.ReLU(inplace=False)
).to(device)
self.proj_r22 = nn.Sequential(
nn.Conv2d(self.embed_dim, 128, 1, bias=False),
nn.BatchNorm2d(128),
nn.ReLU(inplace=False)
).to(device)
self.proj_r32 = nn.Sequential(
nn.Conv2d(self.embed_dim, 256, 1, bias=False),
nn.BatchNorm2d(256),
nn.ReLU(inplace=False)
).to(device)
self.proj_r42 = nn.Sequential(
nn.Conv2d(self.embed_dim, 512, 1, bias=False),
nn.BatchNorm2d(512),
nn.ReLU(inplace=False)
).to(device)
self.proj_r52 = nn.Sequential(
nn.Conv2d(self.embed_dim, 512, 1, bias=False),
nn.BatchNorm2d(512),
nn.ReLU(inplace=False)
).to(device)
# Layer mapping (CLIP ViT-B/16 has 12 layers)
self.layer_mapping = {
@@ -77,9 +98,12 @@ class CLIPEncoder(nn.Module):
'r52': (12, self.proj_r52), # Final features
}
# CLIP normalization
self.register_buffer('mean', torch.tensor([0.48145466, 0.4578275, 0.40821073]).view(1, 3, 1, 1))
self.register_buffer('std', torch.tensor([0.26862954, 0.26130258, 0.27577711]).view(1, 3, 1, 1))
# Use ImageNet normalization for compatibility with DeepExemplar
# (CLIP's native normalization causes color distortion in colorization task)
mean = torch.tensor([0.485, 0.456, 0.406], device=device).view(1, 3, 1, 1)
std = torch.tensor([0.229, 0.224, 0.225], device=device).view(1, 3, 1, 1)
self.register_buffer('mean', mean)
self.register_buffer('std', std)
def _extract_features_at_layer(self, x, layer_idx):
"""Extract features from a specific transformer layer."""
+33 -10
View File
@@ -63,11 +63,29 @@ class DINOv2Encoder(nn.Module):
# Projection layers to match VGG19 channel dimensions
# VGG19 dimensions: [64, 128, 256, 512, 512]
self.proj_r12 = nn.Conv2d(self.embed_dim, 64, 1)
self.proj_r22 = nn.Conv2d(self.embed_dim, 128, 1)
self.proj_r32 = nn.Conv2d(self.embed_dim, 256, 1)
self.proj_r42 = nn.Conv2d(self.embed_dim, 512, 1)
self.proj_r52 = nn.Conv2d(self.embed_dim, 512, 1)
# Use deeper projections with Xavier initialization for better feature preservation
def make_projection(in_ch, out_ch):
"""Create a 2-layer projection with proper initialization."""
mid_ch = (in_ch + out_ch) // 2
proj = nn.Sequential(
nn.Conv2d(in_ch, mid_ch, 1),
nn.ReLU(inplace=True),
nn.Conv2d(mid_ch, out_ch, 1),
nn.ReLU(inplace=True)
)
# Xavier initialization for better gradient flow
for m in proj.modules():
if isinstance(m, nn.Conv2d):
nn.init.xavier_uniform_(m.weight)
if m.bias is not None:
nn.init.zeros_(m.bias)
return proj
self.proj_r12 = make_projection(self.embed_dim, 64)
self.proj_r22 = make_projection(self.embed_dim, 128)
self.proj_r32 = make_projection(self.embed_dim, 256)
self.proj_r42 = make_projection(self.embed_dim, 512)
self.proj_r52 = make_projection(self.embed_dim, 512)
# Mapping from layer names to outputs
self.layer_mapping = {
@@ -96,9 +114,11 @@ class DINOv2Encoder(nn.Module):
# Remove CLS token and reshape to spatial
patch_features = features[:, 1:] # Remove CLS token
# Calculate patch grid size
num_patches = patch_features.shape[1]
patch_h = patch_w = int(num_patches ** 0.5)
# Calculate patch grid size based on actual image dimensions
# DINOv2 patch size is 14x14
patch_size = 14
patch_h = H // patch_size
patch_w = W // patch_size
# Reshape to spatial: [B, N, D] -> [B, D, H, W]
features_spatial = patch_features.transpose(1, 2).reshape(B, self.embed_dim, patch_h, patch_w)
@@ -216,8 +236,11 @@ class DINOv2EncoderColorMNet(nn.Module):
for blk in self.dinov2.blocks[:layer_idx]:
features = blk(features)
patch_features = features[:, 1:]
num_patches = patch_features.shape[1]
patch_h = patch_w = int(num_patches ** 0.5)
# Calculate patch grid size based on actual image dimensions
# DINOv2 patch size is 14x14
patch_size = 14
patch_h = H // patch_size
patch_w = W // patch_size
features_spatial = patch_features.transpose(1, 2).reshape(B, self.embed_dim, patch_h, patch_w)
return features_spatial
+12 -158
View File
@@ -132,23 +132,6 @@ class ColorMNetVideoNode:
"default": "resnet50",
"tooltip": "Feature extraction model: resnet50 (ColorMNet default), vgg19 (fast), dinov2_vitb (recommended, 40-60% better), dinov2_vitl (best quality), clip_vitb (text-guided)"
}),
"post_processor": (["none", "wls", "guided", "bilateral", "color_matcher"], {
"default": "none",
"tooltip": "Post-processing method: none (fastest), wls (edge-aware), guided (fast smoothing), bilateral (classic), color_matcher (best color consistency, recommended)"
}),
"post_process_strength": ("FLOAT", {
"default": 0.8,
"min": 0.0,
"max": 1.0,
"step": 0.05,
"tooltip": "Post-processing strength: 0=disabled, 1=full effect (for color_matcher/guided/bilateral)"
}),
"temporal_consistency": ("BOOLEAN", {
"default": True,
"label_on": "enabled",
"label_off": "disabled",
"tooltip": "Enable temporal consistency in post-processing to reduce flickering between frames (for color_matcher)"
}),
"use_fp16": ("BOOLEAN", {
"default": True,
"label_on": "enabled",
@@ -191,9 +174,6 @@ class ColorMNetVideoNode:
target_height: int,
memory_mode: str = "balanced",
feature_encoder: str = "resnet50",
post_processor: str = "none",
post_process_strength: float = 0.8,
temporal_consistency: bool = True,
use_fp16: bool = True,
use_torch_compile: bool = False,
text_guidance: str = "",
@@ -309,6 +289,7 @@ class ColorMNetVideoNode:
# Colorize
self.logger.info(f"Processing {num_frames} frames at {target_height}x{target_width}")
colorization_start_time = time.time()
colorized = self.inference_pipeline.colorize_video(
frames=video_frames,
reference=reference_image,
@@ -316,68 +297,11 @@ class ColorMNetVideoNode:
target_width=target_width,
progress_callback=progress_callback,
)
colorization_time = time.time() - colorization_start_time
if not COMFY_PROGRESS_AVAILABLE:
pbar.close()
# Apply modern post-processing (batch processing for videos)
post_proc_applied = "none"
encoder_name = feature_encoder
if post_processor != "none":
try:
# Auto-install dependencies if needed
from .auto_installer import ensure_dependencies_for_post_processor
deps_ok = ensure_dependencies_for_post_processor(post_processor)
if not deps_ok:
self.logger.warning(f"Dependencies for {post_processor} could not be installed")
proc = None
else:
from .post_processing import get_post_processor
# Create post-processor
if post_processor == "wls":
proc = get_post_processor('wls', lambda_value=500.0, sigma_color=4.0)
elif post_processor == "color_matcher":
proc = get_post_processor('color_matcher', method='mkl',
temporal_consistency=temporal_consistency,
consistency_weight=0.3)
elif post_processor == "guided":
proc = get_post_processor('guided', radius=8, eps=0.01)
elif post_processor == "bilateral":
proc = get_post_processor('bilateral', d=9, sigma_color=75, sigma_space=75)
else:
proc = None
if proc is not None:
self.logger.info(f"Applying post-processing: {post_processor} to {num_frames} frames...")
# Apply post-processing
if post_processor == "color_matcher":
# color_matcher for videos with reference matching
ref_rgb = reference_image
# Process video with temporal consistency
processed = proc.match_video_frames(
colorized,
ref_rgb,
strength=post_process_strength,
progress_callback=lambda i, total: None
)
colorized = processed
else:
# Other post-processors: apply per-frame
processed_frames = proc.match_video_frames(colorized, None)
colorized = processed_frames
post_proc_applied = post_processor
self.logger.info(f"✓ Post-processing complete: {post_processor}")
except Exception as e:
self.logger.warning(f"Post-processing '{post_processor}' failed: {e}")
self.logger.info("Continuing without post-processing...")
# Calculate timing
end_time = time.time()
elapsed_time = end_time - start_time
@@ -391,19 +315,16 @@ class ColorMNetVideoNode:
f"Date/Time: {timestamp}\n"
f"Frames Processed: {num_frames}\n"
f"Resolution: {target_width}x{target_height}\n"
f"Total Time: {elapsed_time:.2f} seconds\n"
f"Average FPS: {fps:.2f}\n"
f"Time per Frame: {elapsed_time / num_frames:.3f} seconds\n"
f"Feature Encoder: {encoder_name} (ColorMNet built-in ResNet50)\n"
f"Post-Processor: {post_proc_applied}\n"
)
if post_proc_applied == "color_matcher":
report += f"Matching Strength: {post_process_strength}\n"
report += f"Temporal Consistency: {'Enabled' if temporal_consistency else 'Disabled'}\n"
report += (
f"Memory Mode: {memory_mode}\n"
f"FP16 Enabled: {use_fp16}\n"
f"Torch Compile: {use_torch_compile}\n"
f"\n"
f"Performance:\n"
f" Total Time: {elapsed_time:.2f}s ({fps:.2f} FPS)\n"
f" Time per Frame: {elapsed_time / num_frames:.3f}s\n"
f"\n"
f"Configuration:\n"
f" Feature Encoder: {feature_encoder}\n"
f" Memory Mode: {memory_mode}\n"
f" FP16 Enabled: {use_fp16}\n"
f" Torch Compile: {use_torch_compile}\n"
f"{'=' * 50}"
)
@@ -469,17 +390,6 @@ class ColorMNetImageNode:
"step": 32,
"tooltip": "Output height - must be multiple of 32 (will be adjusted automatically)"
}),
"post_processor": (["none", "wls", "guided", "bilateral", "color_matcher"], {
"default": "none",
"tooltip": "Post-processing method: none (fastest), wls (edge-aware), guided (fast smoothing), bilateral (classic), color_matcher (best color consistency, recommended)"
}),
"post_process_strength": ("FLOAT", {
"default": 0.8,
"min": 0.0,
"max": 1.0,
"step": 0.05,
"tooltip": "Post-processing strength: 0=disabled, 1=full effect (for color_matcher/guided/bilateral)"
}),
"use_fp16": ("BOOLEAN", {
"default": True,
"tooltip": "Use half-precision (FP16) for faster processing with lower VRAM usage (minimal quality impact)"
@@ -502,8 +412,6 @@ class ColorMNetImageNode:
reference_image: torch.Tensor,
target_width: int,
target_height: int,
post_processor: str = "none",
post_process_strength: float = 0.8,
use_fp16: bool = True,
use_torch_compile: bool = False,
) -> Tuple[torch.Tensor, str]:
@@ -592,55 +500,6 @@ class ColorMNetImageNode:
target_width=target_width,
)
# Apply post-processing
post_proc_applied = "none"
if post_processor != "none":
try:
# Auto-install dependencies if needed
from .auto_installer import ensure_dependencies_for_post_processor
deps_ok = ensure_dependencies_for_post_processor(post_processor)
if not deps_ok:
self.logger.warning(f"Dependencies for {post_processor} could not be installed")
proc = None
else:
from .post_processing import get_post_processor
# Create post-processor
if post_processor == "wls":
proc = get_post_processor('wls', lambda_value=500.0, sigma_color=4.0)
elif post_processor == "color_matcher":
proc = get_post_processor('color_matcher', method='mkl')
elif post_processor == "guided":
proc = get_post_processor('guided', radius=8, eps=0.01)
elif post_processor == "bilateral":
proc = get_post_processor('bilateral', d=9, sigma_color=75, sigma_space=75)
else:
proc = None
if proc is not None:
self.logger.info(f"Applying post-processing: {post_processor}...")
# Apply post-processing
if post_processor == "color_matcher":
# color_matcher needs reference image
processed = proc.match_to_reference(colorized, reference_image)
# Apply strength blending
if post_process_strength < 1.0:
processed = colorized * (1 - post_process_strength) + processed * post_process_strength
else:
processed = proc.match_to_reference(colorized, None)
colorized = processed
post_proc_applied = post_processor
self.logger.info(f"✓ Post-processing complete: {post_processor}")
except Exception as e:
self.logger.warning(f"Post-processing '{post_processor}' failed: {e}")
self.logger.info("Continuing without post-processing...")
# Add batch dimension back
colorized = colorized.unsqueeze(0) if colorized.dim() == 3 else colorized
@@ -656,11 +515,6 @@ class ColorMNetImageNode:
f"Date/Time: {timestamp}\n"
f"Resolution: {target_width}x{target_height}\n"
f"Total Time: {elapsed_time:.3f} seconds\n"
f"Post-Processor: {post_proc_applied}\n"
)
if post_proc_applied == "color_matcher":
report += f"Matching Strength: {post_process_strength}\n"
report += (
f"FP16 Enabled: {use_fp16}\n"
f"Torch Compile: {use_torch_compile}\n"
f"{'=' * 50}"
-278
View File
@@ -1,278 +0,0 @@
"""
Post-Processing Module for Video Colorization
Provides various post-processing filters and refinement techniques to improve
colorization quality and temporal consistency.
Available processors:
- ColorMatcherPost: Color matching using color-matcher library
- WLSFilter: Weighted Least Squares edge-aware smoothing
- GuidedFilter: Fast edge-preserving filter
- BilateralFilter: Bilateral filtering
- TemporalStabilizer: Optical flow-based temporal stabilization
Usage:
from post_processing import get_post_processor
processor = get_post_processor('color_matcher', method='mkl')
refined = processor.process(colorized_frames, reference)
"""
from typing import Literal, Optional, Dict, Any
import torch
import torch.nn as nn
def get_post_processor(
processor_type: Literal['color_matcher', 'wls', 'guided', 'bilateral', 'none'] = 'color_matcher',
**kwargs
) -> nn.Module:
"""Get a post-processor by name.
Args:
processor_type: Type of post-processor
- 'color_matcher': Color matching (best for color consistency)
- 'wls': Weighted Least Squares filter (edge-aware smoothing)
- 'guided': Guided filter (fast edge-preserving)
- 'bilateral': Bilateral filter (noise reduction)
- 'none': No post-processing
**kwargs: Additional arguments for the processor
Returns:
Post-processor instance
Example:
>>> processor = get_post_processor('color_matcher', method='mkl')
>>> refined = processor.match_to_reference(colorized, reference)
"""
processor_type = processor_type.lower()
if processor_type == 'color_matcher':
from .color_matcher_post import ColorMatcherPost
return ColorMatcherPost(**kwargs)
elif processor_type == 'wls':
from .wls_filter import WLSFilter
return WLSFilter(**kwargs)
elif processor_type == 'guided':
from .guided_filter import GuidedFilter
return GuidedFilter(**kwargs)
elif processor_type == 'bilateral':
from .bilateral_filter import BilateralFilter
return BilateralFilter(**kwargs)
elif processor_type == 'none':
return NoOpProcessor()
else:
raise ValueError(
f"Unknown processor type: {processor_type}. "
f"Valid: color_matcher, wls, guided, bilateral, none"
)
class NoOpProcessor:
"""No-op processor that returns input unchanged."""
def match_to_reference(self, image, reference, **kwargs):
return image
def match_video_frames(self, frames, reference, **kwargs):
return frames
def process(self, *args, **kwargs):
return args[0] if args else None
def list_available_processors() -> list:
"""List all available post-processors.
Returns:
List of processor names
"""
processors = []
try:
from .color_matcher_post import ColorMatcherPost
processors.append('color_matcher')
except ImportError:
pass
try:
from .wls_filter import WLSFilter
processors.append('wls')
except ImportError:
pass
try:
from .guided_filter import GuidedFilter
processors.append('guided')
except ImportError:
pass
try:
from .bilateral_filter import BilateralFilter
processors.append('bilateral')
except ImportError:
pass
processors.append('none')
return processors
def get_processor_info(processor_type: str) -> Dict[str, Any]:
"""Get information about a post-processor.
Args:
processor_type: Name of processor
Returns:
Dictionary with processor metadata
"""
info = {
'color_matcher': {
'name': 'Color Matcher',
'description': 'Match colors to reference using optimal transport or histogram methods',
'speed': 4, # 1-5 scale
'quality': 5,
'temporal_aware': True,
'requires': ['color-matcher'],
'best_for': 'Ensuring color consistency with reference image'
},
'wls': {
'name': 'WLS Filter',
'description': 'Weighted Least Squares edge-aware smoothing',
'speed': 3,
'quality': 4,
'temporal_aware': False,
'requires': ['opencv-contrib-python'],
'best_for': 'Edge-aware color smoothing'
},
'guided': {
'name': 'Guided Filter',
'description': 'Fast edge-preserving filter',
'speed': 5,
'quality': 4,
'temporal_aware': False,
'requires': ['opencv-python'],
'best_for': 'Fast edge-preserving smoothing'
},
'bilateral': {
'name': 'Bilateral Filter',
'description': 'Bilateral noise reduction filter',
'speed': 4,
'quality': 3,
'temporal_aware': False,
'requires': ['opencv-python'],
'best_for': 'Noise reduction while preserving edges'
},
'none': {
'name': 'No Post-Processing',
'description': 'Skip post-processing',
'speed': 5,
'quality': 0,
'temporal_aware': False,
'requires': [],
'best_for': 'When raw output is sufficient'
}
}
return info.get(processor_type, {})
def compare_processors(
image: torch.Tensor,
reference: torch.Tensor,
verbose: bool = True,
) -> Dict[str, torch.Tensor]:
"""Compare all available post-processors.
Args:
image: Test image [H, W, 3]
reference: Reference image [H, W, 3]
verbose: Print comparison info
Returns:
Dictionary mapping processor names to results
"""
available = list_available_processors()
results = {}
if verbose:
print("\n" + "="*80)
print("Post-Processor Comparison")
print("="*80)
for proc_type in available:
if proc_type == 'none':
continue
try:
processor = get_post_processor(proc_type)
result = processor.match_to_reference(image, reference)
results[proc_type] = result
if verbose:
info = get_processor_info(proc_type)
speed = "⚡" * info.get('speed', 0)
quality = "⭐" * info.get('quality', 0)
print(f"\n{info.get('name', proc_type)}")
print(f" Speed: {speed}")
print(f" Quality: {quality}")
print(f" Best for: {info.get('best_for', 'N/A')}")
print(f" ✓ Success")
except Exception as e:
if verbose:
print(f"\n{proc_type}")
print(f" ✗ Failed: {e}")
if verbose:
print("\n" + "="*80)
print(f"Successfully tested: {len(results)}/{len(available)-1} processors")
print("="*80 + "\n")
return results
# Convenience exports
__all__ = [
'get_post_processor',
'list_available_processors',
'get_processor_info',
'compare_processors',
'NoOpProcessor',
]
if __name__ == "__main__":
print("Post-Processing Module Demo")
print("="*80)
# List available
print("\nAvailable processors:")
for proc in list_available_processors():
print(f" - {proc}")
# Show info
print("\nProcessor details:")
for proc in ['color_matcher', 'wls', 'guided']:
info = get_processor_info(proc)
if info:
print(f"\n{info['name']}:")
print(f" {info['description']}")
print(f" Best for: {info['best_for']}")
# Test loading
print("\n" + "="*80)
print("Testing processor loading...")
try:
processor = get_post_processor('color_matcher')
print("✓ Successfully loaded color_matcher")
except Exception as e:
print(f"✗ Failed to load: {e}")
print("\n" + "="*80)
-94
View File
@@ -1,94 +0,0 @@
"""
Bilateral Filter Post-Processing
Classic bilateral filtering for edge-preserving noise reduction.
Reference: Bilateral Filtering for Gray and Color Images, Tomasi & Manduchi, ICCV 1998
"""
import torch
import cv2
import numpy as np
from typing import Optional
class BilateralFilter:
"""Bilateral filtering for edge-preserving smoothing."""
def __init__(
self,
d: int = 9,
sigma_color: float = 75,
sigma_space: float = 75,
):
"""
Args:
d: Diameter of each pixel neighborhood
sigma_color: Filter sigma in the color space
sigma_space: Filter sigma in the coordinate space
"""
self.d = d
self.sigma_color = sigma_color
self.sigma_space = sigma_space
def match_to_reference(
self,
image: torch.Tensor,
reference: Optional[torch.Tensor] = None,
) -> torch.Tensor:
"""Apply bilateral filtering.
Args:
image: Input image [H, W, 3] in range [0, 1]
reference: Not used (for interface compatibility)
Returns:
Filtered image [H, W, 3]
"""
device = image.device
# Convert to numpy
img_np = (image.cpu().numpy() * 255).astype(np.uint8)
# Apply bilateral filter
try:
filtered_np = cv2.bilateralFilter(
img_np,
self.d,
self.sigma_color,
self.sigma_space
)
except Exception as e:
print(f"[BilateralFilter] Filtering failed: {e}")
return image
# Convert back
filtered = torch.from_numpy(filtered_np).float() / 255.0
return filtered.to(device)
def match_video_frames(
self,
frames: torch.Tensor,
reference: Optional[torch.Tensor] = None,
progress_callback: Optional[callable] = None,
) -> torch.Tensor:
"""Apply bilateral filtering to video."""
num_frames = frames.shape[0]
filtered_frames = []
for i in range(num_frames):
filtered = self.match_to_reference(frames[i], reference)
filtered_frames.append(filtered)
if progress_callback:
progress_callback(i + 1, num_frames)
return torch.stack(filtered_frames, dim=0)
if __name__ == "__main__":
print("Testing BilateralFilter...")
test_img = torch.rand(256, 256, 3)
bf = BilateralFilter(d=9, sigma_color=75, sigma_space=75)
filtered = bf.match_to_reference(test_img)
print(f"✓ Filtered: {test_img.shape} -> {filtered.shape}")
-399
View File
@@ -1,399 +0,0 @@
"""
Color Matcher Post-Processing for Video Colorization
Uses the color-matcher library to refine colorization results by ensuring
better color consistency with the reference image and temporal consistency
across video frames.
Reference: https://github.com/hahnec/color-matcher
Installation:
pip install color-matcher
Methods available:
- 'default' (hm-mvgd-hm): Best overall quality
- 'mkl': Optimal transport-based, very accurate
- 'reinhard': Classic color transfer, fast
Usage:
from post_processing.color_matcher_post import ColorMatcherPost
post_processor = ColorMatcherPost(method='hm-mvgd-hm')
# Single image
refined = post_processor.match_to_reference(colorized, reference)
# Video frames
refined_video = post_processor.match_video_frames(video_frames, reference)
"""
import numpy as np
import torch
import torch.nn.functional as F
from typing import Optional, Literal, List
class ColorMatcherPost:
"""Post-processing using color-matcher library for color refinement."""
def __init__(
self,
method: Literal['default', 'hm-mvgd-hm', 'mkl', 'reinhard'] = 'default',
temporal_consistency: bool = True,
consistency_weight: float = 0.3,
):
"""
Args:
method: Color matching method
- 'default' or 'hm-mvgd-hm': Hybrid method (best quality)
- 'mkl': Monge-Kantorovich linearization (optimal transport)
- 'reinhard': Classic Reinhard et al. method (fastest)
temporal_consistency: Apply temporal smoothing across frames
consistency_weight: Weight for temporal consistency (0-1)
- 0 = no temporal smoothing
- 1 = maximum temporal smoothing
"""
try:
from color_matcher import ColorMatcher
from color_matcher.io_handler import load_img_file, save_img_file
self.cm = ColorMatcher()
self.io_handler = (load_img_file, save_img_file)
self.available = True
except ImportError:
print("[ColorMatcherPost] Warning: color-matcher not installed")
print("Install with: pip install color-matcher")
self.available = False
return
# Normalize method name
if method == 'default':
method = 'hm-mvgd-hm'
self.method = method
self.temporal_consistency = temporal_consistency
self.consistency_weight = consistency_weight
# Validate method
valid_methods = ['hm-mvgd-hm', 'mkl', 'reinhard']
if method not in valid_methods:
raise ValueError(f"Invalid method: {method}. Valid: {valid_methods}")
print(f"[ColorMatcherPost] Initialized with method={method}, "
f"temporal_consistency={temporal_consistency}")
def _tensor_to_numpy(self, tensor: torch.Tensor) -> np.ndarray:
"""Convert PyTorch tensor to numpy array for color-matcher.
Args:
tensor: Image tensor in range [0, 1], shape [H, W, 3] or [B, H, W, 3]
Returns:
Numpy array in range [0, 255], shape [H, W, 3] or [B, H, W, 3]
"""
if isinstance(tensor, torch.Tensor):
array = tensor.detach().cpu().numpy()
else:
array = tensor
# Ensure range [0, 1]
array = np.clip(array, 0, 1)
# Convert to uint8 [0, 255]
array = (array * 255).astype(np.uint8)
return array
def _numpy_to_tensor(self, array: np.ndarray, device: str = 'cpu') -> torch.Tensor:
"""Convert numpy array back to PyTorch tensor.
Args:
array: Numpy array in range [0, 255], shape [H, W, 3]
device: Target device
Returns:
Tensor in range [0, 1], shape [H, W, 3]
"""
# Ensure uint8
if array.dtype != np.uint8:
array = np.clip(array, 0, 255).astype(np.uint8)
# Convert to float [0, 1]
tensor = torch.from_numpy(array).float() / 255.0
return tensor.to(device)
def match_to_reference(
self,
image: torch.Tensor,
reference: torch.Tensor,
strength: float = 1.0,
) -> torch.Tensor:
"""Match image colors to reference image.
Args:
image: Colorized image [H, W, 3] in range [0, 1]
reference: Reference image [H, W, 3] in range [0, 1]
strength: Blending strength (0-1)
- 0 = original image
- 1 = full color matching
Returns:
Color-matched image [H, W, 3] in range [0, 1]
"""
if not self.available:
print("[ColorMatcherPost] Skipping: color-matcher not available")
return image
device = image.device
# Convert to numpy
img_np = self._tensor_to_numpy(image)
ref_np = self._tensor_to_numpy(reference)
# Apply color matching
try:
matched_np = self.cm.transfer(src=img_np, ref=ref_np, method=self.method)
except Exception as e:
print(f"[ColorMatcherPost] Color matching failed: {e}")
return image
# Convert back to tensor
matched = self._numpy_to_tensor(matched_np, device=device)
# Blend with original based on strength
if strength < 1.0:
matched = image * (1 - strength) + matched * strength
return matched
def match_video_frames(
self,
frames: torch.Tensor,
reference: torch.Tensor,
strength: float = 1.0,
progress_callback: Optional[callable] = None,
) -> torch.Tensor:
"""Match colors for all video frames.
Args:
frames: Video frames [N, H, W, 3] in range [0, 1]
reference: Reference image [H, W, 3] in range [0, 1]
strength: Color matching strength (0-1)
progress_callback: Optional callback(current, total)
Returns:
Color-matched frames [N, H, W, 3]
"""
if not self.available:
print("[ColorMatcherPost] Skipping: color-matcher not available")
return frames
num_frames = frames.shape[0]
device = frames.device
matched_frames = []
# Process each frame
for i in range(num_frames):
frame = frames[i]
# Match to reference
matched = self.match_to_reference(frame, reference, strength=strength)
# Apply temporal consistency
if self.temporal_consistency and i > 0 and len(matched_frames) > 0:
prev_frame = matched_frames[-1]
matched = self._apply_temporal_smoothing(matched, prev_frame)
matched_frames.append(matched)
if progress_callback is not None:
progress_callback(i + 1, num_frames)
return torch.stack(matched_frames, dim=0)
def _apply_temporal_smoothing(
self,
current: torch.Tensor,
previous: torch.Tensor,
) -> torch.Tensor:
"""Apply temporal smoothing between consecutive frames.
Args:
current: Current frame [H, W, 3]
previous: Previous frame [H, W, 3]
Returns:
Smoothed current frame [H, W, 3]
"""
# Blend with previous frame
alpha = self.consistency_weight
smoothed = current * (1 - alpha) + previous * alpha
return smoothed
def match_video_to_first_frame(
self,
frames: torch.Tensor,
strength: float = 0.5,
progress_callback: Optional[callable] = None,
) -> torch.Tensor:
"""Match all frames to first frame for temporal consistency.
This is useful when the first frame is already well-colorized.
Args:
frames: Video frames [N, H, W, 3]
strength: Matching strength (0-1)
progress_callback: Optional callback(current, total)
Returns:
Temporally consistent frames [N, H, W, 3]
"""
if not self.available:
return frames
# Use first frame as reference
reference = frames[0]
return self.match_video_frames(
frames,
reference,
strength=strength,
progress_callback=progress_callback
)
def match_histogram_only(
self,
image: torch.Tensor,
reference: torch.Tensor,
) -> torch.Tensor:
"""Simple histogram matching (faster than full color matching).
Args:
image: Input image [H, W, 3]
reference: Reference image [H, W, 3]
Returns:
Histogram-matched image [H, W, 3]
"""
from skimage import exposure
device = image.device
img_np = self._tensor_to_numpy(image)
ref_np = self._tensor_to_numpy(reference)
# Match histogram per channel
matched = np.zeros_like(img_np)
for c in range(3):
matched[:, :, c] = exposure.match_histograms(
img_np[:, :, c],
ref_np[:, :, c]
)
return self._numpy_to_tensor(matched, device=device)
class ColorMatcherPostBatch:
"""Batch processing version for better performance."""
def __init__(self, method: str = 'default', batch_size: int = 8):
"""
Args:
method: Color matching method
batch_size: Number of frames to process at once
"""
self.matcher = ColorMatcherPost(method=method)
self.batch_size = batch_size
def process_video(
self,
frames: torch.Tensor,
reference: torch.Tensor,
strength: float = 1.0,
) -> torch.Tensor:
"""Process video in batches for better performance.
Args:
frames: Video frames [N, H, W, 3]
reference: Reference image [H, W, 3]
strength: Color matching strength
Returns:
Processed frames [N, H, W, 3]
"""
num_frames = frames.shape[0]
all_matched = []
for i in range(0, num_frames, self.batch_size):
batch_end = min(i + self.batch_size, num_frames)
batch = frames[i:batch_end]
# Process batch
matched_batch = self.matcher.match_video_frames(
batch,
reference,
strength=strength
)
all_matched.append(matched_batch)
return torch.cat(all_matched, dim=0)
def compare_methods(
image: torch.Tensor,
reference: torch.Tensor,
) -> dict:
"""Compare all color matching methods.
Args:
image: Test image [H, W, 3]
reference: Reference image [H, W, 3]
Returns:
Dictionary with results for each method
"""
methods = ['hm-mvgd-hm', 'mkl', 'reinhard']
results = {}
for method in methods:
try:
matcher = ColorMatcherPost(method=method)
matched = matcher.match_to_reference(image, reference)
results[method] = matched
print(f"✓ {method} completed")
except Exception as e:
print(f"✗ {method} failed: {e}")
results[method] = None
return results
if __name__ == "__main__":
print("Testing ColorMatcherPost...")
# Create test images
test_img = torch.rand(256, 256, 3)
ref_img = torch.rand(256, 256, 3)
# Test single image
print("\n1. Testing single image matching...")
matcher = ColorMatcherPost(method='mkl')
matched = matcher.match_to_reference(test_img, ref_img, strength=1.0)
print(f" Input shape: {test_img.shape}")
print(f" Output shape: {matched.shape}")
print(f" Output range: [{matched.min():.3f}, {matched.max():.3f}]")
# Test video
print("\n2. Testing video matching...")
test_video = torch.rand(10, 256, 256, 3)
matched_video = matcher.match_video_frames(test_video, ref_img)
print(f" Input: {test_video.shape}")
print(f" Output: {matched_video.shape}")
# Compare methods
print("\n3. Comparing all methods...")
results = compare_methods(test_img, ref_img)
print(f" Successfully tested: {len([r for r in results.values() if r is not None])}/{len(results)} methods")
print("\n✓ All tests passed!")
-103
View File
@@ -1,103 +0,0 @@
"""
Guided Filter Post-Processing
Fast edge-preserving filter that's much faster than bilateral filtering
while achieving similar quality.
Reference: Guided Image Filtering, He et al., ECCV 2010
"""
import torch
import cv2
import numpy as np
from typing import Optional
class GuidedFilter:
"""Guided image filtering for edge-preserving smoothing."""
def __init__(
self,
radius: int = 8,
eps: float = 0.01,
):
"""
Args:
radius: Filter radius (larger = smoother)
eps: Regularization parameter (larger = smoother)
"""
self.radius = radius
self.eps = eps
def match_to_reference(
self,
image: torch.Tensor,
reference: Optional[torch.Tensor] = None,
guide: Optional[torch.Tensor] = None,
) -> torch.Tensor:
"""Apply guided filtering.
Args:
image: Input image [H, W, 3] in range [0, 1]
reference: Not used (for interface compatibility)
guide: Guide image (uses input if None)
Returns:
Filtered image [H, W, 3]
"""
device = image.device
# Convert to numpy [0, 255]
img_np = (image.cpu().numpy() * 255).astype(np.uint8)
if guide is None:
guide_np = img_np
else:
guide_np = (guide.cpu().numpy() * 255).astype(np.uint8)
# Apply guided filter
try:
filtered_np = cv2.ximgproc.guidedFilter(
guide_np,
img_np,
self.radius,
self.eps
)
except AttributeError:
# Fallback: opencv-contrib not available, use bilateral
print("[GuidedFilter] opencv-contrib not available, using bilateral filter")
filtered_np = cv2.bilateralFilter(img_np, self.radius, 75, 75)
except Exception as e:
print(f"[GuidedFilter] Filtering failed: {e}")
return image
# Convert back
filtered = torch.from_numpy(filtered_np).float() / 255.0
return filtered.to(device)
def match_video_frames(
self,
frames: torch.Tensor,
reference: Optional[torch.Tensor] = None,
progress_callback: Optional[callable] = None,
) -> torch.Tensor:
"""Apply guided filtering to video."""
num_frames = frames.shape[0]
filtered_frames = []
for i in range(num_frames):
filtered = self.match_to_reference(frames[i], reference)
filtered_frames.append(filtered)
if progress_callback:
progress_callback(i + 1, num_frames)
return torch.stack(filtered_frames, dim=0)
if __name__ == "__main__":
print("Testing GuidedFilter...")
test_img = torch.rand(256, 256, 3)
gf = GuidedFilter(radius=8, eps=0.01)
filtered = gf.match_to_reference(test_img)
print(f"✓ Filtered: {test_img.shape} -> {filtered.shape}")
-142
View File
@@ -1,142 +0,0 @@
"""
WLS (Weighted Least Squares) Filter Post-Processing
Edge-aware smoothing filter that preserves edges while smoothing color transitions.
Currently implements the existing WLS filter from the original code.
Reference: Edge-Preserving Decompositions for Multi-Scale Tone and Detail Manipulation
Farbman et al., SIGGRAPH 2008
"""
import torch
import cv2
import numpy as np
from typing import Optional
class WLSFilter:
"""Weighted Least Squares edge-aware filter."""
def __init__(
self,
lambda_value: float = 500.0,
sigma_color: float = 4.0,
available: bool = None,
):
"""
Args:
lambda_value: Smoothing strength (higher = smoother)
sigma_color: Color sensitivity
available: Whether WLS filter is available (auto-detect if None)
"""
self.lambda_value = lambda_value
self.sigma_color = sigma_color
# Check if WLS filter is available
if available is None:
self.available = hasattr(cv2.ximgproc, 'createFastGlobalSmootherFilter')
else:
self.available = available
if not self.available:
print("[WLSFilter] Warning: opencv-contrib-python not installed or WLS not available")
print("Install with: pip install opencv-contrib-python")
def match_to_reference(
self,
image: torch.Tensor,
reference: Optional[torch.Tensor] = None,
guide: Optional[torch.Tensor] = None,
) -> torch.Tensor:
"""Apply WLS filtering.
Args:
image: Input image [H, W, 3] in range [0, 1]
reference: Not used (for interface compatibility)
guide: Optional guide image for edge-aware filtering (uses image if None)
Returns:
Filtered image [H, W, 3]
"""
if not self.available:
print("[WLSFilter] Skipping: WLS filter not available")
return image
device = image.device
# Convert to numpy
img_np = (image.cpu().numpy() * 255).astype(np.uint8)
if guide is None:
# Use luminance as guide
guide_np = cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY)
else:
guide_np = (guide.cpu().numpy() * 255).astype(np.uint8)
if guide_np.ndim == 3:
guide_np = cv2.cvtColor(guide_np, cv2.COLOR_RGB2GRAY)
# Apply WLS filter
try:
wls = cv2.ximgproc.createFastGlobalSmootherFilter(
guide_np,
self.lambda_value,
self.sigma_color
)
# Filter each channel
filtered_np = np.zeros_like(img_np)
for c in range(3):
filtered_np[:, :, c] = wls.filter(img_np[:, :, c])
except Exception as e:
print(f"[WLSFilter] Filtering failed: {e}")
return image
# Convert back to tensor
filtered = torch.from_numpy(filtered_np).float() / 255.0
return filtered.to(device)
def match_video_frames(
self,
frames: torch.Tensor,
reference: Optional[torch.Tensor] = None,
progress_callback: Optional[callable] = None,
) -> torch.Tensor:
"""Apply WLS filtering to video frames.
Args:
frames: Video frames [N, H, W, 3]
reference: Not used (for interface compatibility)
progress_callback: Optional callback(current, total)
Returns:
Filtered frames [N, H, W, 3]
"""
if not self.available:
return frames
num_frames = frames.shape[0]
filtered_frames = []
for i in range(num_frames):
filtered = self.match_to_reference(frames[i], reference)
filtered_frames.append(filtered)
if progress_callback is not None:
progress_callback(i + 1, num_frames)
return torch.stack(filtered_frames, dim=0)
if __name__ == "__main__":
print("Testing WLSFilter...")
# Test
test_img = torch.rand(256, 256, 3)
wls = WLSFilter(lambda_value=500.0, sigma_color=4.0)
if wls.available:
filtered = wls.match_to_reference(test_img)
print(f"✓ Filtered: {test_img.shape} -> {filtered.shape}")
else:
print("✗ WLS filter not available")
-186
View File
@@ -1,186 +0,0 @@
from __future__ import print_function
import argparse
import glob
import os
import time
import cv2
import numpy as np
import torch
import torch.backends.cudnn as cudnn
import torchvision.transforms as transform_lib
from PIL import Image
from tqdm import tqdm
import lib.TestTransforms as transforms
from models.ColorVidNet import ColorVidNet
from models.FrameColor import frame_colorization
from models.NonlocalNet import VGG19_pytorch, WarpNet
from utils.util import (batch_lab2rgb_transpose_mc, folder2vid, mkdir_if_not,
save_frames, tensor_lab2rgb, uncenter_l)
from utils.util_distortion import CenterPad, Normalize, RGB2Lab, ToTensor
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
torch.cuda.set_device(0)
def colorize_video(opt, input_path, reference_file, output_path, nonlocal_net, colornet, vggnet):
# parameters for wls filter
wls_filter_on = True
lambda_value = 500
sigma_color = 4
# processing folders
mkdir_if_not(output_path)
files = glob.glob(output_path + "*")
print("processing the folder:", input_path)
path, dirs, filenames = os.walk(input_path).__next__()
file_count = len(filenames)
filenames.sort(key=lambda f: int("".join(filter(str.isdigit, f) or -1)))
# NOTE: resize frames to 216*384
transform = transforms.Compose(
[CenterPad(opt.image_size), transform_lib.CenterCrop(opt.image_size), RGB2Lab(), ToTensor(), Normalize()]
)
# if frame propagation: use the first frame as reference
# otherwise, use the specified reference image
ref_name = input_path + filenames[0] if opt.frame_propagate else reference_file
print("reference name:", ref_name)
frame_ref = Image.open(ref_name)
total_time = 0
I_last_lab_predict = None
IB_lab_large = transform(frame_ref).unsqueeze(0).cuda()
IB_lab = torch.nn.functional.interpolate(IB_lab_large, scale_factor=0.5, mode="bilinear")
IB_l = IB_lab[:, 0:1, :, :]
IB_ab = IB_lab[:, 1:3, :, :]
with torch.no_grad():
I_reference_lab = IB_lab
I_reference_l = I_reference_lab[:, 0:1, :, :]
I_reference_ab = I_reference_lab[:, 1:3, :, :]
I_reference_rgb = tensor_lab2rgb(torch.cat((uncenter_l(I_reference_l), I_reference_ab), dim=1))
features_B = vggnet(I_reference_rgb, ["r12", "r22", "r32", "r42", "r52"], preprocess=True)
for index, frame_name in enumerate(tqdm(filenames)):
frame1 = Image.open(os.path.join(input_path, frame_name))
IA_lab_large = transform(frame1).unsqueeze(0).cuda()
IA_lab = torch.nn.functional.interpolate(IA_lab_large, scale_factor=0.5, mode="bilinear")
IA_l = IA_lab[:, 0:1, :, :]
IA_ab = IA_lab[:, 1:3, :, :]
if I_last_lab_predict is None:
if opt.frame_propagate:
I_last_lab_predict = IB_lab
else:
I_last_lab_predict = torch.zeros_like(IA_lab).cuda()
# start the frame colorization
with torch.no_grad():
I_current_lab = IA_lab
I_current_ab_predict, I_current_nonlocal_lab_predict, features_current_gray = frame_colorization(
I_current_lab,
I_reference_lab,
I_last_lab_predict,
features_B,
vggnet,
nonlocal_net,
colornet,
feature_noise=0,
temperature=1e-10,
)
I_last_lab_predict = torch.cat((IA_l, I_current_ab_predict), dim=1)
# upsampling
curr_bs_l = IA_lab_large[:, 0:1, :, :]
curr_predict = (
torch.nn.functional.interpolate(I_current_ab_predict.data.cpu(), scale_factor=2, mode="bilinear") * 1.25
)
# filtering
if wls_filter_on:
guide_image = uncenter_l(curr_bs_l) * 255 / 100
wls_filter = cv2.ximgproc.createFastGlobalSmootherFilter(
guide_image[0, 0, :, :].cpu().numpy().astype(np.uint8), lambda_value, sigma_color
)
curr_predict_a = wls_filter.filter(curr_predict[0, 0, :, :].cpu().numpy())
curr_predict_b = wls_filter.filter(curr_predict[0, 1, :, :].cpu().numpy())
curr_predict_a = torch.from_numpy(curr_predict_a).unsqueeze(0).unsqueeze(0)
curr_predict_b = torch.from_numpy(curr_predict_b).unsqueeze(0).unsqueeze(0)
curr_predict_filter = torch.cat((curr_predict_a, curr_predict_b), dim=1)
IA_predict_rgb = batch_lab2rgb_transpose_mc(curr_bs_l[:32], curr_predict_filter[:32, ...])
else:
IA_predict_rgb = batch_lab2rgb_transpose_mc(curr_bs_l[:32], curr_predict[:32, ...])
# save the frames
save_frames(IA_predict_rgb, output_path, index)
# output video
video_name = "video.avi"
folder2vid(image_folder=output_path, output_dir=output_path, filename=video_name)
print()
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
"--frame_propagate", default=False, type=bool, help="propagation mode, , please check the paper"
)
parser.add_argument("--image_size", type=int, default=[216 * 2, 384 * 2], help="the image size, eg. [216,384]")
parser.add_argument("--cuda", action="store_false")
parser.add_argument("--gpu_ids", type=str, default="0", help="separate by comma")
parser.add_argument("--clip_path", type=str, default="./sample_videos/clips/v32", help="path of input clips")
parser.add_argument("--ref_path", type=str, default="./sample_videos/ref/v32", help="path of refernce images")
parser.add_argument("--output_path", type=str, default="./sample_videos/output", help="path of output clips")
opt = parser.parse_args()
opt.gpu_ids = [int(x) for x in opt.gpu_ids.split(",")]
cudnn.benchmark = True
print("running on GPU", opt.gpu_ids)
clip_name = opt.clip_path.split("/")[-1]
refs = os.listdir(opt.ref_path)
refs.sort()
nonlocal_net = WarpNet(1)
colornet = ColorVidNet(7)
vggnet = VGG19_pytorch()
vggnet.load_state_dict(torch.load("data/vgg19_conv.pth"))
for param in vggnet.parameters():
param.requires_grad = False
nonlocal_test_path = os.path.join("checkpoints/", "video_moredata_l1/nonlocal_net_iter_76000.pth")
color_test_path = os.path.join("checkpoints/", "video_moredata_l1/colornet_iter_76000.pth")
print("succesfully load nonlocal model: ", nonlocal_test_path)
print("succesfully load color model: ", color_test_path)
nonlocal_net.load_state_dict(torch.load(nonlocal_test_path))
colornet.load_state_dict(torch.load(color_test_path))
nonlocal_net.eval()
colornet.eval()
vggnet.eval()
nonlocal_net.cuda()
colornet.cuda()
vggnet.cuda()
for ref_name in refs:
try:
colorize_video(
opt,
opt.clip_path,
os.path.join(opt.ref_path, ref_name),
os.path.join(opt.output_path, clip_name + "_" + ref_name.split(".")[0]),
nonlocal_net,
colornet,
vggnet,
)
except Exception as error:
print("error when colorizing the video " + ref_name)
print(error)
video_name = "video.avi"
clip_output_path = os.path.join(opt.output_path, clip_name)
mkdir_if_not(clip_output_path)
folder2vid(image_folder=opt.clip_path, output_dir=clip_output_path, filename=video_name)
-738
View File
@@ -1,738 +0,0 @@
from __future__ import print_function
import argparse
import math
import os
import queue
import time
import cv2
import torch
import torch.backends.cudnn as cudnn
import torch.nn as nn
import torch.optim as optim
import torchvision.transforms as transform_lib
from torch.utils.data import ConcatDataset, DataLoader, WeightedRandomSampler
from torchvision.transforms import CenterCrop
import lib.TrainTransforms as transforms
from lib.videoloader import VideosDataset
from lib.videoloader_imagenet import VideosDataset_ImageNet
from models.ColorVidNet import ColorVidNet
from models.ContextualLoss import ContextualLoss, ContextualLoss_forward
from models.FrameColor import frame_colorization
from models.GAN_models import Discriminator_x64
from models.NonlocalNet import (NonlocalWeightedAverage, VGG19_pytorch,
WarpNet, WeightedAverage,
WeightedAverage_color)
from tensorboardX import SummaryWriter
from utils.util import (batch_lab2rgb_transpose_mc, feature_normalize, l1_loss,
mkdir_if_not, mse_loss, parse, tensor_lab2rgb,
uncenter_l, weighted_l1_loss, weighted_mse_loss)
from utils.util_distortion import (CenterPad_threshold, Normalize, RGB2Lab,
ToTensor)
from utils.util_tensorboard import TBImageRecorder, value_logger
from utils.warping import WarpingLayer
cv2.setNumThreads(0)
parser = argparse.ArgumentParser()
parser.add_argument("--data_root", default="/home/zhanbo/remote/video/video_pair3/", type=str)
parser.add_argument("--data_root_imagenet", default="/home/zhanbo/remote/imagenet_ref_pair/", type=str)
parser.add_argument("--gpu_ids", type=str, default="0,1,2,3", help="separate by comma")
parser.add_argument("--workers", type=int, default=8)
parser.add_argument("--batch_size", type=int, default=16)
parser.add_argument("--image_size", type=int, default=[216, 384])
parser.add_argument("--ic", type=int, default=7)
parser.add_argument("--epoch", type=int, default=40)
parser.add_argument("--resume_epoch", type=int, default=0)
parser.add_argument("--resume", type=bool, default=False)
parser.add_argument("--load_pretrained_model", type=bool, default=True)
parser.add_argument("--lr", type=float, default=0.00001)
parser.add_argument("--beta1", type=float, default=0.5)
parser.add_argument("--lr_step", type=int, default=100)
parser.add_argument("--lr_gamma", type=float, default=0.1)
parser.add_argument("--checkpoint_dir", type=str, default="checkpoints/default")
parser.add_argument("--tb_log_step", type=int, default=50)
parser.add_argument("--print_step", type=int, default=2)
parser.add_argument("--real_reference_probability", type=float, default=0.7)
parser.add_argument("--nonzero_placeholder_probability", type=float, default=0.0)
parser.add_argument("--with_bad", type=bool, default=True)
parser.add_argument("--with_mid", type=bool, default=True)
parser.add_argument("--domain_invariant", type=bool, default=False)
parser.add_argument("--weigth_l1", type=float, default=2.0)
parser.add_argument("--weight_contextual", type=float, default="0.2")
parser.add_argument("--weight_perceptual", type=float, default="0.001")
parser.add_argument("--weight_smoothness", type=float, default="5.0")
parser.add_argument("--weight_gan", type=float, default="0.2")
parser.add_argument("--weight_nonlocal_smoothness", type=float, default="0.0")
parser.add_argument("--weight_nonlocal_consistent", type=float, default="0.0")
parser.add_argument("--weight_consistent", type=float, default="0.02")
parser.add_argument("--luminance_noise", type=float, default="2.0")
parser.add_argument("--permute_data", type=bool, default=True)
parser.add_argument("--contextual_loss_direction", type=str, default="forward", help="forward or backward matching")
def image_logger_fn(
I_last_lab,
I_current_lab,
I_reference_lab,
I_last_lab_predict,
I_current_lab_predict,
I_last_nonlocal_lab,
I_current_nonlocal_lab,
):
I_last_image = batch_lab2rgb_transpose_mc(I_last_lab[0:32, 0:1, :, :], I_last_lab[0:32, 1:3, :, :])
I_current_image = batch_lab2rgb_transpose_mc(I_current_lab[0:32, 0:1, :, :], I_current_lab[0:32, 1:3, :, :])
I_reference_image = batch_lab2rgb_transpose_mc(I_reference_lab[0:32, 0:1, :, :], I_reference_lab[0:32, 1:3, :, :])
I_last_image_predict = batch_lab2rgb_transpose_mc(
I_last_lab_predict[0:32, 0:1, :, :], I_last_lab_predict[0:32, 1:3, :, :]
)
I_current_image_predict = batch_lab2rgb_transpose_mc(
I_current_lab_predict[0:32, 0:1, :, :], I_current_lab_predict[0:32, 1:3, :, :]
)
I_last_nonlocal_image = batch_lab2rgb_transpose_mc(
I_last_nonlocal_lab[0:32, 0:1, :, :], I_last_nonlocal_lab[0:32, 1:3, :, :]
)
I_current_nonlocal_image = batch_lab2rgb_transpose_mc(
I_current_nonlocal_lab[0:32, 0:1, :, :], I_current_nonlocal_lab[0:32, 1:3, :, :]
)
img_info = {}
img_info["1_I_last"] = I_last_image
img_info["2_I_current"] = I_current_image
img_info["3_I_reference"] = I_reference_image
img_info["4_I_last_predict"] = I_last_image_predict
img_info["5_I_curren_predict"] = I_current_image_predict
img_info["6_I_last_nonlocal"] = I_last_nonlocal_image
img_info["7_I_current_nonlocal"] = I_current_nonlocal_image
return img_info
def training_logger():
try:
if total_iter % opt.print_step == 0:
print("processing time:", elapsed)
print(
"Epoch %d, Step[%d/%d], lr: %f, total_loss: %.2f"
% (
epoch,
((iter + 1) % iter_num_per_epoch),
iter_num_per_epoch,
step_optim_scheduler_g.get_last_lr()[0],
total_loss.item(),
)
)
value_logger(
tb_writer,
total_iter,
loss_info={
"l1_loss": l1_loss.item(),
"feat_loss": feat_loss.item(),
"contextual_loss_total": contextual_loss_total.item(),
"smoothness_loss": smoothness_loss.item(),
"nonlocal_smoothness_loss": nonlocal_smoothness_loss.item(),
"nonlocal_consistent_loss": nonlocal_consistent_loss.item(),
"consistent_loss": consistent_loss.item(),
"generator_loss": generator_loss.item(),
"discriminator_loss": discriminator_loss.item(),
"total_loss": total_loss.item(),
},
)
if total_iter % opt.tb_log_step == 0:
I_last_nonlocal_lab = torch.cat(
(I_last_lab[:, 0:1, :, :], I_last_nonlocal_lab_predict[:, 1:3, :, :]), dim=1
)
I_current_nonlocal_lab = torch.cat(
(I_current_lab[:, 0:1, :, :], I_current_nonlocal_lab_predict[:, 1:3, :, :]), dim=1
)
I_last_lab_predict = torch.cat((I_last_l, I_last_ab_predict), dim=1)
data_queue.put(
(
(
I_last_lab.cpu(),
I_current_lab.cpu(),
I_reference_lab.cpu(),
I_last_lab_predict.cpu(),
I_current_lab_predict.cpu(),
I_last_nonlocal_lab.cpu(),
I_current_nonlocal_lab,
),
total_iter,
)
)
if total_iter % 2000 == 0:
if len(opt.gpu_ids) > 1:
torch.save(
nonlocal_net.module.state_dict(),
os.path.join(opt.checkpoint_dir, "nonlocal_net_iter_%d.pth") % total_iter,
)
torch.save(
colornet.module.state_dict(),
os.path.join(opt.checkpoint_dir, "colornet_iter_%d.pth") % total_iter,
)
torch.save(
discriminator.module.state_dict(),
os.path.join(opt.checkpoint_dir, "discriminator_iter_%d.pth") % total_iter,
)
else:
torch.save(
nonlocal_net.state_dict(),
os.path.join(opt.checkpoint_dir, "nonlocal_net_iter_%d.pth") % total_iter,
)
torch.save(colornet.state_dict(), os.path.join(opt.checkpoint_dir, "colornet_iter_%d.pth") % total_iter)
torch.save(
discriminator.state_dict(),
os.path.join(opt.checkpoint_dir, "discriminator_iter_%d.pth") % total_iter,
)
# save the state for resume
if total_iter % 2000 == 0:
print("saving the checkpoint")
if len(opt.gpu_ids) > 1:
state = {
"total_iter": total_iter + 1,
"epoch": epoch,
"colornet_state": colornet.module.state_dict(),
"nonlocal_net_state": nonlocal_net.module.state_dict(),
"discriminator_state": discriminator.module.state_dict(),
"optimizer_g": optimizer_g.state_dict(),
"optimizer_d": optimizer_d.state_dict(),
"optimizer_schedule_g": step_optim_scheduler_g.state_dict(),
"optimizer_schedule_d": step_optim_scheduler_g.state_dict(),
}
else:
state = {
"total_iter": total_iter + 1,
"epoch": epoch,
"colornet_state": colornet.state_dict(),
"nonlocal_net_state": nonlocal_net.state_dict(),
"discriminator_state": discriminator.state_dict(),
"optimizer_g": optimizer_g.state_dict(),
"optimizer_d": optimizer_d.state_dict(),
"optimizer_schedule_g": step_optim_scheduler_g.state_dict(),
"optimizer_schedule_d": step_optim_scheduler_d.state_dict(),
}
torch.save(state, os.path.join(opt.checkpoint_dir, "learning_checkpoint.pth"))
except Exception as e:
print("Exception during output")
print(e)
def gpu_setup():
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
cudnn.benchmark = True
opt.gpu_ids = [int(x) for x in opt.gpu_ids.split(",")]
torch.cuda.set_device(opt.gpu_ids[0])
device = torch.device("cuda")
print("running on GPU", opt.gpu_ids)
return device
def load_data():
print("initializing dataloader")
transforms_video = [
CenterCrop(opt.image_size),
RGB2Lab(),
ToTensor(),
Normalize(),
]
transforms_imagenet = [CenterPad_threshold(opt.image_size), RGB2Lab(), ToTensor(), Normalize()]
extra_reference_transform = [
transform_lib.RandomHorizontalFlip(0.5),
transform_lib.RandomResizedCrop(480, (0.98, 1.0), ratio=(0.8, 1.2)),
]
train_dataset_video = VideosDataset(
data_root=opt.data_root,
epoch=opt.epoch,
image_size=opt.image_size,
image_transform=transforms.Compose(transforms_video),
real_reference_probability=opt.real_reference_probability,
nonzero_placeholder_probability=opt.nonzero_placeholder_probability,
)
train_dataset_imagenet = VideosDataset_ImageNet(
data_root=opt.data_root_imagenet,
image_size=opt.image_size,
epoch=opt.epoch,
with_bad=opt.with_bad,
with_mid=opt.with_mid,
transforms_imagenet=transforms_imagenet,
distortion_level=4,
brightnessjitter=5,
nonzero_placeholder_probability=opt.nonzero_placeholder_probability,
extra_reference_transform=extra_reference_transform,
real_reference_probability=opt.real_reference_probability,
)
video_training_length = len(train_dataset_video)
imagenet_training_length = len(train_dataset_imagenet)
dataset_training_length = train_dataset_video.real_len + train_dataset_imagenet.real_len
dataset_combined = ConcatDataset([train_dataset_video, train_dataset_imagenet])
sampler = WeightedRandomSampler(
[1] * video_training_length + [1] * imagenet_training_length, dataset_training_length * opt.epoch
)
data_loader = DataLoader(
dataset_combined,
batch_size=opt.batch_size,
shuffle=False,
num_workers=opt.workers,
pin_memory=True,
drop_last=True,
sampler=sampler,
)
return dataset_training_length, train_dataset_video, train_dataset_imagenet, data_loader
def define_loss():
print("defining loss")
ab_criterion = nn.SmoothL1Loss().to(device)
nonlocal_criterion = nn.SmoothL1Loss().to(device)
feat_l2_criterion = nn.MSELoss().to(device)
feat_l1_criterion = nn.SmoothL1Loss().to(device)
contextual_loss = ContextualLoss().to(device)
contextual_forward_loss = ContextualLoss_forward().to(device)
BCE_stable = nn.BCEWithLogitsLoss().to(device)
return contextual_loss, contextual_forward_loss
def define_optimizer():
print("defining optimizer")
optimizer_g = optim.Adam(
[{"params": nonlocal_net.parameters(), "lr": 1e-5}, {"params": colornet.parameters(), "lr": 2e-4}],
betas=(0.5, 0.999),
eps=1e-5,
amsgrad=True,
)
optimizer_d = optim.Adam(
filter(lambda p: p.requires_grad, discriminator.parameters()), lr=2 * 1e-4, betas=(0.5, 0.999)
)
return optimizer_g, optimizer_d
def resume_model():
print("resuming the learning")
checkpoint = torch.load(os.path.join(opt.checkpoint_dir, "learning_checkpoint.pth"))
total_iter = checkpoint["total_iter"]
epoch = checkpoint["epoch"]
colornet.load_state_dict(checkpoint["colornet_state"])
nonlocal_net.load_state_dict(checkpoint["nonlocal_net_state"])
discriminator.load_state_dict(checkpoint["discriminator_state"])
optimizer_g.load_state_dict(checkpoint["optimizer_g"])
optimizer_d.load_state_dict(checkpoint["optimizer_d"])
step_optim_scheduler_g.load_state_dict(checkpoint["optimizer_schedule_g"])
step_optim_scheduler_d.load_state_dict(checkpoint["optimizer_schedule_d"])
def to_device(
colornet,
nonlocal_net,
discriminator,
vggnet,
contextual_loss,
contextual_forward_loss,
weighted_layer_color,
nonlocal_weighted_layer,
warping_layer,
instancenorm,
):
print("moving models to device")
colornet = torch.nn.DataParallel(colornet.to(device), device_ids=opt.gpu_ids)
nonlocal_net = torch.nn.DataParallel(nonlocal_net.to(device), device_ids=opt.gpu_ids)
discriminator = torch.nn.DataParallel(discriminator.to(device), device_ids=opt.gpu_ids)
vggnet = torch.nn.DataParallel(vggnet.to(device), device_ids=opt.gpu_ids)
contextual_loss = torch.nn.DataParallel(contextual_loss.to(device), device_ids=opt.gpu_ids)
contextual_forward_loss = torch.nn.DataParallel(contextual_forward_loss.to(device), device_ids=opt.gpu_ids)
weighted_layer_color = torch.nn.DataParallel(weighted_layer_color.to(device), device_ids=opt.gpu_ids)
nonlocal_weighted_layer = torch.nn.DataParallel(nonlocal_weighted_layer.to(device), device_ids=opt.gpu_ids)
warping_layer = torch.nn.DataParallel(warping_layer.to(device), device_ids=opt.gpu_ids)
instancenorm = torch.nn.DataParallel(instancenorm.to(device), device_ids=opt.gpu_ids)
return (
vggnet,
nonlocal_net,
colornet,
discriminator,
instancenorm,
contextual_loss,
contextual_forward_loss,
weighted_layer_color,
nonlocal_weighted_layer,
warping_layer,
)
def loss_init():
print("initializing losses")
zero_loss = torch.Tensor([0]).to(device)
(
feat_loss,
contextual_loss_total,
smoothness_loss,
nonlocal_smoothness_loss,
consistent_loss,
nonlocal_consistent_loss,
generator_loss,
discriminator_loss,
) = (zero_loss, zero_loss, zero_loss, zero_loss, zero_loss, zero_loss, zero_loss, zero_loss)
return (
feat_loss,
contextual_loss_total,
smoothness_loss,
nonlocal_smoothness_loss,
consistent_loss,
nonlocal_consistent_loss,
generator_loss,
discriminator_loss,
)
def video_colorization():
# colorization for the last frame
I_last_ab_predict, I_last_nonlocal_lab_predict, features_last_gray = frame_colorization(
I_last_lab,
I_reference_lab,
placeholder_lab,
features_B,
vggnet,
nonlocal_net,
colornet,
feature_noise=0,
luminance_noise=opt.luminance_noise,
)
I_last_lab_predict = torch.cat((I_last_l, I_last_ab_predict), dim=1)
# colorization for the current frame
I_current_ab_predict, I_current_nonlocal_lab_predict, features_current_gray = frame_colorization(
I_current_lab,
I_reference_lab,
I_last_lab_predict,
features_B,
vggnet,
nonlocal_net,
colornet,
feature_noise=0,
luminance_noise=opt.luminance_noise,
)
I_current_lab_predict = torch.cat((I_last_l, I_current_ab_predict), dim=1)
return I_current_ab_predict, I_last_ab_predict, I_current_nonlocal_lab_predict, I_last_nonlocal_lab_predict
if __name__ == "__main__":
torch.multiprocessing.set_start_method("spawn", force=True)
opt = parse(parser)
opt.data_root = opt.data_root.split(",")[0]
opt.data_root_imagenet = opt.data_root_imagenet.split(",")[0]
mkdir_if_not(opt.checkpoint_dir)
mkdir_if_not("./runs/")
device = gpu_setup()
dataset_training_length, train_dataset_video, train_dataset_imagenet, data_loader = load_data()
tb_writer = SummaryWriter()
data_queue = queue.Queue()
tb_image_reorder = TBImageRecorder(tb_writer, image_logger_fn, data_queue)
tb_image_reorder.start()
# define network
nonlocal_net = WarpNet(opt.batch_size)
colornet = ColorVidNet(opt.ic)
discriminator = Discriminator_x64(ndf=64)
colornet.to(device)
nonlocal_net.to(device)
discriminator.to(device)
weighted_layer = WeightedAverage()
weighted_layer_color = WeightedAverage_color()
nonlocal_weighted_layer = NonlocalWeightedAverage()
instancenorm = nn.InstanceNorm2d(512, affine=False)
warping_layer = WarpingLayer("gpu")
vggnet = VGG19_pytorch()
vggnet.load_state_dict(torch.load("data/vgg19_conv.pth"))
vggnet.eval()
for param in vggnet.parameters():
param.requires_grad = False
# load pre-trained model
if opt.load_pretrained_model:
nonlocal_pretain_path = os.path.join("checkpoints/video_moredata_l1/", "nonlocal_net_iter_76000.pth")
nonlocal_net.load_state_dict(torch.load(nonlocal_pretain_path))
color_test_path = "checkpoints/video_moredata_l1/" + "colornet_iter_76000.pth"
colornet.load_state_dict(torch.load(color_test_path))
# define loss function
contextual_loss, contextual_forward_loss = define_loss()
# define optimizer
optimizer_g, optimizer_d = define_optimizer()
step_optim_scheduler_g = optim.lr_scheduler.StepLR(optimizer_g, step_size=opt.lr_step, gamma=opt.lr_gamma)
step_optim_scheduler_d = optim.lr_scheduler.StepLR(optimizer_d, step_size=opt.lr_step, gamma=opt.lr_gamma)
# define others
downsampling_by2 = nn.AvgPool2d(kernel_size=2).to(device)
downsampling_by4 = nn.AvgPool2d(kernel_size=4).to(device)
# dataset info
iter_num_per_epoch = dataset_training_length // opt.batch_size
total_iter = opt.resume_epoch * iter_num_per_epoch
print(
"train_dataset info, real_len: %d, epoch_len: %d, iter_num_per_epoch: %d"
% (dataset_training_length, len(train_dataset_video) + len(train_dataset_imagenet), iter_num_per_epoch)
)
if opt.resume:
resume_model()
# move to GPU processing
device = opt.gpu_ids[0]
(
vggnet,
nonlocal_net,
colornet,
discriminator,
instancenorm,
contextual_loss,
contextual_forward_loss,
weighted_layer_color,
nonlocal_weighted_layer,
warping_layer,
) = to_device(
colornet,
nonlocal_net,
discriminator,
vggnet,
contextual_loss,
contextual_forward_loss,
weighted_layer_color,
nonlocal_weighted_layer,
warping_layer,
instancenorm,
)
(
feat_loss,
contextual_loss_total,
smoothness_loss,
nonlocal_smoothness_loss,
consistent_loss,
nonlocal_consistent_loss,
generator_loss,
discriminator_loss,
) = loss_init()
# %% Training
print("start training")
for iter, data in enumerate(data_loader):
start_time = time.time()
total_iter += 1
epoch = math.ceil(total_iter / iter_num_per_epoch)
###### LOADING DATA SAMPLE ######
(
I_last_lab,
I_current_lab,
I_reference_lab,
flow_forward,
flow_backward,
mask,
placeholder_lab,
self_ref_flag,
) = data
I_last_lab = I_last_lab.to(device)
I_current_lab = I_current_lab.to(device)
I_reference_lab = I_reference_lab.to(device)
flow_forward = flow_forward.to(device)
flow_backward = flow_backward.to(device)
mask = mask.to(device)
placeholder_lab = placeholder_lab.to(device)
self_ref_flag = self_ref_flag.to(device)
I_last_l = I_last_lab[:, 0:1, :, :]
I_last_ab = I_last_lab[:, 1:3, :, :]
I_current_l = I_current_lab[:, 0:1, :, :]
I_current_ab = I_current_lab[:, 1:3, :, :]
I_reference_l = I_reference_lab[:, 0:1, :, :]
I_reference_ab = I_reference_lab[:, 1:3, :, :]
I_reference_rgb = tensor_lab2rgb(torch.cat((uncenter_l(I_reference_l), I_reference_ab), dim=1))
features_B = vggnet(I_reference_rgb, ["r12", "r22", "r32", "r42", "r52"], preprocess=True)
###### COLORIZATION ######
(
I_current_ab_predict,
I_last_ab_predict,
I_current_nonlocal_lab_predict,
I_last_nonlocal_lab_predict,
) = video_colorization()
###### UPDATE DISCRIMINATOR ######
if opt.weight_gan > 0:
optimizer_g.zero_grad()
optimizer_d.zero_grad()
fake_data_lab = torch.cat(
(uncenter_l(I_current_l), I_current_ab_predict, uncenter_l(I_last_l), I_last_ab_predict), dim=1
)
real_data_lab = torch.cat((uncenter_l(I_current_l), I_current_ab, uncenter_l(I_last_l), I_last_ab), dim=1)
if opt.permute_data:
batch_index = torch.arange(-1, opt.batch_size - 1, dtype=torch.long)
real_data_lab = real_data_lab[batch_index, ...]
y_pred_fake, feature_pred_fake = discriminator(fake_data_lab.detach())
y_pred_real, feature_pred_real = discriminator(real_data_lab.detach())
y = torch.ones_like(y_pred_real)
y2 = torch.zeros_like(y_pred_real)
discriminator_loss = (
torch.mean((y_pred_real - torch.mean(y_pred_fake) - y) ** 2)
+ torch.mean((y_pred_fake - torch.mean(y_pred_real) + y) ** 2)
) / 2
discriminator_loss.backward()
optimizer_d.step()
###### UPDATE GENERATOR ######
optimizer_g.zero_grad()
optimizer_d.zero_grad()
# extract vgg features for both output and original image
I_predict_rgb = tensor_lab2rgb(torch.cat((uncenter_l(I_current_l), I_current_ab_predict), dim=1))
predict_relu1_1, predict_relu2_1, predict_relu3_1, predict_relu4_1, predict_relu5_1 = vggnet(
I_predict_rgb, ["r12", "r22", "r32", "r42", "r52"], preprocess=True
)
I_current_rgb = tensor_lab2rgb(torch.cat((uncenter_l(I_current_l), I_current_ab), dim=1))
A_relu1_1, A_relu2_1, A_relu3_1, A_relu4_1, A_relu5_1 = vggnet(
I_current_rgb, ["r12", "r22", "r32", "r42", "r52"], preprocess=True
)
B_relu1_1, B_relu2_1, B_relu3_1, B_relu4_1, B_relu5_1 = features_B
###### LOSS COMPUTE ######
# l1 loss
if opt.weigth_l1 > 0:
sample_weights = (self_ref_flag[:, 1:3, :, :]) / (sum(self_ref_flag[:, 0, 0, 0]) + 1e-5)
l1_loss = weighted_l1_loss(I_current_ab_predict, I_current_ab, sample_weights) * opt.weigth_l1
# generator loss
if opt.weight_gan > 0:
y_pred_fake, feature_pred_fake = discriminator(fake_data_lab)
y_pred_real, feature_pred_real = discriminator(real_data_lab)
generator_loss = (
(
torch.mean((y_pred_real - torch.mean(y_pred_fake) + y) ** 2)
+ torch.mean((y_pred_fake - torch.mean(y_pred_real) - y) ** 2)
)
/ 2
* opt.weight_gan
)
# feature loss
if opt.domain_invariant:
feat_loss = (
mse_loss(instancenorm(predict_relu5_1), instancenorm(A_relu5_1.detach()))
* opt.weight_perceptual
* 1e5
* 0.2
)
else:
feat_loss = mse_loss(predict_relu5_1, A_relu5_1.detach()) * opt.weight_perceptual
# contextual loss
if opt.contextual_loss_direction == "backward":
contextual_style5_1 = torch.mean(contextual_loss(predict_relu5_1, B_relu5_1.detach())) * 8
contextual_style4_1 = torch.mean(contextual_loss(predict_relu4_1, B_relu4_1.detach())) * 4
contextual_style3_1 = (
torch.mean(contextual_loss(downsampling_by2(predict_relu3_1), downsampling_by2(B_relu3_1.detach()))) * 2
)
else:
contextual_style5_1 = torch.mean(contextual_forward_loss(predict_relu5_1, B_relu5_1.detach())) * 8
contextual_style4_1 = torch.mean(contextual_forward_loss(predict_relu4_1, B_relu4_1.detach())) * 4
contextual_style3_1 = (
torch.mean(
contextual_forward_loss(downsampling_by2(predict_relu3_1), downsampling_by2(B_relu3_1.detach()))
)
* 2
)
if opt.weight_contextual > 0:
contextual_loss_total = (
contextual_style5_1 + contextual_style4_1 + contextual_style3_1
) * opt.weight_contextual
# smoothness loss
if opt.weight_smoothness > 0:
scale_factor = 1
I_current_lab_predict = torch.cat((I_current_l, I_current_ab_predict), dim=1)
IA_ab_weighed = weighted_layer_color(
I_current_lab, I_current_lab_predict, patch_size=3, alpha=10, scale_factor=scale_factor
)
smoothness_loss = (
mse_loss(nn.functional.interpolate(I_current_ab_predict, scale_factor=scale_factor), IA_ab_weighed)
* opt.weight_smoothness
)
if opt.weight_nonlocal_smoothness > 0:
scale_factor = 0.25
alpha_nonlocal_smoothness = 0.5
nonlocal_smooth_feature = feature_normalize(A_relu2_1)
I_current_lab_predict = torch.cat((I_current_l, I_current_ab_predict), dim=1)
I_current_ab_weighted_nonlocal = nonlocal_weighted_layer(
I_current_lab_predict,
nonlocal_smooth_feature.detach(),
patch_size=3,
alpha=alpha_nonlocal_smoothness,
scale_factor=scale_factor,
)
nonlocal_smoothness_loss = (
mse_loss(
nn.functional.interpolate(I_current_ab_predict, scale_factor=scale_factor),
I_current_ab_weighted_nonlocal,
)
* opt.weight_nonlocal_smoothness
)
if opt.weight_consistent:
I_current_lab_predict_warp = warping_layer(I_current_lab_predict, flow_forward)
I_current_ab_predict_warp = I_current_lab_predict_warp[:, 1:3, :, :]
consistent_loss = (
weighted_mse_loss(I_current_ab_predict_warp, I_last_ab_predict, mask) * opt.weight_consistent
)
if opt.weight_nonlocal_consistent:
I_current_nonlocal_lab_predict_warp = warping_layer(I_current_nonlocal_lab_predict, flow_forward)
nonlocal_consistent_loss = (
weighted_mse_loss(
I_current_nonlocal_lab_predict_warp[:, 1:3, :, :], I_last_nonlocal_lab_predict[:, 1:3, :, :], mask
)
* opt.weight_nonlocal_consistent
)
# total loss
total_loss = (
l1_loss
+ feat_loss
+ contextual_loss_total
+ smoothness_loss
+ nonlocal_smoothness_loss
+ consistent_loss
+ nonlocal_consistent_loss
+ generator_loss
)
total_loss.backward()
optimizer_g.step()
end_time = time.time()
elapsed = end_time - start_time
training_logger()
step_optim_scheduler_g.step()
step_optim_scheduler_d.step()
data_queue.put((None, None))