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

В этом примере показано, как обучить и развернуть полностью сверточную сеть семантической сегментации на графическом процессоре NVIDIA ® с помощью GPU Coder™.

Семантическая сеть сегментации классифицирует каждый пиксель в изображении, получая к изображение, которое сегментировано по классам. Приложения для семантической сегментации включают сегментацию дорог для автономного управления автомобилем и сегментацию раковых камер для медицинского диагностирования. Дополнительные сведения см. в разделе Начало работы с семантической сегментацией с использованием глубокого обучения (Computer Vision Toolbox).

Чтобы проиллюстрировать процедуру обучения, этот пример обучает FCN-8s [1], один тип сверточной нейронной сети (CNN), предназначенной для семантической сегментации изображений. Другие типы сетей для семантической сегментации включают полностью сверточные сети, такие как SegNet и U-Net. Можно применить эту процедуру обучения и к этим сетям.

Этот пример использует набор данных CamVid [2] из Кембриджского университета для обучения. Этот набор данных представляет собой набор изображений, содержащих представления уличного уровня, полученные во время вождения. Набор данных обеспечивает пиксельные метки уровня для 32 семантических классов, включая автомобиль, пешехода и дорогу.

Необходимые условия для третьих лиц

Необходимый

  • Графический процессор NVIDIA и совместимый драйвер с поддержкой CUDA ®.

Дополнительный

Проверьте окружение GPU

Используйте coder.checkGpuInstall (GPU Coder) для проверки правильности настройки компиляторов и библиотек, необходимых для выполнения этого примера.

envCfg = coder.gpuEnvConfig('host');
envCfg.DeepLibTarget = 'cudnn';
envCfg.DeepCodegen = 1;
envCfg.Quiet = 1;
coder.checkGpuInstall(envCfg);

Setup

Этот пример создает полностью сверточную сеть семантической сегментации с весами, инициализированными из VGG-16 сети. Функция vgg16 проверяет существование пакета поддержки Deep Learning Toolbox для VGG-16 Network и возвращает предварительно обученную модель VGG-16.

vgg16();

Загрузите предварительно обученную версию FCN. Эта предварительно обученная модель позволяет вам запустить весь пример, не дожидаясь завершения обучения. Флаг doTraining контролирует, использует ли пример обученную сеть примера или предварительно обученную сеть FCN для генерации кода.

doTraining = false;
if ~doTraining
    pretrainedURL = 'https://www.mathworks.com/supportfiles/gpucoder/cnn_models/fcn/FCN8sCamVid.mat';
    disp('Downloading pretrained FCN (448 MB)...');
    websave('FCN8sCamVid.mat',pretrainedURL);
end
Downloading pretrained FCN (448 MB)...

Загрузить набор данных CamVid

Загрузите набор данных CamVid с этих URL-адресов.

imageURL = 'http://web4.cs.ucl.ac.uk/staff/g.brostow/MotionSegRecData/files/701_StillsRaw_full.zip';
labelURL = 'http://web4.cs.ucl.ac.uk/staff/g.brostow/MotionSegRecData/data/LabeledApproved_full.zip';

outputFolder = fullfile(pwd,'CamVid');

if ~exist(outputFolder, 'dir')
   
    mkdir(outputFolder)
    labelsZip = fullfile(outputFolder,'labels.zip');
    imagesZip = fullfile(outputFolder,'images.zip');   
    
    disp('Downloading 16 MB CamVid dataset labels...'); 
    websave(labelsZip, labelURL);
    unzip(labelsZip, fullfile(outputFolder,'labels'));
    
    disp('Downloading 557 MB CamVid dataset images...');  
    websave(imagesZip, imageURL);       
    unzip(imagesZip, fullfile(outputFolder,'images'));    
end

Время загрузки данных зависит от подключения к Интернету. Выполнение примера не продолжается до завершения операции загрузки. Кроме того, используйте веб-браузер, чтобы сначала загрузить набор данных на локальный диск. Затем используйте переменную outputFolder, чтобы указать местоположение загруженного файла.

Загрузка изображений CamVid

