低光照圖像增強算法彙總

1、場景需求

  在現實場景中,由於光線、視角等問題會導致我們拍攝出來的照片比較陰暗,具體的圖片如下圖中的1、3、5列所示,然後這些陰暗的圖片不僅會影響我們的觀察,而且會極大的影響計算機視覺處理算法的效果,2、4、6列表示的是使用了低光照圖像增強算法之後的效果。本文主要針對低光照的圖片展開論述,對經典的一些低光照圖像增強算法進行了總結和初略的分析。
在這裏插入圖片描述

2、Retinex算法

論文鏈接-Github鏈接

2.1 Retinex算法簡介

  Retinex是一種常用的建立在科學實驗和科學分析基礎上的圖像增強方法,它是由Edwin.H. Land於1963年提出的。Retinex的基礎理論是物體的顏色是由物體對長波(紅色)、中波(綠色)、短波(藍色)光線的反射能力來決定的,而不是由反射光強度的絕對值來決定的,物體的色彩不受光照非均勻性的影響,具有一致性,即retinex是以色感一致性(顏色恆常性)爲基礎的。**不同於傳統的線性、非線性的只能增強圖像某一類特徵的方法,Retinex可以在動態範圍壓縮、邊緣增強和顏色恆定三個方面做到平衡,因此可以對各種不同類型的圖像進行自適應的增強。**具體的原理請參考該博客

2.2 Retinex核心代碼實現

# coding=utf-8
import numpy as np
import cv2

def singleScaleRetinex(img, sigma):
    '''單尺度Retinex函數'''
    retinex = np.log10(img) - np.log10(cv2.GaussianBlur(img, (0, 0), sigma))
    return retinex

def multiScaleRetinex(img, sigma_list):
    '''多尺度Retinex函數'''
    # 提前分配空間
    retinex = np.zeros_like(img)
    # 遍歷所有的尺度
    for sigma in sigma_list:
    	# 對計算的結果進行疊加
        retinex += singleScaleRetinex(img, sigma)
    # 計算多個尺度的平均值
    retinex = retinex / len(sigma_list)
    return retinex

def colorRestoration(img, alpha, beta):
	'''顏色灰度函數'''
    img_sum = np.sum(img, axis=2, keepdims=True)
    color_restoration = beta * (np.log10(alpha * img) - np.log10(img_sum))
    return color_restoration

def simplestColorBalance(img, low_clip, high_clip):    
    '''最簡單的顏色均衡函數'''
    total = img.shape[0] * img.shape[1]
    for i in range(img.shape[2]):
        unique, counts = np.unique(img[:, :, i], return_counts=True)
        current = 0
        for u, c in zip(unique, counts):            
            if float(current) / total < low_clip:
                low_val = u
            if float(current) / total < high_clip:
                high_val = u
            current += c    
        img[:, :, i] = np.maximum(np.minimum(img[:, :, i], high_val), low_val)
    return img    

def MSRCR(img, sigma_list, G, b, alpha, beta, low_clip, high_clip):
	'''MSRCR函數'''
    img = np.float64(img) + 1.0
    # 對原圖先做多尺度的Retinex
    img_retinex = multiScaleRetinex(img, sigma_list)    
    # 對原圖做顏色恢復
    img_color = colorRestoration(img, alpha, beta)   
    # 進行圖像融合 
    img_msrcr = G * (img_retinex * img_color + b)
    
    for i in range(img_msrcr.shape[2]):
        img_msrcr[:, :, i] = (img_msrcr[:, :, i] - np.min(img_msrcr[:, :, i])) / \
                             (np.max(img_msrcr[:, :, i]) - np.min(img_msrcr[:, :, i])) * \
                             255
    # 將圖像調整到[0,255]範圍內          
    img_msrcr = np.uint8(np.minimum(np.maximum(img_msrcr, 0), 255))
    # 做簡單的顏色均衡
    img_msrcr = simplestColorBalance(img_msrcr, low_clip, high_clip)       
    return img_msrcr

def automatedMSRCR(img, sigma_list):
	'''automatedMSRCR函數'''
    img = np.float64(img) + 1.0
    img_retinex = multiScaleRetinex(img, sigma_list)
    for i in range(img_retinex.shape[2]):
        unique, count = np.unique(np.int32(img_retinex[:, :, i] * 100), return_counts=True)
        for u, c in zip(unique, count):
            if u == 0:
                zero_count = c
                break
            
        low_val = unique[0] / 100.0
        high_val = unique[-1] / 100.0
        for u, c in zip(unique, count):
            if u < 0 and c < zero_count * 0.1:
                low_val = u / 100.0
            if u > 0 and c < zero_count * 0.1:
                high_val = u / 100.0
                break 
        img_retinex[:, :, i] = np.maximum(np.minimum(img_retinex[:, :, i], high_val), low_val)
        img_retinex[:, :, i] = (img_retinex[:, :, i] - np.min(img_retinex[:, :, i])) / \
                               (np.max(img_retinex[:, :, i]) - np.min(img_retinex[:, :, i])) \
                               * 255
    img_retinex = np.uint8(img_retinex)
    return img_retinex

