Восстановите изображения с экстремального слабого освещения Используя глубокое обучение

В этом примере показано, как восстановить украшенные изображения RGB с НЕОБРАБОТАННЫХ данных о камере, собранных при экстремальном слабом освещении с помощью U-Net.

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

Глубокое обучение предлагает решения, которые восстанавливают разумные изображения для Необработанных данных, собранных от цифровых однообъективных зеркальных фотоаппаратов и многих современных телефонных камер несмотря на слабое освещение и короткие времена воздействия. Эти решения используют в своих интересах полную информацию, существующую в Необработанных данных, чтобы превзойти по характеристикам проясняющиеся методы, выполняемые в постобработанных данных о RGB [1].

Изображение недостаточной освещенности (слева) и восстановленное изображение (справа)

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

Загрузка видит в темноте набор данных

Этот пример использует данные о камере Sony из набора данных Смотрите в темноте (SID) [1]. Набор данных SID обеспечивает указанные пары НЕОБРАБОТАННЫХ изображений той же сцены. В каждой паре одно изображение имеет короткую выдержку и недоэкспонируется, и другое изображение имеет более длительную выдержку и хорошо отсоединено. Размер данных о камере Sony из набора данных SID составляет 25 Гбайт.

Установите dataDir как желаемое местоположение набора данных.

dataDir = fullfile(tempdir,"SID");
if ~exist(dataDir,"dir")
    mkdir(dataDir);
end

Чтобы загрузить набор данных, перейдите к этой ссылке: https://storage.googleapis.com/isl-datasets/SID/Sony.zip. Извлеките данные в директорию, заданную dataDir переменная. Когда экстракция успешна, dataDir содержит директорию Sony с двумя подкаталогами: long и short. Файлы в long подкаталог имеет длинную выдержку и хорошо отсоединен. Файлы в short подкаталог имеет короткое воздействие и вполне недоэкспонируется и темный.

Набор данных также предоставляет текстовые файлы, которые описывают, как разделить файлы в обучение, валидацию и наборы тестовых данных. Переместите файлы Sony_train_list.txt, Sony_val_list.txt, и Sony_test_list.txt к директории, заданной dataDir переменная.

Создайте хранилища данных для обучения, валидации и тестирования

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

trainInfo = importSonyFileInfo(fullfile(dataDir,"Sony_train_list.txt"));
valInfo = importSonyFileInfo(fullfile(dataDir,"Sony_val_list.txt"));
testInfo = importSonyFileInfo(fullfile(dataDir,"Sony_test_list.txt"));

Объедините и предварительно обработайте СЫРЫЕ ДАННЫЕ и данные о RGB Используя хранилища данных

Создайте объединенные хранилища данных, которые читают и предварительно обрабатывают пары недоэкспонированных и хорошо отсоединили НЕОБРАБОТАННЫЕ изображения с помощью createCombinedDatastoreForLowLightRecovery функция помощника. Эта функция присоединена к примеру как вспомогательный файл.

createCombinedDatastoreForLowLightRecovery функция помощника выполняет эти операции:

  • Создайте imageDatastore это читает короткие изображения СЫРЫХ ДАННЫХ воздействия с помощью пользовательской функции чтения. Функция чтения читает НЕОБРАБОТАННОЕ изображение с помощью rawread функция, затем разделяет НЕОБРАБОТАННЫЙ шаблон Байера на отдельные каналы для каждого из этих четырех датчиков с помощью raw2planar функция. Нормируйте данные к области значений [0, 1] путем преобразования imageDatastore объект.

  • Создайте imageDatastore возразите, что СЫРЫЕ ДАННЫЕ длинной выдержки чтений отображают и преобразуют данные в изображение RGB за один шаг использование raw2rgb функция. Нормируйте данные к области значений [0, 1] путем преобразования imageDatastore объект.

  • Объедините imageDatastore объекты с помощью combine функция.

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

  • Сопоставьте изображения с метаданными, такие как выдержка, ISO и апертура.

dsTrainFull = createCombinedDatastoreForLowLightRecovery(dataDir,trainInfo);
dsValFull = createCombinedDatastoreForLowLightRecovery(dataDir,valInfo);
dsTestFull = createCombinedDatastoreForLowLightRecovery(dataDir,testInfo);

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

