Low Light Image Enhancement using CNN

Low Light Image Enhancement using CNN

This project demonstrates how to build a Convolutional Neural Network (CNN) model to enhance low-light images. Using a combination of CNN layers, we aim to transform dark and noisy images into more vibrant and clear ones. The project uses the LOL dataset, which consists of paired low-light and bright images, and includes techniques for adding noise to images for training a robust enhancement model.

Table of Content

  1. Prerequisites

  2. Installation Steps

  3. Model Overview

  4. Model Summary

  5. Training

  6. Inference

  7. Example Usage

  8. Results

  9. References

1. Prerequisites

  • Google Colab (Recommended for using the mounted Google Drive and GPU support)

  • Keras

  • OpenCV

  • NumPy

  • Matplotlib

  • TQDM (for progress tracking)

2. Installation Steps

  1. Clone the repository or download the code files.

  2. Upload the LOL dataset(https://drive.google.com/drive/folders/1UBsbY3CczeT03BOF3a7-FJoHHL4aCHWf?usp=sharing) to your Google Drive.

  3. Mount Google Drive in your Colab session:

from google.colab import drive
drive.mount('/content/drive')

4. Install necessary packages:

import numpy as np 
import pandas as pd 
import os
import cv2 as cv
import matplotlib.pyplot as plt
from keras import backend as K
from keras.layers import add, Conv2D,MaxPooling2D,UpSampling2D,Input,BatchNormalization, RepeatVector, Reshape
from keras.models import Model
np.random.seed(1)

5. Dataset: The dataset used is the LOL dataset for low-light image enhancement. The dataset consists of paired low-light (low) and normal light (high) images. You can download the dataset from the following link:

https://drive.google.com/drive/folders/1UBsbY3CczeT03BOF3a7-FJoHHL4aCHWf?usp=sharing

6. LOL Dataset: Place the dataset in your Google Drive and modify the InputPath to point to the correct dataset directory.

InputPath = "/content/drive/MyDrive/ml_projects/low_light_image_enhancer/LOLdataset/train/high"

3. Model Overview

This project uses a CNN model with several convolutional layers to process and enhance the input low-light images. Below are the main components of the model:

  1. Noise Addition: Salt-and-pepper noise is artificially added to the input images to simulate real-world noise scenarios.
def addNoise(image):
    # salt and pepper noise
    noiseAddedImage = np.copy(image)

    # Adding salt (white) noise
    num_salt = np.ceil(image.size * 0.01)  # Percentage of image to be "salt"
    coords = [np.random.randint(0, i, int(num_salt)) for i in image.shape[:2]]
    noiseAddedImage[coords[0], coords[1], :] = 1  # Apply to all channels

    # Adding pepper (black) noise
    num_pepper = np.ceil(image.size * 0.01)  # Percentage of image to be "pepper"
    coords = [np.random.randint(0, i, int(num_pepper)) for i in image.shape[:2]]
    noiseAddedImage[coords[0], coords[1], :] = 0  # Apply to all channels

    return noiseAddedImage

2. Data Preprocessing: The images are resized to 500x500 pixels and converted from BGR to RGB format. The images are darkened by manipulating the HSV channels to simulate low-light conditions.

def PreProcessData(ImagePath):
    X_ = []
    y_ = []
    count = 0
    # Iterate over all images in the provided directory
    for imageName in tqdm(os.listdir(HighPath)):
        count += 1
        imagePath = os.path.join(HighPath, imageName)

        # Load the image
        low_img = cv.imread(imagePath)
        if low_img is None:
            print(f"Warning: Skipping {imageName}, could not load the image.")
            continue

        # Convert BGR to RGB
        low_img = cv.cvtColor(low_img, cv.COLOR_BGR2RGB)

        # Resize the image to 500x500
        low_img = cv.resize(low_img, (500, 500))

        # Convert to HSV and darken the image by reducing the value channel
        hsv = cv.cvtColor(low_img, cv.COLOR_RGB2HSV)
        hsv[..., 2] = hsv[..., 2] * 0.2
        img_1 = cv.cvtColor(hsv, cv.COLOR_HSV2RGB)

        # Apply noise to the darkened image
        Noisey_img = addNoise(img_1)

        # Append the processed noisy image and original low image to the lists
        X_.append(Noisey_img)
        y_.append(low_img)

    # Convert the lists to NumPy arrays
    X_ = np.array(X_)
    y_ = np.array(y_)

    return X_, y_

3. CNN Architecture: A custom CNN architecture is designed, consisting of multiple layers of Conv2D with varying filter sizes, combined with add layers to combine different feature maps.

def InstantiateModel(in_):
        model_1 = Conv2D(16,(3,3), activation='relu',padding='same',strides=1)(in_)
        model_1 = Conv2D(32,(3,3), activation='relu',padding='same',strides=1)(model_1)
        model_1 = Conv2D(64,(2,2), activation='relu',padding='same',strides=1)(model_1)

        model_2 = Conv2D(32,(3,3), activation='relu',padding='same',strides=1)(in_)
        model_2 = Conv2D(64,(2,2), activation='relu',padding='same',strides=1)(model_2)

        model_2_0 = Conv2D(64,(2,2), activation='relu',padding='same',strides=1)(model_2)

        model_add = add([model_1,model_2,model_2_0])

        model_3 = Conv2D(64,(3,3), activation='relu',padding='same',strides=1)(model_add)
        model_3 = Conv2D(32,(3,3), activation='relu',padding='same',strides=1)(model_3)
        model_3 = Conv2D(16,(2,2), activation='relu',padding='same',strides=1)(model_3)

        model_3_1 = Conv2D(32,(3,3), activation='relu',padding='same',strides=1)(model_add)
        model_3_1 = Conv2D(16,(2,2), activation='relu',padding='same',strides=1)(model_3_1)

        model_3_2 = Conv2D(16,(2,2), activation='relu',padding='same',strides=1)(model_add)

        model_add_2 = add([model_3_1,model_3_2,model_3])

        model_4 = Conv2D(16,(3,3), activation='relu',padding='same',strides=1)(model_add_2)
        model_4_1 = Conv2D(16,(3,3), activation='relu',padding='same',strides=1)(model_add)

        model_add_3 = add([model_4_1,model_add_2,model_4])

        model_5 = Conv2D(16,(3,3), activation='relu',padding='same',strides=1)(model_add_3)
        model_5 = Conv2D(16,(2,2), activation='relu',padding='same',strides=1)(model_add_3)

        model_5 = Conv2D(3,(3,3), activation='relu',padding='same',strides=1)(model_5)

        return model_5

4. Model Summary

* Input: 500x500 RGB image.

* Output: Enhanced 500x500 RGB image.

* Optimizer: Adam

* Loss: Mean Squared Error (MSE)

5. Training

The model is trained using noisy, darkened images as input and the corresponding high-light images as ground truth. The model is compiled using the Adam optimizer and trained over multiple epochs.

Model_Enhancer.fit(GenerateInputs(X_, y_), epochs=53, verbose=1, steps_per_epoch=8, shuffle=True)

6. Inference

Once the model is trained, you can perform inference on new low-light images. The function ExtractTestInput is used to preprocess test images, and the trained model generates enhanced images.

Prediction = Model_Enhancer.predict(image_for_test)

7. Example Usage

1.Load a test image.

2.Apply noise and darkening to simulate a low-light condition.

3.Run the model to get the enhanced image.

4.Compare the low-light image, the original image, and the enhanced output.

image_for_test = ExtractTestInput("/path/to/test/image.png")
Prediction = Model_Enhancer.predict(image_for_test)

8. Results

Below are sample outputs from the model:

  1. Original Image: The ground truth image in normal lighting.

  2. Low Light Image: The darkened and noisy input to the model.

  3. Enhanced Image: The output of the model, which restores brightness and reduces noise.

9. References

  1. LOL Dataset

  2. Keras Documentation

  3. OpenCV Documentation

  4. My Github repo

  5. Referred Model