def MSRCP(img, sigma_list, low_clip, high_clip):
	'''MSRCP函數'''
    img = np.float64(img) + 1.0
    intensity = np.sum(img, axis=2) / img.shape[2]    
    retinex = multiScaleRetinex(intensity, sigma_list)
    intensity = np.expand_dims(intensity, 2)
    retinex = np.expand_dims(retinex, 2)
    intensity1 = simplestColorBalance(retinex, low_clip, high_clip)
    intensity1 = (intensity1 - np.min(intensity1)) / \
                 (np.max(intensity1) - np.min(intensity1)) * \
                 255.0 + 1.0
    img_msrcp = np.zeros_like(img)
    
    for y in range(img_msrcp.shape[0]):
        for x in range(img_msrcp.shape[1]):
            B = np.max(img[y, x])
            A = np.minimum(256.0 / B, intensity1[y, x, 0] / intensity[y, x, 0])
            img_msrcp[y, x, 0] = A * img[y, x, 0]
            img_msrcp[y, x, 1] = A * img[y, x, 1]
            img_msrcp[y, x, 2] = A * img[y, x, 2]
    img_msrcp = np.uint8(img_msrcp - 1.0)
    return img_msrcp

2.3 Retinex算法效果展示與分析

在這裏插入圖片描述
  上圖展示了Retinex算法在一些真實數據上面的效果。上面主要展示了該算法在一些文檔上面的增強效果,第一行的5張圖片表示的是原始的輸入圖片,第二行的5張圖片表示的是使用Retinex算法增強之後的效果。通過觀察我們可以發現,該算法不僅能夠增強圖像的亮度信息,同時可以去除圖片中的部分陰影信息;但是該算法的運算速度比較慢,不能應用到一些實時的場景中。

3、LIME算法

論文鏈接-LIME主頁-Github鏈接

3.1、LIME算法簡介

  LIME算法是一個簡單而高效的低光照圖像增強算法。該算法首先通過在R、G和B通道中找到最大值來單獨估計每個像素的照明;然後我們通過在初始光照圖上施加一個結構先驗來細化它,作爲最終的光照映射;最後根據光照映射就可以生成最終的增強圖像

3.2、LIME核心代碼實現

// 導入頭文件
#include "lime.h"
#include <vector>
#include <iostream>

// 定義命名空間
namespace feature
{
    // 獲取原圖像的通道
    lime::lime(cv::Mat src)
    {
        channel = src.channels();
    }
    
    cv::Mat lime::lime_enhance(cv::Mat &src)
    {
        cv::Mat img_norm;
        // 圖像歸一化
        src.convertTo(img_norm, CV_32F, 1 / 255.0, 0);
        // 提前分配空間
        cv::Size sz(img_norm.size());
        cv::Mat out(sz, CV_32F, cv::Scalar::all(0.0));

        auto gammT = out.clone();
        // 根據通道做不同的處理
        if (channel == 3)
        {
            Illumination(img_norm, out);
            Illumination_filter(out, gammT);

            //lime
            std::vector<cv::Mat> img_norm_rgb;
            cv::Mat img_norm_b, img_norm_g, img_norm_r;

            cv::split(img_norm, img_norm_rgb);

            img_norm_g = img_norm_rgb.at(0);
            img_norm_b = img_norm_rgb.at(1);
            img_norm_r = img_norm_rgb.at(2);

            cv::Mat one = cv::Mat::ones(sz, CV_32F);

            float nameta = 0.9;
            auto g = 1 - ((one - img_norm_g) - (nameta * (one - gammT))) / gammT;
            auto b = 1 - ((one - img_norm_b) - (nameta * (one - gammT))) / gammT;
            auto r = 1 - ((one - img_norm_r) - (nameta * (one - gammT))) / gammT;

            cv::Mat g1, b1, r1;

            //TODO <=1
            threshold(g, g1, 0.0, 0.0, 3);
            threshold(b, b1, 0.0, 0.0, 3);
            threshold(r, r1, 0.0, 0.0, 3);

            img_norm_rgb.clear();
            img_norm_rgb.push_back(g1);
            img_norm_rgb.push_back(b1);
            img_norm_rgb.push_back(r1);

            cv::merge(img_norm_rgb,out_lime);
            out_lime.convertTo(out_lime,CV_8U,255);

        }
        else if(channel == 1)
        {
            Illumination_filter(img_norm, gammT);
            cv::Mat one = cv::Mat::ones(sz, CV_32F);
            float nameta = 0.9;
            //std::cout<<img_norm.at<float>(1,1)<<std::endl;
            auto out = 1 - ((one - img_norm) - (nameta * (one - gammT))) / gammT;

            threshold(out, out_lime, 0.0, 0.0, 3);

            out_lime.convertTo(out_lime,CV_8UC1,255);

        }
        else
        {
            std::cout<<"There is a problem with the channels"<<std::endl;
            exit(-1);
        }
        return out_lime.clone();
    }