numVal = 30;
dsValFull = shuffle(dsValFull);
dsVal = subset(dsValFull,1:numVal);

Предварительно обработайте данные об обучении и валидации

Предварительно обработайте обучающий набор данных с помощью transform функционируйте и extractRandomPatch функция помощника. Функция помощника присоединена к примеру как к вспомогательному файлу. extractRandomPatch обрезки функции помощника несколько случайных закрашенных фигур размера 512 512 на 4 пикселя от плоского НЕОБРАБОТАННОГО изображения и соответствующих закрашенных фигур размера 1024 1 024 на 3 пикселя от изображения RGB. Содержимое сцены в соответствиях закрашенных фигур. Извлеките 12 закрашенных фигур на учебное изображение.

inputSize = [512,512,4];
patchesPerImage = 12;
dsTrain = transform(dsTrainFull,@(data) extractRandomPatch(data,inputSize,patchesPerImage));

Предварительно просмотрите исходное полноразмерное изображение и случайную учебную закрашенную фигуру.

previewFull = preview(dsTrainFull);
previewPatch = preview(dsTrain);
montage({previewFull{1,2},previewPatch{1,2}},BackgroundColor="w");

Предварительно обработайте набор данных валидации с помощью transform функционируйте и extractCenterPatch функция помощника. Функция помощника присоединена к примеру как к вспомогательному файлу. extractCenterPatch обрезки функции помощника одна закрашенная фигура размера 512 512 на 4 пикселя от центра плоского НЕОБРАБОТАННОГО изображения и соответствующих закрашенных фигур размера 1024 1 024 на 3 пикселя от изображения RGB. Содержимое сцены в соответствиях закрашенных фигур.

dsVal = transform(dsVal,@(data) extractCenterPatch(data,inputSize));

Набор данных тестирования не требует предварительной обработки. Тестовые изображения питаются в полном размере в сеть.

Увеличьте обучающие данные

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

dsTrain = transform(dsTrain,@(data) augmentPatchesForLowLightRecovery(data));

Проверьте, что операции предварительной обработки и увеличения работают как ожидалось путем предварительного просмотра одного канала от плоской НЕОБРАБОТАННОЙ закрашенной фигуры изображений, и соответствующий RGB декодировал закрашенную фигуру. Плоские Необработанные данные и целевые данные о RGB изображают закрашенные фигуры той же сцены, случайным образом извлеченной из изображения первоисточника. Значительный шум отображается в НЕОБРАБОТАННОЙ закрашенной фигуре из-за короткого времени захвата Необработанных данных, вызывая низкое отношение сигнал-шум.

imagePairs = read(dsTrain);
rawImage = imagePairs{1,1};
rgbPatch = imagePairs{1,2};
montage({rawImage(:,:,1),rgbPatch});

Сеть Define

Используйте сетевую архитектуру, похожую на U-Net. Пример создает подсети энкодера и декодера с помощью blockedNetwork функция. Эта функция создает подсети энкодера и декодера программно использование buildEncoderBlock и buildDecoderBlock помощник функционирует, соответственно. Функции помощника заданы в конце этого примера. Пример использует нормализацию экземпляра между сверткой и слоями активации во всех сетевых блоках кроме первого и последнего, и использует текучий слой ReLU в качестве слоя активации.

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

numModules = 4;
numChannelsEncoder = 2.^(5:8);
encoder = blockedNetwork(@(block) buildEncoderBlock(block,numChannelsEncoder), ...
    numModules,NamePrefix="encoder");

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

numChannelsDecoder = fliplr(numChannelsEncoder);
decoder = blockedNetwork(@(block) buildDecoderBlock(block,numChannelsDecoder), ...
    numModules,NamePrefix="decoder");

Задайте мостоукладчики, которые соединяют подсети энкодера и декодера.

bridgeLayers = [
    convolution2dLayer(3,512,Padding="same",PaddingValue="replicate")
    groupNormalizationLayer("channel-wise")
    leakyReluLayer(0.2)
    convolution2dLayer(3,512,Padding="same",PaddingValue="replicate")
    groupNormalizationLayer("channel-wise")
    leakyReluLayer(0.2)];

Задайте последние слои сети.

finalLayers = [
    convolution2dLayer(1,12)
    depthToSpace2dLayer(2)];

