Low-Light Image Enhancement: Lighting up Images in the Deep Learning Era
In this article, we explore some deep learning techniques for low-light image enhancement, so that you can enhance images taken under sub-optimal conditions.
Created on March 7|Last edited on May 6
Comment
Images are often taken under sub-optimal lighting conditions, which makes them harder to parse for most models. Some common examples are backlit subjects, uneven light, dim light, insufficient illumination, and limited exposure time. You can see a few examples of the degradations induced by sub-optimal lighting conditions below:
Dark-Images
Examples of images degraded by sub-optimal lighting conditions.
5
Performance of high-level computer vision tasks depending upon quality of illumination in the image.
5
Low-light enhancement makes those images a lot easier for our models to handle! This technique enjoys a wide range of applications in different areas, including visual surveillance, autonomous driving, and computational photography. In particular, smartphone photography has become ubiquitous and prominent. Limited by the size of the camera aperture, the requirement of real-time processing, and the constraint of memory, taking photographs with a smartphone’s camera in a dim environment is especially challenging.
The following video by Sayak Paul shows the application of a low-light enhancement model for a smartphone-based night-vision system:
In this article, we'll walk you through how to perform low-light enhancements on your images to make them more digestible for your models.
Here's what we'll cover:
Table of Contents
Traditional Methods for Low-Light EnhancementDeep Learning for Low-Light EnhancementHow to Train Your Dragons ModelsHow to Evaluate Your Dragons Models???How to Use Your Dragons Models???Standing on the Shoulders of Giants
Traditional Methods for Low-Light Enhancement
Traditional methods for low-light enhancement can be broadly categorized into the following two categories:
- Histogram Equalization: this is a technique used to enhance the contrast of an image by redistributing the intensity values of the pixels. In low-light images, the majority of the pixel values are concentrated towards the lower end of the intensity range, resulting in a dark and dull appearance. Histogram equalization can be applied to these images to spread out the pixel values across the entire range of intensities, resulting in a brighter and more vibrant image.
- Retinex Models: a typical Retinex model-based approach decomposes a lowlight image into a reflection component and an illumination component by priors or regularizations. The estimated reflection component is treated as the enhanced result.
However, these traditional approaches have a few significant disadvantages or limitations:
- Unrealistic Enhancement: both Histogram Equalization and Retinex models face the problem of over-enhancement of images. This can be especially problematic for images with sharp edges and textures, which may become exaggerated and appear artificial.
- Noise: the noise already present in the images is usually ignored in such an approach, thus, it remains or becomes amplified in the enhanced results.
- Computational complexity: the runtime is relatively long because of the complicated optimization processes in the case of Retinex models and computation of image histogram and cumulative distribution function in the case of Histogram Equalization.
- Manual Tweaks: both approaches require manual tweaks and interventions to account for edge cases.
Deep Learning for Low-Light Enhancement
Since the publication of LLNet in 2017, recent years have witnessed the compelling success of deep learning-based approaches for low-light image enhancement. Deep learning solutions enjoy simpler problem formulations, better accuracy, robustness, and speed over conventional methods, thus attracting increasing attention in the space of image restoration in general.

An ongoing timeline of deep learning models for low-light image enhancement
In this report, we will explore 3 of these approaches:
- MirNetv2, as proposed by the paper Learning Enriched Features for Fast Image Restoration and Enhancement
- Zero-DCE, as proposed by the paper Zero-Reference Deep Curve Estimation for Low-Light Image Enhancement
How to Train Your Dragons Models
We will be using restorers, an ongoing effort to compile implementations of SoTA image-restoration techniques.
- Restorers currently provide clean and well-documented implementations of all of the aforementioned models using TensorFlow and Keras for easy training, inference, and benchmarking of such models.
- In the near future, we also intend to publish high-quality pre-trained models for public usage as part of this project.
- You can install restorers using:
pip install --upgrade pip setuptools
For the complete code on how to train a low-light enhancement model using restorers, you can refer to the following colab notebook:
The Dataset and the Input Pipeline
Building an input pipeline for training a low-light enhancement model using restorers and wandb
0
MirNetv2: Learning Enriched Features for Fast Image Restoration and Enhancement
MirNetv2, as proposed by the paper Learning Enriched Features for Fast Image Restoration and Enhancement is a fully convolutional architecture that learns enriched feature representations for image restoration and enhancement. It is based on a recursive residual design with the multi-scale residual block or MRB at its core. The main branch of the MRB is dedicated to maintaining spatially-precise high-resolution representations throughout the entire network. The complementary set of parallel branches provides features that are better contextualized.
The main features of the MirNetv2 architecture are:
- A novel feature extraction model that obtains a complementary set of features across multiple spatial scales while maintaining the original high-resolution features to preserve precise spatial details.
- A regularly repeated mechanism for information exchange, where the features from coarse-to-fine resolution branches are progressively fused together for improved representation learning.
- A new approach to fuse multi-scale features using a selective kernel network that dynamically combines variable receptive fields and faithfully preserves the original feature information at each spatial resolution.
- A recursive residual design that progressively breaks down the input signal in order to simplify the overall learning process and allows the construction of very deep networks.