    void lime::Illumination_filter(cv::Mat& img_in,cv::Mat& img_out)
    {
        int ksize = 5;
        //mean filter
        blur(img_in,img_out,cv::Size(ksize,ksize));
        //GaussianBlur(img_in,img_mean_filter,Size(ksize,ksize),0,0);

        //gamma
        int row = img_out.rows;
        int col = img_out.cols;
        float tem;
        float gamma = 0.8;
        for(int i=0;i<row;i++)
        {
            for(int j=0;j<col;j++)
            {
                tem = pow(img_out.at<float>(i,j),gamma);
                tem = tem <= 0 ? 0.0001 : tem;  //  double epsolon = 0.0001;
                tem = tem > 1 ? 1 : tem;

                img_out.at<float>(i,j) = tem;
            }
        }
    }
    void lime::Illumination(cv::Mat& src,cv::Mat& out)
    {
        int row = src.rows, col = src.cols;
        for(int i=0;i<row;i++)
        {
            for(int j=0;j<col;j++)
            {
                out.at<float>(i,j) = lime::compare(src.at<cv::Vec3f>(i,j)[0],
                                                   src.at<cv::Vec3f>(i,j)[1],
                                                   src.at<cv::Vec3f>(i,j)[2]);
            }
        }
    }
}

3.3、LIME算法效果展示與分析

在這裏插入圖片描述
  上圖展示了LIME算法在一些圖片上面的效果,第一行表示的是原始的輸入圖片,第二行表示的是由LIME算法輸出的光照映射,第三行表示的是經過LIME算法增強之後的效果。通過觀察我們可以得出,LIME算法能夠較好的處理一些低光照圖片,美中不足的是經過LIME算法增強之後的圖片的色彩好像不太理想。

4、RetinexNet算法

論文鏈接-Github鏈接-項目主頁

4.1 RetinexNet算法簡介

  RetinexNet是Retinex算法的加強版,主要的思路是通過在收集到的低光照數據塊上面訓練一個神經網絡。整個網路分爲兩部分,Decom-Net網絡用來實現圖像分解,Enhance-Net網絡用來實現光照調節。在訓練Decom-Net的過程中,沒有GT,學習網絡時只需滿足以下關鍵約束條件包括由成對的低/正常光圖像共享的一致反射率以及光照的一致性,由Enhance-Net來實現光照的增強。RetinexNet是一個端到端的低光照增強網絡,大量的實驗表明,該方法不僅可以獲得令人滿意的低光增強效果,而且可以很好地表示圖像分解

4.2 RetinexNet網絡詳解

在這裏插入圖片描述  上圖展示了RetinexNet網絡的實現細節,整個圖像增強的過程分爲三步,具體包括圖像分解、亮度調整,圖像重建。在圖像分解階段,Decom-Net子網絡將輸入的圖像分解爲反射率圖像和光照圖像兩部分,兩個部分之間共享權重,如上圖中的Snormal和Slow所示,輸入的兩個圖像都被分解成兩個部分;在光照調節階段,一個基於編解碼架構的Enhance-Net網絡用來對光照圖像進行光照增強,該子網絡中包含着一些殘差連接用來解決梯度消失問題,同時對反射率圖像執行去噪操作;在圖像重建階段,將經過光照增強的光照圖像和經過去噪的反射率圖像重新組合起來形成最終的增強圖片

4.3 RetinexNet核心代碼實現

# 導入相應的python包
from __future__ import print_function

import os
import time
import random

from PIL import Image
import tensorflow as tf
import numpy as np

from utils import *

# 定義concat操作
def concat(layers):
    return tf.concat(layers, axis=3)

def DecomNet(input_im, layer_num, channel=64, kernel_size=3):
	'''分解子網絡函數'''
    input_max = tf.reduce_max(input_im, axis=3, keepdims=True)
    input_im = concat([input_max, input_im])
    with tf.variable_scope('DecomNet', reuse=tf.AUTO_REUSE):
        conv = tf.layers.conv2d(input_im, channel, kernel_size * 3, padding='same', activation=None, name="shallow_feature_extraction")
        for idx in range(layer_num):
            conv = tf.layers.conv2d(conv, channel, kernel_size, padding='same', activation=tf.nn.relu, name='activated_layer_%d' % idx)
        conv = tf.layers.conv2d(conv, 4, kernel_size, padding='same', activation=None, name='recon_layer')

    R = tf.sigmoid(conv[:,:,:,0:3])
    L = tf.sigmoid(conv[:,:,:,3:4])

    return R, L