Использование imageDatastore для загрузки изображений CamVid. imageDatastore позволяет эффективно загружать на диск большой набор изображений.

imgDir = fullfile(outputFolder,'images','701_StillsRaw_full');
imds = imageDatastore(imgDir);

Отобразите одно из изображений.

I = readimage(imds,25);
I = histeq(I);
imshow(I)

Загрузка пиксельных маркированных изображений CamVid

Использование pixelLabelDatastore (Computer Vision Toolbox) для загрузки данных о пиксельных метках изображение. pixelLabelDatastore инкапсулирует данные о пиксельных метках и идентификатор метки в отображение имен классов.

Следуя методу обучения, описанному в документе SegNet [3], сгруппируйте 32 исходных класса в классах CamVid по 11. Задайте эти классы.

classes = [
    "Sky"
    "Building"
    "Pole"
    "Road"
    "Pavement"
    "Tree"
    "SignSymbol"
    "Fence"
    "Car"
    "Pedestrian"
    "Bicyclist"
    ];

Чтобы уменьшить 32 класса до 11 классов, несколько классов из исходного набора данных сгруппированы вместе. Например, «Car» является комбинацией «Car», «SUVPickupTruck», «Truck_Bus,» «Train» и «OtherMoving». Возвращает сгруппированные идентификаторы меток с помощью вспомогательной функции camvid Pixel Label ID s.

labelIDs = camvidPixelLabelIDs();

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

labelDir = fullfile(outputFolder,'labels');
pxds = pixelLabelDatastore(labelDir,classes,labelIDs);

Чтение и отображение одного из маркированных пикселями изображений путем наложения его поверх изображения.

C = readimage(pxds,25);
cmap = camvidColorMap;
B = labeloverlay(I,C,'ColorMap',cmap);
imshow(B)
pixelLabelColorbar(cmap,classes);

Области без наложения цветов не имеют пиксельных меток и не используются во время обучения.

Анализ статистики набора данных

Чтобы увидеть распределение меток классов в наборе данных CamVid, используйте countEachLabel (Computer Vision Toolbox). Эта функция отсчитывает количество пикселей по меткам классов.

tbl = countEachLabel(pxds)
tbl=11×3 table
         Name         PixelCount    ImagePixelCount
    ______________    __________    _______________

    {'Sky'       }    7.6801e+07      4.8315e+08   
    {'Building'  }    1.1737e+08      4.8315e+08   
    {'Pole'      }    4.7987e+06      4.8315e+08   
    {'Road'      }    1.4054e+08      4.8453e+08   
    {'Pavement'  }    3.3614e+07      4.7209e+08   
    {'Tree'      }    5.4259e+07       4.479e+08   
    {'SignSymbol'}    5.2242e+06      4.6863e+08   
    {'Fence'     }    6.9211e+06       2.516e+08   
    {'Car'       }    2.4437e+07      4.8315e+08   
    {'Pedestrian'}    3.4029e+06      4.4444e+08   
    {'Bicyclist' }    2.5912e+06      2.6196e+08   

Визуализируйте счетчики пикселей по классам.

frequency = tbl.PixelCount/sum(tbl.PixelCount);

bar(1:numel(classes),frequency)
xticks(1:numel(classes)) 
xticklabels(tbl.Name)
xtickangle(45)
ylabel('Frequency')

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

Изменение размера данных CamVid

Изображения в наборе данных CamVid 720 на 960. Чтобы уменьшить время обучения и использование памяти, измените размер изображений и пиксельных меток на 360 на 480 с помощью функций поддержки resizeCamVidImages и resizeCamVidPixelLabels.

imageFolder = fullfile(outputFolder,'imagesResized',filesep);
imds = resizeCamVidImages(imds,imageFolder);

labelFolder = fullfile(outputFolder,'labelsResized',filesep);
pxds = resizeCamVidPixelLabels(pxds,labelFolder);

Подготовка обучающих и тестовых наборов

SegNet обучается, используя 60% изображений из набора данных. Остальная часть изображений используется для проверки. Следующий код случайным образом разделяет изображение и данные о пиксельных метках на набор обучающих данных и тестовый набор.