MirNetv2 is a significant improvement on the original MirNet model for image enhancement in terms of performance, while being significantly lighter and faster. You can find a keras implementation of the original MirNet at https://keras.io/examples/vision/mirnet/
💡
Defining the MirNetv2 Model
# import MirNetv2 from restorersfrom restorers.model import MirNetv2# define the MirNetv2 model; this gives us a `tf.keras.Model`model = MirNetv2(channels=80, # number of channels in the feature mapchannel_factor=1.5, # number of multi-scale residual blocksnum_mrb_blocks=2, # factor by which number of the number of output channels varyadd_residual_connection=True, # number of groups in which the input is split along the channel axis in the convolution layers.)
For the complete code on how to train the MirNetv2 model for low-light enhancement model using restorers, you can refer to the following colab notebook:
If you're interested to find out the details regarding the implementation of MirNetv2, you can checkout out this notebook on Kaggle.
💡
The Charbonnier Loss
We will train the MirNetv2 model using Charbonnier Loss, which is a commonly used loss function for training low-light image enhancement models. It is a differentiable function that measures the difference between two images.
The Charbonnier loss function is used for low-light image enhancement because it is robust to outliers and can handle large errors between the input and target images. Low-light images often suffer from noise, low contrast, and low brightness, which can cause traditional loss functions such as the L1 or L2 loss to perform poorly. The Charbonnier loss function is more effective in such scenarios as it penalizes larger errors more heavily than smaller ones, which is useful for enhancing details in dark regions of the image.
The Charbonnier loss is given by...
...where:
- and are the input and target images, respectively
- is a small constant to avoid division by zero
from restorers.losses import CharbonnierLossloss = CharbonnierLoss(# a small constant to avoid division by zeroepsilon=1e-3,# type of reduction applied to the loss, it needs to be explicitly specified in case of distributed trainingreduction=tf.keras.losses.Reduction.SUM,)
Training and Experiment Tracking with Weights & Biases
Now comes the most fun part of a Keras-based machine learning workflow, calling model.fit() to start training. Before starting to train, we would define the W&B callbacks for Keras, which enable us not only to track all the important metrics regarding our experiment but also automatically save our model periodically as artifacts that could be used for evaluation, inference, or other future references. We'll use the following callbacks:
- The WandbMetricsLogger callback automatically logs Keras' logs dictionary that callback methods such as on_epoch_end, on_batch_end, etc, take as an argument. Using this callback provides the following features:
- system (CPU/GPU/TPU) metrics
- learning rate (both for a fixed value or a learning rate scheduler)
- The WandbModelCheckpoint callback automatically saves the Keras model or model weights periodically and uploads them to W&B as a W&B artifact for model versioning. This callback provides the following features:
- Save the model that has achieved "best performance" based on the "monitor".
- Save the model at the end of every epoch regardless of the performance.
- Save the model at the end of the epoch or after a fixed number of training batches.
- Save only model weights, or save the whole model.
- Save the model either in SavedModel format or in .h5 format.
# import Peak Signal-to-Noise Ratio and Structural Similarity metrics,# implemented as part of restorersfrom restorers.metrics import PSNRMetric, SSIMMetric# import the wandb callbacks for kerasfrom wandb.keras import WandbMetricsLogger, WandbModelCheckpoint# define the optimizeroptimizer = tf.keras.optimizers.experimental.AdamW(learning_rate=2e-4,)# define the metricspsnr_metric = PSNRMetric(max_val=1.0) # peak signal-to-noise ratio metricssim_metric = SSIMMetric(max_val=1.0) # structural similarity metric# compile te model with loss, optimizer and metricsmodel.compile(optimizer=optimizer, loss=loss, metrics=[psnr_metric, ssim_metric])callbacks = [# define the metrics logger callback;# we set the `log_freq="batch"` explicitly# to the metrics are logged both batch-wise and epoch-wiseWandbMetricsLogger(log_freq="batch"),# define the model checkpoint callbackWandbModelCheckpoint(filepath="checkpoint",monitor="val_loss",save_best_only=False,save_weights_only=False,initial_value_threshold=None,)]# call model.fit()model.fit(train_dataset,validation_data=val_dataset,epochs=50,callbacks=callbacks,)
For the complete code on how to train the MirNetv2 model for low-light enhancement model using restorers, you can refer to the following colab notebook:
Training Metrics for MirNetv2 tracked automatically using the WandbMetricsLogger callback
2
NAFNet: Nonlinear Activation Free Network for Image Restoration
NAFNet, or the Nonlinear Activation Free Network, is a simple baseline model for all kinds of image restoration tasks as proposed by the paper Simple Baselines for Image Restoration. The aim of the authors was to create a simple baseline that exceeds the then SoTA methods in terms of performance and is also computationally efficient. To further simplify the baseline, the authors reveal that the nonlinear activation functions, e.g. Sigmoid, ReLU, GELU, Softmax, etc., are not necessary: they could be replaced by multiplication or removed. Some of the most significant features of NAFNet are:
- NAFNet adopts a classic single-staged UNet architecture instead of the multi-scale residual architecture of MirNetv2. This architecture consists of NAFBlocks, which are the basic building blocks of NAFNet.

- The authors experiment with multiple normalization techniques in the NAFBlock:
- Batch Normalization is rejected since small batch sizes may introduce unstable statistics (check this paper for more details)
- Although Instance Normalization is able to handle small batch sizes, it has been noted that
- Instance Normalization does not always bring performance gains
- The authors end up favoring Layer Normalization since,
- It has been proven to handle small batch sizes
- It can make training smooth, even with a 10 times increase in the learning rate, which can make the model converge faster.
- Instead of any non-linear activation functions, the authors propose using SimpleGate, which directly divides the input feature map into two parts in the channel dimension and multiplies them element-wise.

Defining the NAFNet Model
# import NAFNet from restorersfrom restorers.model import NAFNet# define the NAFNet model; this gives us a `tf.keras.Model`model = NAFNet(filters=16, # the starting filter sizemiddle_block_num=1, # denotes the number of middle blocks, where each middle block is a single NAFBlock unit# the tuple size denotes the number of encoder blocks,# where each tuple entry denotes the number of NAFBlocks in the corresponding encoder blockencoder_block_nums=(1, 1, 1, 1),# the tuple size denotes the number of decoder blocks,# where each tuple entry denotes the number of NAFBlocks in the corresponding decoder blockdecoder_block_nums=(1, 1, 1, 1),)# Note that len(decoder_block_nums) should be the same as the len(encoder_block_nums).
Training NAFNet
We train NAFNet using the exact same input and training pipeline as MirNetv2 on the LoL dataset.
For the complete code on how to train NAFNet for low-light enhancement model using restorers, you can refer to the following colab notebook:
Training Metrics for MirNetv2 tracked automatically using the WandbMetricsLogger callback
2
For the complete code on how to train NAFNet for low-light enhancement model using restorers, you can refer to the following colab notebook:
Zero-DCE: Zero-Reference Deep Curve Estimation for Low-Light Image Enhancement
Zero-Reference Deep Curve Estimation or Zero-DCE formulates low-light image enhancement as the task of estimating an image-specific tonal curve) with a deep neural network. Instead of performing image-to-image mapping, in the case of Zero-DCE, the problem is reformulated as an image-specific curve estimation problem.
In particular, the proposed method takes a low-light image as input and produces high-order curves as its output. These curves are then used for pixel-wise adjustment on the dynamic range of the input to obtain an enhanced image. A unique advantage of this approach is that its zero-reference, i.e., it does not require any paired or even unpaired data in the training process as in existing CNN-based (such as Mirnet) and GAN-based methods (such as EnlightenGAN).
Understanding light-enhancement curves
A light-enhancement curve is a kind of curve that can map a low-light image to its enhanced version automatically, where the self-adaptive curve parameters are solely dependent on the input image. When designing such a curve, three objectives should be taken into account:
- Each pixel value of the enhanced image should be in the normalized range , in order to avoid information loss induced by overflow truncation.
- It should be monotonous to preserve the contrast between neighboring pixels.
- The shape of this curve should be as simple as possible, and the curve should be differentiable to allow backpropagation.
The light-enhancement curve is separately applied to three RGB channels instead of solely on the illumination channel. The three-channel adjustment can better preserve the inherent color and reduce the risk of over-saturation.