def RelightNet(input_L, input_R, channel=64, kernel_size=3):
	'''光照增強子網絡函數'''
    input_im = concat([input_R, input_L])
    with tf.variable_scope('RelightNet'):
        conv0 = tf.layers.conv2d(input_im, channel, kernel_size, padding='same', activation=None)
        conv1 = tf.layers.conv2d(conv0, channel, kernel_size, strides=2, padding='same', activation=tf.nn.relu)
        conv2 = tf.layers.conv2d(conv1, channel, kernel_size, strides=2, padding='same', activation=tf.nn.relu)
        conv3 = tf.layers.conv2d(conv2, channel, kernel_size, strides=2, padding='same', activation=tf.nn.relu)
        
        up1 = tf.image.resize_nearest_neighbor(conv3, (tf.shape(conv2)[1], tf.shape(conv2)[2]))
        deconv1 = tf.layers.conv2d(up1, channel, kernel_size, padding='same', activation=tf.nn.relu) + conv2
        up2 = tf.image.resize_nearest_neighbor(deconv1, (tf.shape(conv1)[1], tf.shape(conv1)[2]))
        deconv2= tf.layers.conv2d(up2, channel, kernel_size, padding='same', activation=tf.nn.relu) + conv1
        up3 = tf.image.resize_nearest_neighbor(deconv2, (tf.shape(conv0)[1], tf.shape(conv0)[2]))
        deconv3 = tf.layers.conv2d(up3, channel, kernel_size, padding='same', activation=tf.nn.relu) + conv0
        
        deconv1_resize = tf.image.resize_nearest_neighbor(deconv1, (tf.shape(deconv3)[1], tf.shape(deconv3)[2]))
        deconv2_resize = tf.image.resize_nearest_neighbor(deconv2, (tf.shape(deconv3)[1], tf.shape(deconv3)[2]))
        feature_gather = concat([deconv1_resize, deconv2_resize, deconv3])
        feature_fusion = tf.layers.conv2d(feature_gather, channel, 1, padding='same', activation=None)
        output = tf.layers.conv2d(feature_fusion, 1, 3, padding='same', activation=None)
    return output

4.4 RetinexNet算法效果展示與分析

在這裏插入圖片描述
在這裏插入圖片描述
  上面第一張展示了Retinex-Net算法在一些室內和室外場景下和其它不同增強算法的比較結果,第一列表示原始的輸入圖片,最後一列表示使用Retinex-Net增強之後的結果。與其它的算法相比,Retinex-Net算法能夠獲得更清晰的增強效果,圖像的細節更加豐富。第二張展示了Retinex-Net算法在真實的文檔圖片上面的增強效果,第一行表示原始的輸入圖片,第二行表示增強後的圖片,增強後的圖像看起來更加清晰,便於後續算法的處理。

5、MBLLEN算法

論文鏈接-Github鏈接-項目主頁

5.1 MBLLEN算法簡介

  MBLLEN算法是一個多分支低光照圖像增強網絡。該算法的核心思想是在不同等級中提取出豐富的圖像特徵,因此我們可以通過多個子網絡做圖像增強,最後通過多分支融合產生輸出圖像。圖像的質量從不同的方向得到了提升。該算法不僅僅能用來進行圖像增強,而且可以用來進行視頻增強

5.2 MBLLEN網絡詳解

在這裏插入圖片描述
  上圖展示了MBLLEN的網絡架構,該網絡主要包含三個主要的模塊,具體包括FEM模塊、EM模塊和FM模塊。FEM模塊(feature extraction module ),即特徵檢測模塊,其主要作用是從圖像中獲取關鍵的特徵,如圖中所示,主要有一些WXHX32的卷積層構成;EM模塊(enhancement module),即增強模塊,其主要的作用是通過編解碼架構對圖像的特徵進行增強,具體的細節如圖中所示;FM模塊(fusion module),即融合模塊,其主要的作用是將不同等級輸出的結果融合起來,從而形成最終的結果

5.3 MBLLEN核心代碼實現

# 導入相應的python包
from keras.layers import Input, Conv2D, Conv2DTranspose, Concatenate
from keras.applications.vgg19 import VGG19
from keras.models import Model

def build_vgg():
	''''搭建VGG基準網絡函數'''
	# 使用預訓練的VGG網絡
    vgg_model = VGG19(include_top=False, weights='imagenet')
    vgg_model.trainable = False
    return Model(inputs=vgg_model.input, outputs=vgg_model.get_layer('block3_conv4').output)