[imdsTrain,imdsTest,pxdsTrain,pxdsTest] = partitionCamVidData(imds,pxds);

Соотношение 60/40 разделяет результаты на следующие количества обучающих и тестовых изображений:

numTrainingImages = numel(imdsTrain.Files)
numTrainingImages = 421
numTestingImages = numel(imdsTest.Files)
numTestingImages = 280

Создание сети

Использование fcnLayers (Computer Vision Toolbox), чтобы создать полностью сверточные сетевые слои, инициализированные при помощи VGG-16 весов. The fcnLayers функция выполняет преобразования сети для передачи весов от VGG-16 и добавляет дополнительные слои, необходимые для семантической сегментации. Выходные выходы fcnLayers функция является объектом LayerGraph, представляющим FCN. Объект LayerGraph инкапсулирует слои сети и соединения между слоями.

imageSize = [360 480];
numClasses = numel(classes);
lgraph = fcnLayers(imageSize,numClasses);

Размер изображения выбирается в зависимости от размера изображений в наборе данных. Количество классов выбирается на основе классов в CamVid.

Балансировка классов при помощи взвешивания классов

Классы в CamVid не сбалансированы. Чтобы улучшить обучение, можно использовать счетчики пиксельных меток, вычисленные ранее countEachLabel (Computer Vision Toolbox) и вычислите средние веса классов частот [3].

imageFreq = tbl.PixelCount ./ tbl.ImagePixelCount;
classWeights = median(imageFreq) ./ imageFreq;

Задайте веса классов при помощи pixelClassificationLayer (Computer Vision Toolbox).

pxLayer = pixelClassificationLayer('Name','labels','Classes',tbl.Name,'ClassWeights',classWeights)
pxLayer = 
  PixelClassificationLayer with properties:

            Name: 'labels'
         Classes: [11×1 categorical]
    ClassWeights: [11×1 double]
      OutputSize: 'auto'

   Hyperparameters
    LossFunction: 'crossentropyex'

Обновите сеть SegNet, которая имеет новый pixelClassificationLayer, удалив текущий pixelClassificationLayer и добавив новый слой. Текущий pixelClassificationLayer имеет имя 'pixelLabels'. Удалите его при помощи removeLayers function, добавить новый при помощи addLayers function, и соединить новый слой с остальной частью сети с помощью connectLayers функция.

lgraph = removeLayers(lgraph,'pixelLabels');
lgraph = addLayers(lgraph, pxLayer);
lgraph = connectLayers(lgraph,'softmax','labels');

Выбор опций обучения

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

options = trainingOptions('adam', ...
    'InitialLearnRate',1e-3, ...
    'MaxEpochs',100, ...  
    'MiniBatchSize',4, ...
    'Shuffle','every-epoch', ...
    'CheckpointPath', tempdir, ...
    'VerboseFrequency',2);

'MiniBatchSize' из четырех сокращает использование памяти во время обучения. Вы можете увеличить или уменьшить это значение в зависимости от объема памяти графический процессор в вашей системе.

Значение 'CheckpointPath' установлено во временное место. Эта пара "имя-значение" включает сохранение сетевых контрольных точек в конце каждой эпохи обучения. Если обучение прервано из-за отказа системы или отключения степени, можно возобновить обучение с сохраненной контрольной точки. Убедитесь, что в расположении, указанном в CheckpointPath, достаточно пространства для хранения сетевых контрольных точек.

Увеличение количества данных

Увеличение количества данных предоставляет сети больше примеров, потому что это помогает улучшить точность сети. Здесь для увеличения данных используются случайное отражение «слева/справа» и случайный преобразование X/Y на +/-10 пикселей. Используйте imageDataAugmenter функция, чтобы задать эти параметры увеличения данных.

augmenter = imageDataAugmenter('RandXReflection',true,...
    'RandXTranslation',[-10 10],'RandYTranslation',[-10 10]);

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

Начало обучения

