Multiple architectural upgrades Enhanced Algorithm and multiple algorithms
This commit is contained in:
+5
-1
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user