def build_mbllen(input_shape):
    def EM(input, kernal_size, channel):
    	'''搭建EM子網絡函數'''
        conv_1 = Conv2D(channel, (3, 3), activation='relu', padding='same', data_format='channels_last')(input)
        conv_2 = Conv2D(channel, (kernal_size, kernal_size), activation='relu', padding='valid', data_format='channels_last')(conv_1)
        conv_3 = Conv2D(channel*2, (kernal_size, kernal_size), activation='relu', padding='valid', data_format='channels_last')(conv_2)
        conv_4 = Conv2D(channel*4, (kernal_size, kernal_size), activation='relu', padding='valid', data_format='channels_last')(conv_3)
        conv_5 = Conv2DTranspose(channel*2, (kernal_size, kernal_size), activation='relu', padding='valid', data_format='channels_last')(conv_4)
        conv_6 = Conv2DTranspose(channel, (kernal_size, kernal_size), activation='relu', padding='valid', data_format='channels_last')(conv_5)
        res = Conv2DTranspose(3, (kernal_size, kernal_size), activation='relu', padding='valid', data_format='channels_last')(conv_6)
        return res

    inputs = Input(shape=input_shape)
    FEM = Conv2D(32, (3, 3), activation='relu', padding='same', data_format='channels_last')(inputs)
    EM_com = EM(FEM, 5, 8)

    for j in range(3):
        for i in range(0, 3):
            FEM = Conv2D(32, (3, 3), activation='relu', padding='same', data_format='channels_last')(FEM)
            EM1 = EM(FEM, 5, 8)
            EM_com = Concatenate(axis=3)([EM_com, EM1])

    outputs = Conv2D(3, (1, 1), activation='relu', padding='same', data_format='channels_last')(EM_com)
    return Model(inputs, outputs)

5.4 MBLLEN算法效果展示與分析

在這裏插入圖片描述
在這裏插入圖片描述
  上圖展示了MBLLEN算法的增強效果。第一張圖表示的是MBLLEN算法在一些公有數據集上面和其它算法的比較結果,通過觀察可以得知,該算法能夠獲得更加自然的增強效果,增強後的圖像的整體效果看起來比較舒服,增強之後的細節更加清晰。第二張圖表示的是該算法在幾張真實的文檔圖像上面的增強效果,增強之後的圖片中彷彿進入了陽光,整個圖片都變得亮堂了很多,但是在有些情況下,該算法輸出的結果有點過曝光的效果,幸運的是該算法還可以輸出一個較低亮度的結果,具體的結果留給你自己去做測試。

6、KinD算法

論文鏈接-Github鏈接

6.1 KinD算法簡介

  在暗光條件下拍攝的圖像經常(部分)能見度低。除了不滿意的燈光,多種類型的退化,如由於相機質量的限制而產生的噪聲和顏色失真,隱藏在黑暗中。換句話說,
僅僅提高暗區的亮度必然會放大隱藏的僞影。KinD算法是一個簡單高效的網絡,它是受到Retinex的啓發,將原始的圖像分解爲兩個部分。KinD將原始的圖像空間分解爲兩個比較相似的子空間,該算法使用在不同曝光程度的圖片塊來進行訓練,該算法對嚴重的視覺缺陷具有很強的魯棒性,並且用戶友好地任意調整光照水平。另外,我們的模型在2080ti GPU上處理一個VGA分辨率處理圖像的時間不到50ms

6.2 KinD網絡詳解

在這裏插入圖片描述
  上圖展示了KinD算法的整體網絡架構。整個網絡的架構和RetinexNet很相似,整個網絡包括Decomposition-Net、Adjustment-Net和Restoration-Net,Decomposition-Net子網絡的作用是對輸入的圖像進行分解,網絡結構方面和RetinexNet稍微有一些不同,但是主要的作用是相同的;Adjustment-Net用來調節光照,該子網絡由幾個卷積層組成,該網絡不執行去噪操作;Restoration-Net子網絡的作用是通過組合反射率圖像和光照圖像形成最終的增強圖像,該子網絡是一個帶有殘差連接的編解碼網絡,具體的細節如上圖所示。

6.3 KinD核心代碼實現

# 導入相應的Python包
import tensorflow as tf
import tensorflow.contrib.slim as slim
from tensorflow.contrib.layers.python.layers import initializers

def lrelu(x, trainbable=None):
    return tf.maximum(x*0.2,x)

def upsample_and_concat(x1, x2, output_channels, in_channels, scope_name, trainable=True):
    with tf.variable_scope(scope_name, reuse=tf.AUTO_REUSE) as scope:
        pool_size = 2
        deconv_filter = tf.get_variable('weights', [pool_size, pool_size, output_channels, in_channels], trainable= True)
        deconv = tf.nn.conv2d_transpose(x1, deconv_filter, tf.shape(x2) , strides=[1, pool_size, pool_size, 1], name=scope_name)

        deconv_output =  tf.concat([deconv, x2],3)
        deconv_output.set_shape([None, None, None, output_channels*2])

        return deconv_output

