В этом примере показано, как применить стилистический внешний вид одного изображения к содержимому сцены второго изображения с помощью предварительно обученной VGG-19 сети [1].
Загрузите изображение стиля и изображение содержимого. Этот пример использует отличительную картину Ван Гога «Звездная ночь» в качестве изображения стиля и фотографию маяка в качестве изображения содержимого.
styleImage = im2double(imread('starryNight.jpg')); contentImage = imread('lighthouse.png');
Отобразите изображение стиля и изображение содержимого как монтаж.
imshow(imtile({styleImage,contentImage},'BackgroundColor','w'));
В этом примере вы используете измененную предварительно обученную VGG-19 глубокую нейронную сеть, чтобы извлечь функции изображения содержимого и стиля в различных слоях. Эти многослойные функции используются для вычисления соответствующего содержимого и потерь стиля. Сеть генерирует стилизованное передаточное изображение с помощью комбинированных потерь.
Чтобы получить предварительно обученную VGG-19 сеть, установите vgg19
. Если у вас нет установленных необходимых пакетов поддержки, то программное обеспечение предоставляет ссылку на загрузку.
net = vgg19;
Чтобы сделать VGG-19 сеть подходящей для редукции данных, удалите все полносвязные слои из сети.
lastFeatureLayerIdx = 38; layers = net.Layers; layers = layers(1:lastFeatureLayerIdx);
Максимальные слои объединения VGG-19 сети вызывают эффект затухания. Чтобы уменьшить эффект затухания и увеличить градиентный поток, замените все максимальные слои объединения средними слоями объединения [1].
for l = 1:lastFeatureLayerIdx layer = layers(l); if isa(layer,'nnet.cnn.layer.MaxPooling2DLayer') layers(l) = averagePooling2dLayer(layer.PoolSize,'Stride',layer.Stride,'Name',layer.Name); end end
Создайте график слоев с измененными слоями.
lgraph = layerGraph(layers);
Визуализация сети редукции данных на графике.
plot(lgraph)
title('Feature Extraction Network')
Чтобы обучить сеть с помощью пользовательского цикла обучения и включить автоматическую дифференциацию, преобразуйте график слоев в dlnetwork
объект.
dlnet = dlnetwork(lgraph);
Измените размер изображения стиля и изображения содержимого на меньший размер для более быстрой обработки.
imageSize = [384,512]; styleImg = imresize(styleImage,imageSize); contentImg = imresize(contentImage,imageSize);
Предварительно обученная VGG-19 сеть выполняет классификацию на вычитаемом по каналу изображении. Получите среднее значение по каналу из входного слоя изображения, который является первым слоем в сети.
imgInputLayer = lgraph.Layers(1); meanVggNet = imgInputLayer.Mean(1,1,:);
Значения среднего по каналу подходят для изображений типа данных с плавающей точкой со значениями пикселей в области значений [0, 255]. Преобразуйте изображение стиля и изображение содержимого в тип данных single
с областью значений [0, 255]. Затем вычесть среднее значение канала из изображения стиля и изображения содержимого.
styleImg = rescale(single(styleImg),0,255) - meanVggNet; contentImg = rescale(single(contentImg),0,255) - meanVggNet;
Передаточное изображение является выходным изображением в результате передачи стиля. Можно инициализировать изображение переноса с помощью изображения стиля, изображения содержимого или любого случайного изображения. Инициализация с изображением стиля или изображением содержимого смещает процесс передачи стиля и создает передаточное изображение, более похожее на входное изображение. Напротив, инициализация с белым шумом удаляет смещение, но занимает больше времени, чтобы сходиться на стилизованном изображении. Для лучшей стилизации и более быстрой сходимости этот пример инициализирует выход передаточное изображение как взвешенную комбинацию содержимого изображения и белого шумового изображения.
noiseRatio = 0.7; randImage = randi([-20,20],[imageSize 3]); transferImage = noiseRatio.*randImage + (1-noiseRatio).*contentImg;
Цель потери содержимого состоит в том, чтобы признаки передаточного изображения совпадали с особенностями содержимого изображения. Потеря содержимого вычисляется как среднее квадратное различие между функциями изображения содержимого и функциями передающего изображения для каждого слоя функции содержимого [1]. - предсказанная карта функций для передаточного изображения и - предсказанная карта функций для изображения содержимого. - вес слоя содержимого для слой. - высота, ширина и каналы карт функций, соответственно.
Задайте имена слоев редукции данных содержимого. Извлеченные из этих слоев функции используются для вычисления потерь содержимого. В VGG-19 сети обучение более эффективно с использованием функций из более глубоких слоев, а не функций из мелких слоев. Поэтому задайте слой редукции данных содержимого как четвертый сверточный слой.
styleTransferOptions.contentFeatureLayerNames = {'conv4_2'};
Задайте веса слоев редукции данных содержимого.
styleTransferOptions.contentFeatureLayerWeights = 1;
Цель потери стиля состоит в том, чтобы текстура передаточного изображения совпадала с текстурой стилевого изображения. Представление стиля изображения представлено в виде матрицы Gram. Поэтому потеря стиля вычисляется как среднее квадратное различие между матрицей Gram изображения стиля и матрицей Gram передаточного изображения [1]. и являются предсказанными картами функций для стиля и передаточного изображения, соответственно. и являются матрицами граммов для функций стиля и передаточных функций, соответственно. - вес слоя стиля для слой стиля.
Задайте имена слоев редукции данных стиля. Извлеченные из этих слоев функции используются для вычисления потерь стиля.
styleTransferOptions.styleFeatureLayerNames = {'conv1_1','conv2_1','conv3_1','conv4_1','conv5_1'};
Задайте веса слоев редукции данных стиля. Задайте малые веса для изображений простого стиля и увеличьте веса для изображений сложного стиля.
styleTransferOptions.styleFeatureLayerWeights = [0.5,1.0,1.5,3.0,4.0];
Общая потеря - это взвешенная комбинация потерь содержимого и потерь стиля. и являются весовыми коэффициентами для потери содержимого и потери стиля, соответственно.
Задайте весовые коэффициенты alpha
и beta
для потерь содержимого и стилей. Отношение alpha
на beta
должно быть около 1e-3 или 1e-4 [1].
styleTransferOptions.alpha = 1; styleTransferOptions.beta = 1e3;
Обучите для 2500 итераций.
numIterations = 2500;
Задайте опции для оптимизации Adam. Установите скорость обучения равной 2 для более быстрого сходимости. Можно экспериментировать со скоростью обучения, наблюдая за выходным изображением и потерями. Инициализируйте конечный средний градиент и конечный средний градиент-квадратные скорости распада с []
.
learningRate = 2; trailingAvg = []; trailingAvgSq = [];
Преобразуйте изображение стиля, изображение содержимого и передайте изображение в dlarray
объекты с базовым типом single
и размерность метки 'SSC'
.
dlStyle = dlarray(styleImg,'SSC'); dlContent = dlarray(contentImg,'SSC'); dlTransfer = dlarray(transferImage,'SSC');
Обучите на графическом процессоре, если он доступен. Для использования GPU требуется Parallel Computing Toolbox™ и графический процессор с поддержкой CUDA ® NVIDIA ®. Для получения дополнительной информации смотрите Поддержку GPU by Release (Parallel Computing Toolbox). Для обучения графический процессор преобразуйте данные в gpuArray
.
if canUseGPU dlContent = gpuArray(dlContent); dlStyle = gpuArray(dlStyle); dlTransfer = gpuArray(dlTransfer); end
Извлеките функции содержимого из изображения содержимого.
numContentFeatureLayers = numel(styleTransferOptions.contentFeatureLayerNames);
contentFeatures = cell(1,numContentFeatureLayers);
[contentFeatures{:}] = forward(dlnet,dlContent,'Outputs',styleTransferOptions.contentFeatureLayerNames);
Извлечение функций стиля из изображения стиля.
numStyleFeatureLayers = numel(styleTransferOptions.styleFeatureLayerNames);
styleFeatures = cell(1,numStyleFeatureLayers);
[styleFeatures{:}] = forward(dlnet,dlStyle,'Outputs',styleTransferOptions.styleFeatureLayerNames);
Обучите модель с помощью пользовательского цикла обучения. Для каждой итерации:
Вычислите потери и потери стиля содержимого с помощью функций изображения содержимого, изображения стиля и изображения переноса. Чтобы вычислить потери и градиенты, используйте функцию helper imageGradients
(определено в разделе Вспомогательные функции этого примера).
Обновите передаточное изображение с помощью adamupdate
функция.
Выберите лучшее изображение переноса стиля в качестве конечного выходного изображения.
figure minimumLoss = inf; for iteration = 1:numIterations % Evaluate the transfer image gradients and state using dlfeval and the % imageGradients function listed at the end of the example. [grad,losses] = dlfeval(@imageGradients,dlnet,dlTransfer,contentFeatures,styleFeatures,styleTransferOptions); [dlTransfer,trailingAvg,trailingAvgSq] = adamupdate(dlTransfer,grad,trailingAvg,trailingAvgSq,iteration,learningRate); if losses.totalLoss < minimumLoss minimumLoss = losses.totalLoss; dlOutput = dlTransfer; end % Display the transfer image on the first iteration and after every 50 % iterations. The postprocessing steps are described in the "Postprocess % Transfer Image for Display" section of this example. if mod(iteration,50) == 0 || (iteration == 1) transferImage = gather(extractdata(dlTransfer)); transferImage = transferImage + meanVggNet; transferImage = uint8(transferImage); transferImage = imresize(transferImage,size(contentImage,[1 2])); image(transferImage) title(['Transfer Image After Iteration ',num2str(iteration)]) axis off image drawnow end end
Получите обновленное изображение передачи.
transferImage = gather(extractdata(dlOutput));
Добавьте обученное сетью среднее значение к изображению передачи.
transferImage = transferImage + meanVggNet;
Некоторые значения пикселей могут превысить исходную область значений [0, 255] изображения содержимого и стиля. Можно отсечь значения в область значений [0, 255], преобразовав тип данных в uint8
.
transferImage = uint8(transferImage);
Измените размер передаточного изображения на исходный размер содержимого изображения.
transferImage = imresize(transferImage,size(contentImage,[1 2]));
Отобразите изображение содержимого, передайте изображение и стиль изображения в монтаже.
imshow(imtile({contentImage,transferImage,styleImage}, ... 'GridSize',[1 3],'BackgroundColor','w'));
The imageGradients
Функция helper возвращает потери и градиенты, используя функции изображения содержимого, изображения стиля и передаточного изображения.
function [gradients,losses] = imageGradients(dlnet,dlTransfer,contentFeatures,styleFeatures,params) % Initialize transfer image feature containers. numContentFeatureLayers = numel(params.contentFeatureLayerNames); numStyleFeatureLayers = numel(params.styleFeatureLayerNames); transferContentFeatures = cell(1,numContentFeatureLayers); transferStyleFeatures = cell(1,numStyleFeatureLayers); % Extract content features of transfer image. [transferContentFeatures{:}] = forward(dlnet,dlTransfer,'Outputs',params.contentFeatureLayerNames); % Extract style features of transfer image. [transferStyleFeatures{:}] = forward(dlnet,dlTransfer,'Outputs',params.styleFeatureLayerNames); % Compute content loss. cLoss = contentLoss(transferContentFeatures,contentFeatures,params.contentFeatureLayerWeights); % Compute style loss. sLoss = styleLoss(transferStyleFeatures,styleFeatures,params.styleFeatureLayerWeights); % Compute final loss as weighted combination of content and style loss. loss = (params.alpha * cLoss) + (params.beta * sLoss); % Calculate gradient with respect to transfer image. gradients = dlgradient(loss,dlTransfer); % Extract various losses. losses.totalLoss = gather(extractdata(loss)); losses.contentLoss = gather(extractdata(cLoss)); losses.styleLoss = gather(extractdata(sLoss)); end
The contentLoss
Функция helper вычисляет средневзвешенное квадратное различие между признаками изображения содержимого и функциями передаточного изображения.
function loss = contentLoss(transferContentFeatures,contentFeatures,contentWeights) loss = 0; for i=1:numel(contentFeatures) temp = 0.5 .* mean((transferContentFeatures{1,i} - contentFeatures{1,i}).^2,'all'); loss = loss + (contentWeights(i)*temp); end end
The styleLoss
Функция helper вычисляет средневзвешенное квадратное различие между матрицей Gram функций изображения стиля и матрицей Gram функций изображения переноса.
function loss = styleLoss(transferStyleFeatures,styleFeatures,styleWeights) loss = 0; for i=1:numel(styleFeatures) tsf = transferStyleFeatures{1,i}; sf = styleFeatures{1,i}; [h,w,c] = size(sf); gramStyle = computeGramMatrix(sf); gramTransfer = computeGramMatrix(tsf); sLoss = mean((gramTransfer - gramStyle).^2,'all') / ((h*w*c)^2); loss = loss + (styleWeights(i)*sLoss); end end
The computeGramMatrix
helper function используется в styleLoss
вспомогательная функция для вычисления матрицы Gram карты признаков.
function gramMatrix = computeGramMatrix(featureMap) [H,W,C] = size(featureMap); reshapedFeatures = reshape(featureMap,H*W,C); gramMatrix = reshapedFeatures' * reshapedFeatures; end
[1] Леон А. Гатис, Александр С. Эккер и Маттиас Бетж. Нейронный алгоритм художественного стиля. Препринт, представленный 2 сентября 2015 года. https://arxiv.org/abs/1508.06576
dlarray
| trainingOptions
| trainNetwork
| vgg19