Multiple architectural upgrades Enhanced Algorithm and multiple algorithms

This commit is contained in:
sumitchatterjee13
2025-09-21 01:21:29 +10:00
parent b2c0e9f55b
commit bc227d7cd3
3 changed files with 103 additions and 30 deletions
+5 -1
View File
@@ -15,7 +15,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **Enhanced 16-bit linear output** - Improved scaling algorithm for better 16-bit range utilization
### Fixed
- Nothing yet
- **CRITICAL: Fixed color inversion issues** - Added proper BGR↔RGB conversion for correct color handling
- **Fixed brightness problems** - Improved scaling and algorithm selection
- **Added Mertens Exposure Fusion algorithm** - Now default, produces better results like Adobe Lightroom
- **Added Robertson algorithm** - Alternative HDR method for different use cases
- **Enhanced algorithm selection** - Users can choose between Mertens (default), Debevec, and Robertson
## [1.0.1] - 2025-01-20
+40 -5
View File
@@ -12,7 +12,7 @@ Professional HDR (High Dynamic Range) processing nodes for ComfyUI using the **D
## 🎯 Features
- **Professional HDR Processing**: Implements the industry-standard Debevec Algorithm for HDR image creation
- **Multiple HDR Algorithms**: Supports Mertens Exposure Fusion (default), Debevec, and Robertson algorithms
- **Two Processing Modes**:
- **3-Stop Processor**: Merges EV+2, EV+0, EV-2 exposures
- **5-Stop Processor**: Merges EV+4, EV+2, EV+0, EV-2, EV-4 exposures
@@ -76,6 +76,7 @@ Perfect for standard HDR bracketing with 3 exposures:
- `ev_0`: Normal exposure image (0 EV)
- `ev_minus_2`: Underexposed image (-2 EV)
- `exposure_step`: (Optional) EV step size (default: 2.0)
- `hdr_algorithm`: (Optional) Algorithm to use: "mertens" (default), "debevec", "robertson"
**Output:**
- `hdr_image`: Merged 16-bit linear HDR image
@@ -91,13 +92,14 @@ For extended dynamic range with 5 exposures:
- `ev_minus_2`: Underexposed image (-2 EV)
- `ev_minus_4`: Most underexposed image (-4 EV)
- `exposure_step`: (Optional) EV step size (default: 2.0)
- `hdr_algorithm`: (Optional) Algorithm to use: "mertens" (default), "debevec", "robertson"
**Output:**
- `hdr_image`: Merged 16-bit linear HDR image
## 🔬 How It Works
The nodes implement the **Debevec Algorithm** (Paul E. Debevec, Jitendra Malik, 1997) which:
The nodes implement **multiple HDR algorithms** with **Mertens Exposure Fusion** as the default (often produces better results):
1. **Analyzes Multiple Exposures**: Takes differently exposed 8-bit images of the same scene
2. **Estimates Camera Response Function**: Determines how the camera sensor responds to light
@@ -105,6 +107,23 @@ The nodes implement the **Debevec Algorithm** (Paul E. Debevec, Jitendra Malik,
4. **Merges to HDR**: Combines all exposures into a single high dynamic range image
5. **Linear 16-bit Output**: Outputs 16-bit linear colorspace data preserving full HDR range
### 🎯 **HDR Algorithm Options:**
#### **Mertens Exposure Fusion (Default - Recommended)**
- **Best for most use cases**: Produces natural-looking results similar to Adobe Lightroom
- **No tone mapping needed**: Output looks like enhanced EV0 with extended dynamic range
- **Fastest processing**: No camera response function estimation required
- **Color accuracy**: Superior color handling, avoids common HDR artifacts
#### **Debevec Algorithm (Classic HDR)**
- **Industry standard**: Original HDR reconstruction method from 1997
- **True scene radiance**: Recovers actual physical light values
- **Research accurate**: Mathematically precise but may require tone mapping for display
#### **Robertson Algorithm**
- **Alternative approach**: Different camera response function estimation method
- **Similar to Debevec**: But with different mathematical approach
## 📸 Best Practices
### For Capturing Source Images:
@@ -119,9 +138,14 @@ The nodes implement the **Debevec Algorithm** (Paul E. Debevec, Jitendra Malik,
- **5-Stop**: +4, +2, 0, -2, -4 EV (16x range)
- Adjust `exposure_step` parameter if using different increments
### Algorithm Selection Guide:
- **Use Mertens (Default)**: For most photography - produces natural results like Lightroom
- **Use Debevec**: For scientific/research work requiring precise scene radiance values
- **Use Robertson**: Alternative to Debevec with different mathematical approach
## ⚙️ Technical Details
- **Algorithm**: Debevec & Malik HDR reconstruction
- **Algorithms**: Mertens Exposure Fusion (default), Debevec, Robertson
- **Input Format**: 8-bit ComfyUI IMAGE tensors (0-1 float range from 8-bit sources)
- **Output Format**: 16-bit linear colorspace images for extended dynamic range
- **Processing**: OpenCV's `createCalibrateDebevec()` and `createMergeDebevec()`
@@ -136,12 +160,23 @@ The nodes implement the **Debevec Algorithm** (Paul E. Debevec, Jitendra Malik,
- Ensure all dependencies are installed: `pip install -r requirements.txt`
- Restart ComfyUI completely
2. **Poor HDR results**:
2. **Color inversion or wrong colors**:
- Try switching to **Mertens algorithm** (default) - usually fixes color issues
- Mertens produces more natural colors similar to Adobe Lightroom
- Debevec/Robertson may need additional tone mapping for proper color appearance
3. **Image too bright or too dark**:
- **Mertens algorithm** (default) produces best brightness levels
- Output should look like enhanced EV0 with extended dynamic range
- For Debevec/Robertson, you may need tone mapping in post-processing
4. **Poor HDR results**:
- Ensure input images are properly exposed (not all over/under)
- Check that images are aligned (use tripod)
- Verify EV differences match your capture method
- Try different algorithms: Mertens usually works best
3. **Memory issues**:
5. **Memory issues**:
- Process smaller images first
- Ensure adequate RAM available
- Close unnecessary applications
+58 -24
View File
@@ -88,19 +88,24 @@ def cv2_to_tensor(hdr_image: np.ndarray, output_16bit_linear: bool = True) -> to
class DebevecHDRProcessor:
"""Core HDR processing using Debevec Algorithm"""
"""Core HDR processing using multiple algorithms"""
def __init__(self):
self.calibrator = cv2.createCalibrateDebevec()
self.merge_debevec = cv2.createMergeDebevec()
# Alternative algorithms
self.merge_mertens = cv2.createMergeMertens()
self.merge_robertson = cv2.createMergeRobertson()
self.calibrator_robertson = cv2.createCalibrateRobertson()
def process_hdr(self, images: List[np.ndarray], exposure_times: List[float]) -> np.ndarray:
def process_hdr(self, images: List[np.ndarray], exposure_times: List[float], algorithm: str = "mertens") -> np.ndarray:
"""
Process multiple exposure images using Debevec algorithm with proper linear colorspace handling
Process multiple exposure images using various HDR algorithms
Args:
images: List of 8-bit images (OpenCV format) - CRITICAL: Must be 8-bit for OpenCV HDR
exposure_times: List of exposure times in seconds
exposure_times: List of exposure times in seconds
algorithm: HDR algorithm to use ("mertens", "debevec", "robertson")
Returns:
HDR merged image in linear colorspace (float32)
@@ -112,9 +117,11 @@ class DebevecHDRProcessor:
if img.dtype != np.uint8:
logger.warning(f"Image {i} is not 8-bit (dtype: {img.dtype}), this may cause issues")
# Ensure proper format
# CRITICAL FIX: Convert BGR to RGB for proper color handling
# OpenCV uses BGR by default, but HDR processing expects RGB
if len(img.shape) == 3 and img.shape[2] == 3: # RGB
processed_images.append(img)
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
processed_images.append(img_rgb)
else:
logger.error(f"Image {i} has invalid shape: {img.shape}")
raise ValueError(f"Image must be 3-channel RGB, got shape: {img.shape}")
@@ -122,24 +129,40 @@ class DebevecHDRProcessor:
# Convert exposure times to numpy array
times = np.array(exposure_times, dtype=np.float32)
logger.info(f"Processing {len(processed_images)} images with exposure times: {times}")
logger.info(f"Processing {len(processed_images)} images with {algorithm} algorithm")
logger.info(f"Exposure times: {times}")
logger.info(f"Image formats: {[img.shape for img in processed_images]}")
logger.info(f"Image dtypes: {[img.dtype for img in processed_images]}")
# Estimate camera response function using Debevec method
logger.info("Estimating camera response function...")
response = self.calibrator.process(processed_images, times)
logger.info(f"Response function shape: {response.shape}")
# Merge images into HDR using Debevec algorithm
logger.info("Merging images using Debevec algorithm...")
hdr_radiance = self.merge_debevec.process(processed_images, times, response)
# Process using selected algorithm
if algorithm == "mertens":
# Mertens Exposure Fusion - often produces better results
logger.info("Using Mertens Exposure Fusion algorithm...")
hdr_radiance = self.merge_mertens.process(processed_images)
# Mertens output is typically in 0-1 range, scale appropriately
if hdr_radiance.max() <= 1.0:
hdr_radiance = hdr_radiance * 2.0 # Boost for better dynamic range
elif algorithm == "robertson":
# Robertson algorithm - alternative to Debevec
logger.info("Using Robertson algorithm...")
response = self.calibrator_robertson.process(processed_images, times)
hdr_radiance = self.merge_robertson.process(processed_images, times, response)
else: # Default to Debevec
# Estimate camera response function using Debevec method
logger.info("Using Debevec algorithm...")
response = self.calibrator.process(processed_images, times)
logger.info(f"Response function shape: {response.shape}")
# Merge images into HDR using Debevec algorithm
hdr_radiance = self.merge_debevec.process(processed_images, times, response)
# Validate HDR output
if hdr_radiance is None or hdr_radiance.size == 0:
raise ValueError("HDR merge produced empty result")
logger.info(f"HDR merge completed:")
logger.info(f"HDR merge completed with {algorithm} algorithm:")
logger.info(f" Output shape: {hdr_radiance.shape}")
logger.info(f" Output dtype: {hdr_radiance.dtype}")
logger.info(f" Value range: [{hdr_radiance.min():.6f}, {hdr_radiance.max():.6f}]")
@@ -149,6 +172,11 @@ class DebevecHDRProcessor:
if np.all(hdr_radiance == 0):
raise ValueError("HDR merge produced all-zero result")
# CRITICAL FIX: Convert back from RGB to BGR for consistency
# This ensures the output color channels are in the expected order
if len(hdr_radiance.shape) == 3 and hdr_radiance.shape[2] == 3:
hdr_radiance = cv2.cvtColor(hdr_radiance, cv2.COLOR_RGB2BGR)
# The result is already in linear colorspace (scene radiance values)
# Don't apply tone mapping - preserve the HDR data as-is
return hdr_radiance.astype(np.float32)
@@ -194,6 +222,9 @@ class LuminanceStackProcessor3Stops:
"step": 0.1,
"display": "number"
}),
"hdr_algorithm": (["mertens", "debevec", "robertson"], {
"default": "mertens"
}),
}
}
@@ -205,7 +236,7 @@ class LuminanceStackProcessor3Stops:
def __init__(self):
self.processor = DebevecHDRProcessor()
def process_3_stop_hdr(self, ev_plus_2, ev_0, ev_minus_2, exposure_step=2.0):
def process_3_stop_hdr(self, ev_plus_2, ev_0, ev_minus_2, exposure_step=2.0, hdr_algorithm="mertens"):
"""
Process 3-stop HDR merge
@@ -235,10 +266,10 @@ class LuminanceStackProcessor3Stops:
images = [img_plus_2, img_0, img_minus_2]
times = [time_plus_2, time_0, time_minus_2]
logger.info(f"3-Stop HDR: Processing with times {times}")
logger.info(f"3-Stop HDR: Processing with times {times} using {hdr_algorithm} algorithm")
# Process HDR using improved Debevec algorithm
hdr_result = self.processor.process_hdr(images, times)
# Process HDR using selected algorithm (default: Mertens for better results)
hdr_result = self.processor.process_hdr(images, times, algorithm=hdr_algorithm)
# Convert back to tensor - output 16-bit linear data
output_tensor = cv2_to_tensor(hdr_result, output_16bit_linear=True)
@@ -275,6 +306,9 @@ class LuminanceStackProcessor5Stops:
"step": 0.1,
"display": "number"
}),
"hdr_algorithm": (["mertens", "debevec", "robertson"], {
"default": "mertens"
}),
}
}
@@ -286,7 +320,7 @@ class LuminanceStackProcessor5Stops:
def __init__(self):
self.processor = DebevecHDRProcessor()
def process_5_stop_hdr(self, ev_plus_4, ev_plus_2, ev_0, ev_minus_2, ev_minus_4, exposure_step=2.0):
def process_5_stop_hdr(self, ev_plus_4, ev_plus_2, ev_0, ev_minus_2, ev_minus_4, exposure_step=2.0, hdr_algorithm="mertens"):
"""
Process 5-stop HDR merge
@@ -321,10 +355,10 @@ class LuminanceStackProcessor5Stops:
images = [img_plus_4, img_plus_2, img_0, img_minus_2, img_minus_4]
times = [time_plus_4, time_plus_2, time_0, time_minus_2, time_minus_4]
logger.info(f"5-Stop HDR: Processing with times {times}")
logger.info(f"5-Stop HDR: Processing with times {times} using {hdr_algorithm} algorithm")
# Process HDR using improved Debevec algorithm
hdr_result = self.processor.process_hdr(images, times)
# Process HDR using selected algorithm (default: Mertens for better results)
hdr_result = self.processor.process_hdr(images, times, algorithm=hdr_algorithm)
# Convert back to tensor - output 16-bit linear data
output_tensor = cv2_to_tensor(hdr_result, output_16bit_linear=True)