def DecomNet_simple(input):
	'''分解網絡實現函數'''
    with tf.variable_scope('DecomNet', reuse=tf.AUTO_REUSE):
        conv1=slim.conv2d(input,32,[3,3], rate=1, activation_fn=lrelu,scope='g_conv1_1')
        pool1=slim.max_pool2d(conv1, [2, 2], stride = 2, padding='SAME' )
        conv2=slim.conv2d(pool1,64,[3,3], rate=1, activation_fn=lrelu,scope='g_conv2_1')
        pool2=slim.max_pool2d(conv2, [2, 2], stride = 2, padding='SAME' )
        conv3=slim.conv2d(pool2,128,[3,3], rate=1, activation_fn=lrelu,scope='g_conv3_1')
        up8 =  upsample_and_concat( conv3, conv2, 64, 128 , 'g_up_1')
        conv8=slim.conv2d(up8,  64,[3,3], rate=1, activation_fn=lrelu,scope='g_conv8_1')
        up9 =  upsample_and_concat( conv8, conv1, 32, 64 , 'g_up_2')
        conv9=slim.conv2d(up9,  32,[3,3], rate=1, activation_fn=lrelu,scope='g_conv9_1')
        # Here, we use 1*1 kernel to replace the 3*3 ones in the paper to get better results.
        conv10=slim.conv2d(conv9,3,[1,1], rate=1, activation_fn=None, scope='g_conv10')
        R_out = tf.sigmoid(conv10)

        l_conv2=slim.conv2d(conv1,32,[3,3], rate=1, activation_fn=lrelu,scope='l_conv1_2')
        l_conv3=tf.concat([l_conv2, conv9],3)
        # Here, we use 1*1 kernel to replace the 3*3 ones in the paper to get better results.
        l_conv4=slim.conv2d(l_conv3,1,[1,1], rate=1, activation_fn=None,scope='l_conv1_4')
        L_out = tf.sigmoid(l_conv4)

    return R_out, L_out

def Restoration_net(input_r, input_i):
	'''重建網絡實現函數'''
    with tf.variable_scope('Restoration_net', reuse=tf.AUTO_REUSE):
        input_all = tf.concat([input_r,input_i], 3)
        
        conv1=slim.conv2d(input_all,32,[3,3], rate=1, activation_fn=lrelu,scope='de_conv1_1')
        conv1=slim.conv2d(conv1,32,[3,3], rate=1, activation_fn=lrelu,scope='de_conv1_2')
        pool1=slim.max_pool2d(conv1, [2, 2], padding='SAME' )

        conv2=slim.conv2d(pool1,64,[3,3], rate=1, activation_fn=lrelu,scope='de_conv2_1')
        conv2=slim.conv2d(conv2,64,[3,3], rate=1, activation_fn=lrelu,scope='de_conv2_2')
        pool2=slim.max_pool2d(conv2, [2, 2], padding='SAME' )

        conv3=slim.conv2d(pool2,128,[3,3], rate=1, activation_fn=lrelu,scope='de_conv3_1')
        conv3=slim.conv2d(conv3,128,[3,3], rate=1, activation_fn=lrelu,scope='de_conv3_2')
        pool3=slim.max_pool2d(conv3, [2, 2], padding='SAME' )

        conv4=slim.conv2d(pool3,256,[3,3], rate=1, activation_fn=lrelu,scope='de_conv4_1')
        conv4=slim.conv2d(conv4,256,[3,3], rate=1, activation_fn=lrelu,scope='de_conv4_2')
        pool4=slim.max_pool2d(conv4, [2, 2], padding='SAME' )

        conv5=slim.conv2d(pool4,512,[3,3], rate=1, activation_fn=lrelu,scope='de_conv5_1')
        conv5=slim.conv2d(conv5,512,[3,3], rate=1, activation_fn=lrelu,scope='de_conv5_2')

        up6 =  upsample_and_concat( conv5, conv4, 256, 512, 'up_6')

        conv6=slim.conv2d(up6,  256,[3,3], rate=1, activation_fn=lrelu,scope='de_conv6_1')
        conv6=slim.conv2d(conv6,256,[3,3], rate=1, activation_fn=lrelu,scope='de_conv6_2')

        up7 =  upsample_and_concat( conv6, conv3, 128, 256, 'up_7'  )
        conv7=slim.conv2d(up7,  128,[3,3], rate=1, activation_fn=lrelu,scope='de_conv7_1')
        conv7=slim.conv2d(conv7,128,[3,3], rate=1, activation_fn=lrelu,scope='de_conv7_2')

        up8 =  upsample_and_concat( conv7, conv2, 64, 128, 'up_8' )
        conv8=slim.conv2d(up8,  64,[3,3], rate=1, activation_fn=lrelu,scope='de_conv8_1')
        conv8=slim.conv2d(conv8,64,[3,3], rate=1, activation_fn=lrelu,scope='de_conv8_2')

        up9 =  upsample_and_concat( conv8, conv1, 32, 64, 'up_9' )
        conv9=slim.conv2d(up9,  32,[3,3], rate=1, activation_fn=lrelu,scope='de_conv9_1')
        conv9=slim.conv2d(conv9,32,[3,3], rate=1, activation_fn=lrelu,scope='de_conv9_2')

        conv10=slim.conv2d(conv9,3,[3,3], rate=1, activation_fn=None, scope='de_conv10')
    
        out = tf.sigmoid(conv10)
        return out