Объедините обучающие данные и выборки для увеличения данных при помощи pixelLabelImageDatastore (Computer Vision Toolbox) функция. The pixelLabelImageDatastore функция считывает пакеты обучающих данных, применяет увеличение данных и отправляет дополненные данные в алгоритм настройки.

pximds = pixelLabelImageDatastore(imdsTrain,pxdsTrain, ...
    'DataAugmentation',augmenter);

Если на doTraining флаг true, запустите обучение с помощью trainNetwork функция.

Обучение было проверено на NVIDIA™ Titan Xp с 12 ГБ памяти графический процессор. Если у вашего графического процессора меньше памяти, память может иссякнуть. Если в вашей системе недостаточно памяти, попробуйте опустить MiniBatchSize свойство в trainingOptions по 1. Обучение этой сети занимает около 5 часов или больше в зависимости от вашего графического процессора оборудования.

if doTraining    
    [net, info] = trainNetwork(pximds,lgraph,options);
    save('FCN8sCamVid.mat','net');
end

Сохраните сетевой объект DAG как MAT-файл с именем FCN8sCamVid.mat. Этот MAT-файл используется во время генерации кода.

Выполните генерацию кода MEX

Функция fcn_predict.m принимает вход изображения и выполняет предсказание изображения с помощью нейронной сети для глубокого обучения, сохраненной в FCN8sCamVid.mat файл. Функция загружает сетевой объект из FCN8sCamVid.mat в постоянную переменную mynet и повторно использует постоянный объект при последующих вызовах предсказания.

type('fcn_predict.m')
function out = fcn_predict(in)
%#codegen
% Copyright 2018-2019 The MathWorks, Inc.

persistent mynet;

if isempty(mynet)
    mynet = coder.loadDeepLearningNetwork('FCN8sCamVid.mat');
end

% pass in input
out = predict(mynet,in);

Сгенерируйте объект строения GPU для целевого языка MEX на C++. Используйте coder.DeepLearningConfig (GPU Coder) функция для создания uDNN c объект строения глубокого обучения и присвоение его DeepLearningConfig свойство объекта строения кода GPU. Запуск codegen (MATLAB Coder), задающая размер входного сигнала [360, 480, 3]. Этот размер соответствует входу слою FCN.

cfg = coder.gpuConfig('mex');
cfg.TargetLang = 'C++';
cfg.DeepLearningConfig = coder.DeepLearningConfig('cudnn');
codegen -config cfg fcn_predict -args {ones(360,480,3,'uint8')} -report
Code generation successful: View report

Запуск сгенерированного MEX

Загрузка и отображение входа изображения.

im = imread('testImage.png');
imshow(im);

Запуск предсказания по вызову fcn_predict_mex на вход изображении.

predict_scores = fcn_predict_mex(im);

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

[~,argmax] = max(predict_scores,[],3);

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

classes = [
    "Sky"
    "Building"
    "Pole"
    "Road"
    "Pavement"
    "Tree"
    "SignSymbol"
    "Fence"
    "Car"
    "Pedestrian"
    "Bicyclist"
    ];

cmap = camvidColorMap();
SegmentedImage = labeloverlay(im,argmax,'ColorMap',cmap);
figure
imshow(SegmentedImage);
pixelLabelColorbar(cmap,classes);

Очистка

Очистить загруженный в память статический сетевой объект.

clear mex;

Ссылки

[1] Лонг, Дж., Э. Шелхамер и Т. Даррелл. «Полностью сверточные сети для семантической сегментации». Материалы Конференции IEEE по компьютерному зрению и распознаванию шаблонов, 2015, стр. 3431-3440.

[2] Brostow, G. J., J. Fauqueur, and R. Cipolla. Semantic object classes in video: A high-definition основная истина database (неопр.) (недоступная ссылка). Распознавание Букв. Том 30, Выпуск 2, 2009, стр. 88-97.

[3] Badrinarayanan, V., A. Kendall, and R. Cipolla. «SegNet: Архитектура глубокого сверточного энкодера-декодера для сегментации изображений». arXiv preprint arXiv:1511.00561, 2015.