Объедините подсеть энкодера, мостоукладчики, подсеть декодера и последние слои с помощью encoderDecoderNetwork функция.

net = encoderDecoderNetwork(inputSize,encoder,decoder, ...
    LatentNetwork=bridgeLayers, ...
    SkipConnections="concatenate", ...
    FinalNetwork=finalLayers);
net = layerGraph(net);

Используйте среднюю нормализацию центрирования на входе как часть обучения.

net = replaceLayer(net,"encoderImageInputLayer",imageInputLayer(inputSize,Normalization="zerocenter"));

Задайте полную потерю с помощью пользовательского слоя ssimLossLayerGray. Это определение слоя присоединено к этому примеру как к вспомогательному файлу. ssimLossLayerGray слой использует потерю формы

lossOverall=α×lossSSIM+(1-α)×lossL1

Слой вычисляет многошкальное структурное подобие (SSIM) потеря для полутоновых представлений предсказанных и целевых изображений RGB с помощью multissim функция. Слой задает фактор взвешивания α как 7/8 и использование пять шкал.

finalLayerName = net.Layers(end).Name;
lossLayer = ssimLossLayerGray;
net = addLayers(net,lossLayer);
net = connectLayers(net,finalLayerName,lossLayer.Name);

Задайте опции обучения

Для обучения используйте решатель Адама с начальной скоростью обучения 1e-3. Обучайтесь в течение 30 эпох.

miniBatchSize = 12;
maxEpochs = 30;
options = trainingOptions("adam", ...
    Plots="training-progress", ...
    MiniBatchSize=miniBatchSize, ...
    InitialLearnRate=1e-3, ...
    MaxEpochs=maxEpochs, ...
    ValidationFrequency=400);

Обучите сеть или загрузите предварительно обученную сеть

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

Чтобы обучить сеть, установите doTraining переменная в следующем коде к true. Обучите модель с помощью trainNetwork (Deep Learning Toolbox) функция.

Обучайтесь на графическом процессоре, если вы доступны. Используя графический процессор требует Parallel Computing Toolbox™, и CUDA® включил NVIDIA® графический процессор. Для получения дополнительной информации смотрите Поддержку графического процессора Релизом (Parallel Computing Toolbox).

doTraining = false;

if doTraining  
    checkpointsDir = fullfile(dataDir,"checkpoints");
    if ~exist(checkpointsDir,"dir")
        mkdir(checkpointsDir);
    end
    options.CheckpointPath=checkpointsDir;

    netTrained = trainNetwork(dsTrain,net,options);
    modelDateTime = string(datetime("now",Format="yyyy-MM-dd-HH-mm-ss"));
    save(dataDir+"trainedLowLightCameraPipelineNet-"+modelDateTime+".mat",'netTrained');

else
    trainedNet_url = "https://ssd.mathworks.com/supportfiles/vision/data/trainedLowLightCameraPipelineNet.zip";
    trainedNet_filename = "trainedLowLightCameraPipelineNet.mat";
    downloadTrainedLowLightRecoveryNet(trainedNet_url,dataDir);
    load(fullfile(dataDir,trainedNet_filename));
end

Исследуйте результаты обучившего сеть

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

Считайте пару изображений и сопроводительных метаданных от набора тестов. Получите имена файлов коротких и изображений с большой выдержкой от метаданных.

[testPair,info] = read(dsTestFull);
testShortFilename = info.ShortExposureFilename;
testLongFilename = info.LongExposureFilename;

Преобразуйте исходное недоэкспонированное НЕОБРАБОТАННОЕ изображение в изображение RGB за один шаг использование raw2rgb функция. Отобразите результат, масштабируя область значений отображения к области значений пиксельных значений. Изображение выглядит почти абсолютно черным только с несколькими яркими пикселями.

testShortImage = raw2rgb(testShortFilename);
testShortTime = info.ShortExposureTime;
imshow(testShortImage,[])
title(["Short Exposure Test Image";"Exposure Time = "+num2str(testShortTime)]+" s")

Преобразуйте исходное хорошо отсоединенное НЕОБРАБОТАННОЕ изображение в изображение RGB за один шаг использование raw2rgb функция. Отобразите результат.

testLongImage = raw2rgb(testLongFilename);
testLongTime = info.LongExposureTime;
imshow(testLongImage)
title(["Long Exposure Target Image";"Exposure Time = "+num2str(testLongTime)]+" s")

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