def Illumination_adjust_net(input_i, input_ratio):
	'''光照調節網絡實現函數'''
    with tf.variable_scope('Illumination_adjust_net', reuse=tf.AUTO_REUSE):
        input_all = tf.concat([input_i, input_ratio], 3)
        
        conv1=slim.conv2d(input_all,32,[3,3], rate=1, activation_fn=lrelu,scope='en_conv_1')
        conv2=slim.conv2d(conv1,32,[3,3], rate=1, activation_fn=lrelu,scope='en_conv_2')
        conv3=slim.conv2d(conv2,32,[3,3], rate=1, activation_fn=lrelu,scope='en_conv_3')
        conv4=slim.conv2d(conv3,1,[3,3], rate=1, activation_fn=lrelu,scope='en_conv_4')

        L_enhance = tf.sigmoid(conv4)
    return L_enhance

6.4 KinD算法效果展示與分析

在這裏插入圖片描述
在這裏插入圖片描述
  上圖展示了KinD算法的增強效果。第一張圖像表示的是該算法和其它經典的增強算法之間的比較結果,通過觀察我們可以發現,通過該算法增強後的圖像的更加明亮,看起來更加逼真,色彩基本上都正常的復原了。第二張圖像表示的是該算法在幾張真實的文檔圖片上面的增強效果,除了在第四列圖像上面有一點問題,在其它圖像上面都展現出來較好的增強效果。除此之外,需要提到的是該算法的運行速度並不快,2080Ti上面都需要50ms,都達不到實時,速度還待改進,作者出了一個改進版的KinD++,具體的細節的效果請看該鏈接

7、EnlightenGAN算法

論文鏈接-Github鏈接

7.1 EnlightenGAN算法簡介

  EnlightenGAN算法是一個高效的無監督生成對抗網絡,它不需要使用低光照/正常圖像塊訓練,大量實驗結果表明該算法可以在很多測試圖片中取得較好的泛化效果。該算法使用從輸入本身提取的信息來正則化非配對訓練,論文中提出了一個全局-局部鑑別器結構、一個自正則感知損失融合與注意力機制。總而言之,這個算法開啓了無監督圖像增強的先河,在訓練使用非配對的訓練塊,而且具有很好的泛化效果,它推動了GAN在圖像增強問題上面的應用

7.2 EnlightenGAN網絡簡介

在這裏插入圖片描述
  上圖展示了EnlightenGAN網絡的整體架構。整個網絡包括一個生成器和一個判別器,該生成器是一個帶有殘差連接的編解碼網絡,每一個卷積塊包含兩個3x3的卷積、一個BN和一個LeakyRelu,每一個注意力模塊是有特徵映射和注意力映射相乘獲得的;該網絡包含一個全局判別器和一個局部判別器,全局判別器的輸入是整張圖片,它用來判別輸入的圖片是沒有增強的還是增強過的,局部判別器的輸入是一些patch塊,用來判斷這些patch是增強之後還是沒有增強的。

7.3 EnlightenGAN核心代碼實現

def define_G(input_nc, output_nc, ngf, which_model_netG, norm='batch', use_dropout=False, gpu_ids=[], skip=False, opt=None):
	'''生成器網絡代碼實現'''
    netG = None
    use_gpu = len(gpu_ids) > 0
    norm_layer = get_norm_layer(norm_type=norm)

    if use_gpu:
        assert(torch.cuda.is_available())

    if which_model_netG == 'resnet_9blocks':
        netG = ResnetGenerator(input_nc, output_nc, ngf, norm_layer=norm_layer, use_dropout=use_dropout, n_blocks=9, gpu_ids=gpu_ids)
    elif which_model_netG == 'resnet_6blocks':
        netG = ResnetGenerator(input_nc, output_nc, ngf, norm_layer=norm_layer, use_dropout=use_dropout, n_blocks=6, gpu_ids=gpu_ids)
    elif which_model_netG == 'unet_128':
        netG = UnetGenerator(input_nc, output_nc, 7, ngf, norm_layer=norm_layer, use_dropout=use_dropout, gpu_ids=gpu_ids)
    elif which_model_netG == 'unet_256':
        netG = UnetGenerator(input_nc, output_nc, 8, ngf, norm_layer=norm_layer, use_dropout=use_dropout, gpu_ids=gpu_ids, skip=skip, opt=opt)
    elif which_model_netG == 'unet_512':
        netG = UnetGenerator(input_nc, output_nc, 9, ngf, norm_layer=norm_layer, use_dropout=use_dropout, gpu_ids=gpu_ids, skip=skip, opt=opt)
    elif which_model_netG == 'sid_unet':
        netG = Unet(opt, skip)
    elif which_model_netG == 'sid_unet_shuffle':
        netG = Unet_pixelshuffle(opt, skip)
    elif which_model_netG == 'sid_unet_resize':
        netG = Unet_resize_conv(opt, skip)
    elif which_model_netG == 'DnCNN':
        netG = DnCNN(opt, depth=17, n_channels=64, image_channels=1, use_bnorm=True, kernel_size=3)
    else:
        raise NotImplementedError('Generator model name [%s] is not recognized' % which_model_netG)
    if len(gpu_ids) >= 0:
        netG.cuda(device=gpu_ids[0])
        netG = torch.nn.DataParallel(netG, gpu_ids)
    netG.apply(weights_init)
    return netG


