Retinex
A reference implementation of single-scale and multi-scale Retinex image enhancement with color restoration (MSRCR).
Prerequisites
- Rust toolchain (
rustuprecommended)
Build
Usage
Basic Examples
Single scale with default sigma (produces grayscale reflectance):
Multi scale with custom sigmas:
Multi-Scale Retinex with Color Restoration (MSR)
The recommended approach for best results - combines multiple scales with color restoration:
This produces natural-looking results with enhanced local details and corrected global illumination.
Color Restoration (MSRCR)
The basic Retinex algorithm produces grayscale output because it processes each color channel independently and normalizes them separately. This destroys the color ratios.
Enable color restoration to preserve colors:
Viewing Components Separately
Save the estimated illumination (lighting component):
Save the raw reflectance (before color restoration):
Save all components at once:
Example Results
Original Input

Single-Scale Retinex (Grayscale)
Basic Retinex without color restoration produces high-contrast grayscale:

Multi-Scale Retinex with Color Restoration (MSR)
The recommended approach using multiple scales with color restoration:

Illumination Component
The estimated lighting (slowly varying, blurred version of input):

Reflectance Component
The extracted reflectance (high-contrast, object colors without lighting):

Why Basic Retinex Looks Grayscale
The basic Retinex formula computes:
R = log(I) - log(G_σ * I)
When applied independently to each RGB channel and then normalized per-channel, the relative color ratios are lost. All three channels end up with similar values because:
- Each channel's log-response is normalized to [0, 255] independently
- The min/max stretching destroys the original color proportions
- The result appears bright and desaturated (grayscale)
Color Restoration (MSRCR)
MSRCR (Multi-Scale Retinex with Color Restoration) fixes this by applying a color restoration factor based on the original image's color ratios:
R_final = R_msr × (I_channel / (I_r + I_g + I_b)) × 3
The factor of 3 normalizes the weights (since the three ratios sum to 1). This preserves the original hue while applying the Retinex contrast enhancement.
Algorithm Overview
Given an observed image I:
I(x,y) = R(x,y) × L(x,y)
Ris the reflectance (desired result - object colors)Lis illumination (slowly varying lighting - estimated via Gaussian blur)
Single-scale Retinex:
R = log(I + ε) - log((G_σ × I) + ε)
Multi-scale Retinex averages multiple scales to balance local and global contrast.
Output Files
-
Main output (
output.jpg): The processed result- Without
--color-restore: Normalized reflectance (grayscale, high contrast) - With
--color-restore: Color-corrected result with preserved hues
- Without
-
Illumination (
--illumination): The estimated lighting component- Shows the slowly-varying illumination that was subtracted
- Blurred version of the original image
-
Reflectance (
--reflectance): The raw reflectance before color restoration- Shows what the algorithm extracted as "object colors"
- High-contrast grayscale image
Parameters
--mode:singleormulti- use multiple scales for better results--sigmas: Comma-separated Gaussian blur radii (e.g.,15,80,250)- Small values (5-30): Enhance local details
- Large values (100-300): Correct global illumination
--color-restore: Enable MSRCR color restoration--illumination <path>: Save illumination component to file--reflectance <path>: Save raw reflectance to file
Multi-Scale Mode (--mode multi)
Multi-scale Retinex computes the reflectance at multiple Gaussian blur scales and averages the results. This balances local detail enhancement (small sigma) with global illumination correction (large sigma).
How it works:
R_multi = (R_σ1 + R_σ2 + ... + R_σn) / n
Where each R_σ is the reflectance computed with a different sigma value.
Selecting sigma values:
A good default set is 15,80,250:
15: Fine details, edges, textures80: Medium-scale features, local contrast250: Large-scale illumination variations, global tone
You can use fewer or more scales depending on the image:
- Fast processing:
--sigmas 30,150 - High quality:
--sigmas 10,40,100,250
What happens if you specify multiple sigmas without --mode multi?
Only the first sigma value is used. The rest are ignored:
# This uses only sigma=15 (single-scale mode)
# This uses all three sigmas (multi-scale mode)
The --mode flag determines whether multiple sigmas are averaged (multi) or if only the first is used (single).
Technical Details
Normalization
The reflectance output uses percentile-based clipping (2nd to 98th percentile) to handle outliers in the log-domain values. This prevents extreme values from compressing the visible dynamic range.
Color Restoration Formula
// For each pixel and channel:
let sum = r + g + b;
let color_factor = * 3.0;
let output = reflectance * color_factor * 255.0;
The color factor reweights each channel based on its proportion in the original image, restoring the original color balance.