outputFromNetwork = im2uint8(activations(netTrained,testPair{1},'FinalNetworkLayer2'));
imshow(outputFromNetwork)
title("Low-Light Recovery Network Prediction")

Вспомогательные Функции

extractRandomPatch обрезки функции помощника несколько случайных закрашенных фигур от плоского НЕОБРАБОТАННОГО изображения и соответствующих закрашенных фигур от изображения RGB. Закрашенная фигура Необработанных данных имеет размер m n 4, и закрашенная фигура RGB изображений имеет размер 2m 2n 3, где [m n] значение targetRAWSize входной параметр. Обе закрашенных фигуры имеют то же содержимое сцены.

function dataOut = extractRandomPatch(data,targetRAWSize,patchesPerImage)
    dataOut = cell(patchesPerImage,2);
    raw = data{1};
    rgb = data{2};
    for idx = 1:patchesPerImage
        windowRAW = randomCropWindow3d(size(raw),targetRAWSize);
        windowRGB = images.spatialref.Rectangle(2*windowRAW.XLimits+[-1,0],2*windowRAW.YLimits+[-1,0]);
        dataOut(idx,:) = {imcrop3(raw,windowRAW),imcrop(rgb,windowRGB)};
    end
end

extractCenterPatch обрезки функции помощника одна закрашенная фигура от центра плоского НЕОБРАБОТАННОГО изображения и соответствующая закрашенная фигура от изображения RGB. Закрашенная фигура Необработанных данных имеет размер m n 4, и закрашенная фигура RGB изображений имеет размер 2m 2n 3, где [m n] значение targetRAWSize входной параметр. Обе закрашенных фигуры имеют то же содержимое сцены.

function dataOut = extractCenterPatch(data,targetRAWSize)
    raw = data{1};
    rgb = data{2};
    windowRAW = centerCropWindow3d(size(raw),targetRAWSize);
    windowRGB = images.spatialref.Rectangle(2*windowRAW.XLimits+[-1,0],2*windowRAW.YLimits+[-1,0]);
    dataOut = {imcrop3(raw,windowRAW),imcrop(rgb,windowRGB)};
end

buildEncoderBlock функция помощника задает слои одного модуля энкодера в подсети энкодера.

function block = buildEncoderBlock(blockIdx,numChannelsEncoder)

    if blockIdx < 2
        instanceNorm = [];
    else
        instanceNorm = instanceNormalizationLayer;
    end
    
    filterSize = 3;
    numFilters = numChannelsEncoder(blockIdx);
    block = [
        convolution2dLayer(filterSize,numFilters,Padding="same",PaddingValue="replicate",WeightsInitializer="he")
        instanceNorm
        leakyReluLayer(0.2)
        convolution2dLayer(filterSize,numFilters,Padding="same",PaddingValue="replicate",WeightsInitializer="he")
        instanceNorm
        leakyReluLayer(0.2)
        maxPooling2dLayer(2,Stride=2,Padding="same")];
end

buildDecoderBlock функция помощника задает слои одного модуля энкодера в подсети декодера.

function block = buildDecoderBlock(blockIdx,numChannelsDecoder)

    if blockIdx < 4
        instanceNorm = instanceNormalizationLayer;
    else
        instanceNorm = [];
    end
    
    filterSize = 3;
    numFilters = numChannelsDecoder(blockIdx);
    block = [
        transposedConv2dLayer(filterSize,numFilters,Stride=2,WeightsInitializer="he",Cropping="same")
        convolution2dLayer(filterSize,numFilters,Padding="same",PaddingValue="replicate",WeightsInitializer="he")
        instanceNorm
        leakyReluLayer(0.2)
        convolution2dLayer(filterSize,numFilters,Padding="same",PaddingValue="replicate",WeightsInitializer="he")
        instanceNorm
        leakyReluLayer(0.2)];
end

Ссылки

[1] Чэнь, Чэнь, Цифэн Чэнь, Цзя Сюй и Владлен Кольтун. "Учась Видеть в темноте". Предварительно распечатайте, представленный 4 мая 2018. https://arxiv.org/abs/1805.01934.

Смотрите также

| (Deep Learning Toolbox) | (Deep Learning Toolbox) | |

Связанные примеры

Больше о