def define_D(input_nc, ndf, which_model_netD,
             n_layers_D=3, norm='batch', use_sigmoid=False, gpu_ids=[], patch=False):
    '''判別器網絡代碼實現'''
    netD = None
    use_gpu = len(gpu_ids) > 0
    norm_layer = get_norm_layer(norm_type=norm)

    if use_gpu:
        assert(torch.cuda.is_available())
    if which_model_netD == 'basic':
        netD = NLayerDiscriminator(input_nc, ndf, n_layers=3, norm_layer=norm_layer, use_sigmoid=use_sigmoid, gpu_ids=gpu_ids)
    elif which_model_netD == 'n_layers':
        netD = NLayerDiscriminator(input_nc, ndf, n_layers_D, norm_layer=norm_layer, use_sigmoid=use_sigmoid, gpu_ids=gpu_ids)
    elif which_model_netD == 'no_norm':
        netD = NoNormDiscriminator(input_nc, ndf, n_layers_D, use_sigmoid=use_sigmoid, gpu_ids=gpu_ids)
    elif which_model_netD == 'no_norm_4':
        netD = NoNormDiscriminator(input_nc, ndf, n_layers_D, use_sigmoid=use_sigmoid, gpu_ids=gpu_ids)
    elif which_model_netD == 'no_patchgan':
        netD = FCDiscriminator(input_nc, ndf, n_layers_D, use_sigmoid=use_sigmoid, gpu_ids=gpu_ids, patch=patch)
    else:
        raise NotImplementedError('Discriminator model name [%s] is not recognized' %
                                  which_model_netD)
    if use_gpu:
        netD.cuda(device=gpu_ids[0])
        netD = torch.nn.DataParallel(netD, gpu_ids)
    netD.apply(weights_init)
    return netD

7.4 EnlightenGAN算法效果展示與分析

在這裏插入圖片描述
在這裏插入圖片描述
  上圖展示了EnlightenGAN算法的增強效果。第一張證明了EnlightenGAN論文中提出的局部判別器和注意力模塊的有效性,同時也體現出了EnlightenGAN算法的增強效果。第二張展示了該算法在真實場景中的一些圖片上面的展示效果,整體看來該算法取得了較好的增強效果,但是在有些圖像上面增強的結果不是很均勻,仍然存在着一些陰影區域,不過越來越多的基於GAN的圖像增強算法會慢慢的解決這些問題,敬請期待。

8、總結

  本文主要對幾種經典的低光照圖像算法進行了簡單的介紹,但是並不代表當前只有這麼多算法,我介紹的可能只是冰山一角,如果你對低光照圖像增強感興趣,請參考參考文獻中的一些論文和資料。總而言之,低光照圖像增強算法在現實場景中會有很多的應用,比如低光照條件下面的檢測、跟蹤、行人重識別;通過圖像增強來提升文本檢測和文本識別的精度,更多的場景還需要你自己去挖掘和應用

參考資料

1、Learning-to-See-in-the-Dark-論文鏈接-Github鏈接-項目主頁
2、DPED-論文鏈接-Github鏈接-項目主頁
3、noteshrink-Github鏈接
4、SICE-論文鏈接Github鏈接
5、Attention-guided Low-light Image Enhancement-論文鏈接
6、部分低光照資料彙總-Github鏈接

注意事項

[1] 該博客是本人原創博客,如果您對該博客感興趣,想要轉載該博客,請與我聯繫(qq郵箱:[email protected]),我會在第一時間回覆大家,謝謝大家的關注.
[2] 由於個人能力有限,該博客可能存在很多的問題,希望大家能夠提出改進意見。
[3] 如果您在閱讀本博客時遇到不理解的地方,希望您可以聯繫我,我會及時的回覆您,和您交流想法和意見,謝謝。
[4] 本文測試的圖片可以通過該鏈接進行下載。網盤鏈接- 提取碼:x7ta。
[5] 本人業餘時間承接各種本科畢設設計和各種小項目,包括圖像處理(數據挖掘、機器學習、深度學習等)、matlab仿真、python算法及仿真等,有需要的請加QQ:1575262785詳聊,備註“項目”!!!

發佈了55 篇原創文章 · 獲贊 469 · 訪問量 30萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章