Bug fixes and worked more on Debevec algorithm, updated Documentation
This commit is contained in:
@@ -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."
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user