Анализ и реализация статьи по улучшению освещения при слабом освещении с использованием одного изображения с ярким / темным каналом в Python.

Подробная и улучшенная версия этой статьи размещена здесь: https://learnopencv.com/improving-illumination-in-night-time-images/

На изображениях, снятых при слабом освещении, отсутствуют цвета и четкие края. Из-за этого их очень сложно использовать для любых задач компьютерного зрения или даже для личного использования. Однако, используя методы обработки изображений, мы можем улучшить изображения, чтобы улучшить их. Метод был представлен Shi et al. был представлен в их статье «Улучшение изображения при низкой освещенности в ночное время с помощью одного изображения с использованием предварительного канала яркого / темного света», который мы поймем и реализуем в этой статье.

Оглавление

  • Обзор метода
  • Подробное объяснение с кодами
  • Полученные результаты
  • Ограничения

Обзор метода

Это взято прямо из статьи. Они кратко представляют шаги, которые будут подробно описаны в следующем разделе.

  1. Во-первых, мы используем максимальный фильтр и минимальный фильтр для получения светлого канала и темного канала входного изображения соответственно.
  2. Затем мы вычисляем глобальный атмосферный свет с помощью операции минимальной фильтрации на ярком канале.
  3. Затем мы используем яркий канал до получения начальной оценки передачи.
  4. Затем мы используем темный канал в качестве дополнительной информации для исправления потенциально ошибочных оценок передачи, полученных из предыдущего светлого канала.
  5. Скорректированная карта передачи уточняется с помощью управляемого фильтра, чтобы получить более гладкую структуру.
  6. Наконец, с помощью света и пропускания глобальной атмосферы мы можем получить изображение без темноты.

Подробное объяснение с кодами

Мы будем работать с изображением (540, 720, 3), представленным ниже.

Изображение с улучшенным освещением можно найти по формуле:

где J (x) - улучшенное изображение; I (x) - исходное изображение при слабом освещении, A - атмосферный свет, а t (x) - это исправленная карта передачи.

Получение приора светлого и темного канала

Яркий и темный предшествующие каналы представляют максимальную и минимальную интенсивность пикселей в локальном участке соответственно. Его можно представить как скользящее сверточное окно, определяющее максимальное или минимальное значение всех каналов.

где IC - цветовой канал I, а Ω (x) - локальный фрагмент с центром в x. y - пиксель на локальном пути Ω (x).

Нам нужно будет дополнить изображения половиной размера ядра, чтобы сохранить тот же размер, что и исходное изображение. Взятый размер ядра 9х9.

import cv2
import numpy as np
def get_illumination_channel(I, w):
    M, N, _ = I.shape
    padded = np.pad(I, ((int(w/2), int(w/2)), (int(w/2), int(w/2)), (0, 0)), 'edge')
    darkch = np.zeros((M, N))
    brightch = np.zeros((M, N))
    
    for i, j in np.ndindex(darkch.shape):
        darkch[i, j]  =  np.min(padded[i:i + w, j:j + w, :])
        brightch[i, j] = np.max(padded[i:i + w, j:j + w, :])
    
    return darkch, brightch

Вычисление освещения глобальной атмосферы

Значение глобального атмосферного освещения вычисляется с использованием канала яркости, полученного выше, путем взятия среднего значения из десяти процентов интенсивности. Десять процентов значений взяты для того, чтобы небольшая аномалия не повлияла на нее сильно.

def get_atmosphere(I, brightch, p=0.1):
    M, N = brightch.shape
    flatI = I.reshape(M*N, 3)
    flatbright = brightch.ravel()
    
    searchidx = (-flatbright).argsort()[:int(M*N*p)]  
    # find top M * N * p indexes. argsort() returns sorted (ascending) index.
    
    A = np.mean(flatI.take(searchidx, axis=0),dtype=np.float64, axis=0)

Поиск исходной карты передачи

Карта пропускания описывает часть света, которая не рассеивается и достигает камеры. В этом алгоритме он будет оцениваться по яркому каналу перед использованием следующего уравнения:

def get_initial_transmission(A, brightch):
    A_c = np.max(A)
    init_t = (brightch-A_c)/(1.-A_c)
    return (init_t - np.min(init_t))/(np.max(init_t) - np.min(init_t))

Использование темного канала для оценки скорректированной карты передачи и полученного улучшенного изображения

Карта пропускания также рассчитывается на основе предшествующего темного канала и вычисляется разница между априорными значениями. Если разница в любом месте превышает установленное значение (принятое = 0,4), тогда значение для этой карты передачи корректируется путем взятия произведения карт передачи. Это делается для исправления потенциально ошибочных оценок передачи, полученных из предшествующего яркого канала.

def get_corrected_transmission(I, A, darkch, brightch, init_t, alpha, omega, w):
    im3 = np.empty(I.shape, I.dtype);
for ind in range(0,3):
        im3[:,:,ind] = I[:,:,ind]/A[ind]
dark_c, _ = get_illumination_channel(im3, w)
    dark_t = 1 - omega*dark_c
    corrected_t = init_t
    diffch = brightch - darkch
    
    for i in range(diffch.shape[0]):
        for j in range(diffch.shape[1]):
            if(diffch[i,j]<alpha):
                corrected_t[i,j] = dark_t[i,j]*init_t[i,j]
                    
    return np.abs(corrected_t)

Сглаживание карты передачи с помощью управляемого фильтра

Согласно Mathworks управляемый фильтр можно определить как:

Управляемая фильтрация изображения является операцией соседства, как и другие операции фильтрации, но учитывает статистику области в соответствующей пространственной окрестности в управляющем изображении при вычислении значения выходного пикселя.

По сути, это сглаживающий фильтр, сохраняющий края. Он включен в дистрибутив OpenCV Contrib, откуда вы можете напрямую использовать его как функцию или использовать следующий код.

Расчет результирующего изображения

Нам потребовались карта пропускания и величина атмосферного освещения, чтобы получить улучшенное изображение.

def get_final_image(I, A, corrected_t, tmin):
    corrected_t_broadcasted = np.broadcast_to(corrected_t[:,:,None], (corrected_t.shape[0], corrected_t.shape[1], 3))
    J = (I-A)/(np.where(corrected_t_broadcasted < tmin, tmin, corrected_t_broadcasted)) + A
    
    return (J - np.min(J))/(np.max(J) - np.min(J)

Давайте посмотрим, какой результат мы получили бы как с применением управляемого фильтра, так и без него.

Дальнейшие улучшения

Алгоритм работы с бумагой закончился, и изображения полны цветов, но выглядят немного размытыми, и их можно немного повысить резкостью, чтобы они выглядели лучше. Мы можем использовать cv2.detailEnhance() для этой задачи, но это увеличит шум, поэтому мы можем использовать cv2.edgePreservingFilter() для его ограничения.

img = cv2.detailEnhance(img, sigma_s=10, sigma_r=0.15)
img = cv2.edgePreservingFilter(img, flags=1, sigma_s=64, sigma_r=0.2)

Полученные результаты

Результаты показаны для трех разных изображений, показывающих исходные изображения, результаты после управляемого фильтра и, наконец, результат после дальнейшего улучшения.

Ограничения

На изображениях не должно быть явных источников света, таких как лампа или даже луна, покрывающая достаточную площадь. Почему это проблема? Потому что тогда эти источники света повысят значение интенсивности атмосферы, поскольку мы искали верхние 10% процентов самых ярких пикселей, и это приведет к передержке этих областей.

В заключение, это отличный метод для увеличения освещенности изображений при слабом освещении с постоянным освещением, то есть без света.