The DCE-Net
- The DCE-Net, or the Deep Curve-estimation Network, is a lightweight deep neural network that learns the mapping between an input image and its best-fitting curve parameter maps.
- The input to the DCE-Net is a low-light image, while the outputs are a set of pixel-wise curve parameter maps for corresponding higher-order curves.
- Some architectural details regarding DCE-Net:
- It is a plain CNN of seven convolutional layers with symmetrical concatenation.
- Each layer consists of 32 convolutional kernels of size 3×3 and stride 1, followed by the ReLU activation function.
- The last convolutional layer is followed by the Tanh activation function, which produces 24 parameter maps for 8 iterations, where each iteration requires three curve parameter maps for the three channels.

# Import ZeroDCE from restorersfrom restorers.model.zero_dce import ZeroDCE# define the ZeroDCE model; this gives us a `tf.keras.Model`model = ZeroDCE(num_intermediate_filters=32, # number of filters in the intermediate convolutional layersnum_iterations=8, # number of iterations of enhancementdecoder_channel_factor=1 # factor by which number filters in the decoder of deep curve estimation layer is multiplied)
For the complete code on how to train Zero-DCE for low-light enhancement model using restorers, you can refer to the following colab notebook:
Non-Reference Loss Functions
To enable zero-reference learning in DCE-Net, the Zero-DCE framework employs a set of differentiable non-reference losses that allow us to evaluate the quality of enhanced images. Defining a non-reference loss function that does not requires a ground truth image allows us to train their model on low-light images only. We would discuss and implement the four types of losses adopted by the Zero-DCE framework to train the DCE-Net.
Color Constancy Loss: As per the Gray-World color constancy hypothesis that color in each sensor channel averages to gray over the entire image, the authors of Zero-DCE design a color constancy loss to correct the potential color deviations in the enhanced image and also build the relations among the three adjusted channels. The Color Constancy Loss is given by:
Exposure Control Loss: In order to restrain under/over-exposed regions in an image, the Zero-DCE framework employs an exposure control loss to control the exposure level. The exposure control loss measures the distance between the average intensity value of a local region to the well-exposedness level E=0.6. The Exposure Control Loss is given by:
Illumination Smoothness Loss: In order to preserve the monotonicity relations between neighboring pixels, the Zero-DCE framework employs an illumination smoothness loss to each curve parameter map. The Illumination Smoothness Loss for a given curve parameter map is given by:
where and represent the horizontal and vertical gradient operations, respectively.
Spatial Constancy Loss: The spatial consistency loss encourages spatial coherence of the enhanced image by preserving the difference of neighboring regions between the input image and its enhanced version. The Spatial Constancy Loss is given by:
where...
- is the four neighboring regions (top, down, left, right) centered at the region
- and denote the average intensity value of the local region in the enhanced version and input image, respectively.
All these loss functions have been implemented as part of the restorers.losses API and are automatically initialized when calling model.compile.
model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),weight_exposure_loss=1.0, # weight of the exposure control lossweight_color_constancy_loss=0.5, # weight of the color constancy lossweight_illumination_smoothness_loss=20, # weight of the illumination smoothness loss)
For the complete code on how to train Zero-DCE for low-light enhancement model using restorers, you can refer to the following colab notebook:
Training Zero-DCE
model.fit(train_dataset,validation_data=val_dataset,epochs=experiment_configs.training_configs.epochs,callbacks=callbacks,)
Training Metrics for Zero-DCE tracked automatically using the WandbMetricsLogger callback
4
For the complete code on how to train Zero-DCE for low-light enhancement model using restorers, you can refer to the following colab notebook:
If you're interested to find out the details regarding the implementation of Zero-DCE, you can checkout out this notebook on Kaggle.
💡
How to Evaluate Your Dragons Models???
We will now evaluate our model on the LoL dataset using the restorers.evaluation API and logging the results in our Weights & Biases dashboard.
from restorers.evaluation import LoLEvaluatorfrom restorers.metrics import PSNRMetric, SSIMMetric# define new wandb run for evaluationwandb.init(project="low-light-enhancement", job_type="eval")# Define the Evaluator for LoL datasetevaluator = LoLEvaluator(# pass the list of Keras metrics to be evaluated formetrics=[PSNRMetric(max_val=1.0), SSIMMetric(max_val=1.0)],# pass the wandb artifact for the LoL datasetdataset_artifact_address=""ml-colabs/dataset/LoL:v0"",input_size=256,)# initialize model from wandb artifactsevaluator.initialize_model_from_wandb_artifact("artifact-address-of-your-model-checkpoint")# evaluateevaluator.evaluate()
For the complete code on how to evaluate a low-light enhancement model using restorers on the LoL dataset, you can refer to the following colab notebook:
Evaluating low-light enhancement models using Restorers and Weights & Biases
6
For the complete code on how to evaluate a low-light enhancement model using restorers on the LoL dataset, you can refer to the following colab notebook:
How to Use Your Dragons Models???
import wandb# import Inferer class for low-light enhancementfrom restorers.inference import LowLightInferer# initialize a wandb run for inferencewandb.init(project="low-light-enhancement", entity="ml-colabs", job_type="inference")# initialize the infererinferer = LowLightInferer(resize_factor=1,model_alias="Zero-DCE")# intialize the model from wandb artifactsinferer.initialize_model_from_wandb_artifact("ml-colabs/low-light-enhancement/run_oaa25znm_model:v99")# infer on a directory of imagesinferer.infer("./dark_images")# or infer on a single imageinferer.infer("./dark_images.png")
For the complete code on how to perform inference with a low-light enhancement model using restorers on the LoL dataset, you can refer to the following colab notebook:
Inference!!!
6
For the complete code on how to perform inference with a low-light enhancement model using restorers on the LoL dataset, you can refer to the following colab notebook:
Standing on the Shoulders of Giants
- This talk and, to some extent, the idea of building restorers came up after reading the paper Low-Light Image and Video Enhancement Using Deep Learning: A Survey by Chongyi Li, who also co-authored Zero-DCE.
- MirNetv2, as proposed by the paper Learning Enriched Features for Fast Image Restoration and Enhancement
- Zero-DCE, as proposed by the paper Zero-Reference Deep Curve Estimation for Low-Light Image Enhancement
- I'm grateful to have been able to collaborate on this project with:
- We are grateful to Sayak Paul for the TFLite Benchmarks of our implementation of MirNetv2, which can be found here.
- Shoutout to Ayush Thakur, who has been working with me on improving the Weights & Biases callbacks for Keras.
- We are grateful to Weights & Biases and the ML Developer Programs team at Google for providing us with cloud credits that made training these models a reality.
- We are training models on even larger datasets in order to increase the performance of the model; you can check them here.
- We are adding implementations of more models not just for low-light enhancement but also for other image restoration tasks such as defocus-deblurring, deraining, denoising, super-resolution, and many more!!!
- We are simultaneously also working on improving the API design and documentation of the library.
- If you wish to contribute to restorers or, in general, are curious regarding what we are working on, check out:
Add a comment
Tags: Articles, Computer Vision, Experiment, Tutorial, Image manipulation, Panels, Plots, Keras, PyTorch
Iterate on AI agents and models faster. Try Weights & Biases today.