Bug fixes and worked more on Debevec algorithm, updated Documentation

This commit is contained in:
sumitchatterjee13
2025-10-05 22:16:26 +11:00
parent 3162cc3adc
commit 9f765d9c00
2 changed files with 107 additions and 84 deletions
+71 -47
View File
@@ -134,58 +134,80 @@ class DebevecHDRProcessor:
"""Core HDR processing using multiple algorithms"""
def __init__(self):
self.calibrator = cv2.createCalibrateDebevec()
# CRITICAL: Much higher lambda for ultra-smooth response curves
# Default is ~10, we use 100+ for AI-generated images to prevent quantization
self.calibrator = cv2.createCalibrateDebevec(samples=200, lambda_=100.0)
self.merge_debevec = cv2.createMergeDebevec()
# Alternative algorithms
self.merge_mertens = cv2.createMergeMertens()
self.merge_robertson = cv2.createMergeRobertson()
self.calibrator_robertson = cv2.createCalibrateRobertson()
logger.info("Debevec calibrator configured with ultra-high smoothness (lambda=100.0, samples=200)")
def _apply_antibanding_filter(self, hdr_image: np.ndarray) -> np.ndarray:
def _apply_gradient_adaptive_dithering(self, hdr_image: np.ndarray) -> np.ndarray:
"""
Apply subtle bilateral filtering to reduce banding artifacts in HDR images
while preserving edges and maintaining linear radiance values
Apply dithering ONLY to luminance in smooth gradients - preserves color perfectly
This prevents color shifts while breaking up banding
CRITICAL: Works directly with float32 data to avoid introducing quantization
This is the professional approach used in tools like LuminanceHDR and Photoshop
Args:
hdr_image: HDR image in linear space (float32, BGR)
Returns:
Filtered HDR image with reduced banding
HDR image with gradient-adaptive dithering (edges preserved, color preserved)
"""
# Work with a copy to preserve original
filtered = hdr_image.copy()
logger.info("Applying luminance-based gradient-adaptive dithering (color-preserving)...")
# Normalize to 0-1 range for bilateral filter
max_val = np.percentile(hdr_image, 99.9) # Use 99.9th percentile to avoid extreme outliers
if max_val <= 0:
return filtered
# Calculate luminance (Rec. 709)
# Note: hdr_image is in BGR format from OpenCV
luminance = (0.0722 * hdr_image[:, :, 0] + # B
0.7152 * hdr_image[:, :, 1] + # G
0.2126 * hdr_image[:, :, 2]) # R
normalized = np.clip(hdr_image / max_val, 0, 1).astype(np.float32)
# Calculate gradient magnitude on luminance
grad_x = cv2.Sobel(luminance, cv2.CV_32F, 1, 0, ksize=3)
grad_y = cv2.Sobel(luminance, cv2.CV_32F, 0, 1, ksize=3)
gradient_magnitude = np.sqrt(grad_x**2 + grad_y**2)
# Apply bilateral filter DIRECTLY on float32 data
# This avoids 8-bit quantization that would introduce banding!
# OpenCV's bilateralFilter works with float32 when input is float32
filtered_normalized = cv2.bilateralFilter(
normalized,
d=5, # Small kernel - very subtle
sigmaColor=0.01, # Very low for float32 (0-1 range) - strongly preserves edges
sigmaSpace=5 # Small spatial sigma - local smoothing only
)
# Normalize gradient
if gradient_magnitude.max() > 0:
gradient_norm = gradient_magnitude / gradient_magnitude.max()
else:
gradient_norm = gradient_magnitude
# Restore original scale
filtered = filtered_normalized * max_val
# Smooth gradients get dithering, edges don't
dither_strength_map = 1.0 - gradient_norm
dither_strength_map = cv2.GaussianBlur(dither_strength_map, (5, 5), 1.0)
# Blend: 70% filtered + 30% original - conservative blend
# This ensures we only smooth obvious banding, not fine details
final = filtered * 0.7 + hdr_image * 0.3
# Generate dither for LUMINANCE only (single noise pattern for all channels)
# Much subtler strength to avoid visible noise
adaptive_strength = dither_strength_map * (np.abs(luminance) + 0.01) * 0.0008 # 0.08% instead of 0.3%
logger.info(f" Anti-banding: subtle float32 bilateral filter (no quantization)")
logger.info(f" Blend: 70% filtered + 30% original (preserves detail)")
logger.info(f" Preserved HDR peaks: max value {final.max():.2f}")
# Single noise pattern
noise = np.random.normal(0, 1, luminance.shape).astype(np.float32)
return final
# Apply noise to luminance
luminance_dithered = luminance + noise * adaptive_strength
# Calculate luminance change ratio
# Avoid division by zero
safe_luminance = np.maximum(luminance, 1e-6)
luma_ratio = luminance_dithered / safe_luminance
# Apply same ratio to all color channels - preserves color perfectly!
result = hdr_image.copy()
for c in range(3):
result[:, :, c] = hdr_image[:, :, c] * luma_ratio
# Statistics
smooth_pixels = np.sum(dither_strength_map > 0.5)
total_pixels = dither_strength_map.size
logger.info(f" Luminance dithering: {smooth_pixels}/{total_pixels} smooth pixels ({100*smooth_pixels/total_pixels:.1f}%)")
logger.info(f" Color preservation: 100% (luminance-only dithering)")
logger.info(f" Dither strength: 0.08% (very subtle, imperceptible)")
return result.astype(np.float32)
def _compute_exposure_ratio_srgb(self, reference: np.ndarray, target: np.ndarray) -> float:
"""
@@ -322,8 +344,8 @@ class DebevecHDRProcessor:
debevec_exposure_compensation: Exposure compensation in stops for debevec/robertson output
Default: -8.0 (optimal for AI-generated brackets with sRGB calibration)
Adjust if output is too bright/dark
debevec_anti_banding: Apply subtle bilateral filtering to reduce banding artifacts
Default: True. Preserves edges while smoothing gradients.
debevec_anti_banding: Apply optional gradient-adaptive dithering to reduce banding
Default: True. Disable for pure lambda=100 smoothness with no color changes.
Returns:
HDR merged image in linear radiance space (float32)
@@ -406,12 +428,6 @@ class DebevecHDRProcessor:
logger.info(f"Robertson raw output: [{hdr_radiance.min():.6f}, {hdr_radiance.max():.6f}]")
# Anti-banding filter
if debevec_anti_banding:
logger.info("Applying anti-banding bilateral filter...")
hdr_radiance = self._apply_antibanding_filter(hdr_radiance)
logger.info(f"After anti-banding: [{hdr_radiance.min():.6f}, {hdr_radiance.max():.6f}]")
# Apply exposure compensation
if debevec_exposure_compensation != 0.0:
compensation_factor = 2.0 ** debevec_exposure_compensation
@@ -419,11 +435,17 @@ class DebevecHDRProcessor:
logger.info(f"Applied exposure compensation: {debevec_exposure_compensation:+.1f} stops (factor: {compensation_factor:.6f}x)")
logger.info(f"Robertson compensated output: [{hdr_radiance.min():.6f}, {hdr_radiance.max():.6f}]")
# Apply gradient-adaptive dithering if anti-banding is enabled
if debevec_anti_banding:
hdr_radiance = self._apply_gradient_adaptive_dithering(hdr_radiance)
logger.info(f"Robertson mean radiance: {hdr_radiance.mean():.6f}")
else: # Default to Debevec - Pure HDR recovery with exposure compensation
# Estimate camera response function using Debevec method
logger.info("Using Debevec algorithm - Pure HDR mode...")
# Use higher smoothness for response curve (lambda=50.0)
response = self.calibrator.process(processed_images, times)
logger.info(f"Response function shape: {response.shape}")
@@ -433,12 +455,6 @@ class DebevecHDRProcessor:
logger.info(f"Debevec raw output: [{hdr_radiance.min():.6f}, {hdr_radiance.max():.6f}]")
# Anti-banding filter
if debevec_anti_banding:
logger.info("Applying anti-banding bilateral filter...")
hdr_radiance = self._apply_antibanding_filter(hdr_radiance)
logger.info(f"After anti-banding: [{hdr_radiance.min():.6f}, {hdr_radiance.max():.6f}]")
# Apply exposure compensation
if debevec_exposure_compensation != 0.0:
compensation_factor = 2.0 ** debevec_exposure_compensation
@@ -446,6 +462,14 @@ class DebevecHDRProcessor:
logger.info(f"Applied exposure compensation: {debevec_exposure_compensation:+.1f} stops (factor: {compensation_factor:.6f}x)")
logger.info(f"Debevec compensated output: [{hdr_radiance.min():.6f}, {hdr_radiance.max():.6f}]")
# Optional gradient-adaptive dithering (controlled by debevec_anti_banding parameter)
# ONLY apply if user enables it - default relies on lambda=100 smoothness alone
if debevec_anti_banding:
logger.info("Applying optional gradient-adaptive dithering...")
hdr_radiance = self._apply_gradient_adaptive_dithering(hdr_radiance)
else:
logger.info("Dithering disabled - relying on ultra-smooth response curve (lambda=100) only")
logger.info(f"Debevec mean radiance: {hdr_radiance.mean():.6f}")
# Validate HDR output
@@ -1360,7 +1384,7 @@ class LuminanceStackProcessor3Stops:
"default": True,
"label_on": "enabled",
"label_off": "disabled",
"tooltip": "Apply subtle bilateral filtering to reduce banding (Debevec/Robertson only)"
"tooltip": "Apply optional gradient-adaptive dithering to reduce banding (Debevec/Robertson only). Disable for pure lambda=100 smoothness with no color changes."
}),
}
}
@@ -1495,7 +1519,7 @@ class LuminanceStackProcessor5Stops:
"default": True,
"label_on": "enabled",
"label_off": "disabled",
"tooltip": "Apply subtle bilateral filtering to reduce banding (Debevec/Robertson only)"
"tooltip": "Apply optional gradient-adaptive dithering to reduce banding (Debevec/Robertson only). Disable for pure lambda=100 smoothness with no color changes."
}),
}
}