Этот пример демонстрирует подход, описанный в [1], в котором данные облака точек предварительно обрабатываются в вокселизованное кодирование, а затем используются непосредственно с простой 3-D сверточной нейронной сетевой архитектурой для выполнения классификации объектов. В более поздних подходах, таких как [2], кодировки данных облака точек могут быть более сложными и могут быть изучены кодировки, которые проходят сквозную подготовку вместе с сетью, выполняющей задачу классификации/обнаружения объектов/сегментации. Однако общая схема перемещения от нерегулярных неупорядоченных точек к сетчатой структуре, которую можно подавать в конветы, остается аналогичной во всех этих оценках.
В этом примере мы работаем с набором данных городских объектов Сиднея. В этом примере мы используем складки 1-3 из данных в качестве обучающего набора и складку 4 в качестве проверочного набора.
dataPath = downloadSydneyUrbanObjects(tempdir); dsTrain = loadSydneyUrbanObjectsData(dataPath,[1 2 3]); dsVal = loadSydneyUrbanObjectsData(dataPath,4);
Проанализируйте обучающий набор, чтобы понять метки, присутствующие в данных, и общее распределение меток.
dsLabels = transform(dsTrain,@(data) data{2});
labels = readall(dsLabels);
figure
histogram(labels)
Из гистограммы очевидно, что существует проблема дисбаланса классов в данных обучения, в которых определенные классы объектов подобны Car и Pedestrian встречаются гораздо чаще, чем менее частые классы, такие как Ute.
Чтобы избежать переоборудования и добавить надежность классификатору, некоторое количество рандомизированного увеличения данных, как правило, является хорошей идеей при обучении сети. Функции randomAffine2d и pctransform упрощают определение рандомизированных аффинных преобразований в данных облака точек. Мы дополнительно добавляем случайное дрожание для каждой точки в каждом облаке точек. Функция augmentPointCloudData включен в раздел вспомогательных функций ниже.
dsTrain = transform(dsTrain,@augmentPointCloudData);
Убедитесь, что увеличение объема данных облака точек является разумным.
dataOut = preview(dsTrain);
figure
pcshow(dataOut{1});
title(dataOut{2});
Затем мы добавляем простое преобразование вокселизации к каждому облаку входных точек, как обсуждалось в предыдущем примере, чтобы преобразовать наше облако входных точек в псевдоизображение, которое можно использовать со сверточной нейронной сетью. Используйте простую сетку занятости.
dsTrain = transform(dsTrain,@formOccupancyGrid); dsVal = transform(dsVal,@formOccupancyGrid);
Изучите образец конечного вокселизированного объема, который мы будем подавать в сеть, чтобы убедиться, что вокселикширование работает правильно.
data = preview(dsTrain);
figure
p = patch(isosurface(data{1},0.5));
p.FaceColor = 'red';
p.EdgeColor = 'none';
daspect([1 1 1])
view(45,45)
camlight;
lighting phong
title(data{2});
В этом примере мы используем простую архитектуру классификации 3-D, как описано в [1].
layers = [image3dInputLayer([32 32 32],'Name','inputLayer','Normalization','none'),... convolution3dLayer(5,32,'Stride',2,'Name','Conv1'),... leakyReluLayer(0.1,'Name','leakyRelu1'),... convolution3dLayer(3,32,'Stride',1,'Name','Conv2'),... leakyReluLayer(0.1,'Name','leakyRulu2'),... maxPooling3dLayer(2,'Stride',2,'Name','maxPool'),... fullyConnectedLayer(128,'Name','fc1'),... reluLayer('Name','relu'),... dropoutLayer(0.5,'Name','dropout1'),... fullyConnectedLayer(14,'Name','fc2'),... softmaxLayer('Name','softmax'),... classificationLayer('Name','crossEntropyLoss')]; voxnet = layerGraph(layers); figure plot(voxnet);

Используйте стохастический градиентный спуск с импульсом с кусочной корректировкой к графику обучения. Этот пример был выполнен на GPU TitanX, для GPU с меньшим объемом памяти может потребоваться уменьшить размер партии. Хотя 3D convnets имеют преимущество концептуальной простоты, они имеют недостаток больших объемов использования памяти во время обучения.
miniBatchSize = 32; dsLength = length(dsTrain.UnderlyingDatastore.Files); iterationsPerEpoch = floor(dsLength/miniBatchSize); dropPeriod = floor(8000/iterationsPerEpoch); options = trainingOptions('sgdm','InitialLearnRate',0.01,'MiniBatchSize',miniBatchSize,... 'LearnRateSchedule','Piecewise',... 'LearnRateDropPeriod',dropPeriod,... 'ValidationData',dsVal,'MaxEpochs',60,... 'DispatchInBackground',false,... 'Shuffle','never');
voxnet = trainNetwork(dsTrain,voxnet,options);
Training on single GPU. |======================================================================================================================| | Epoch | Iteration | Time Elapsed | Mini-batch | Validation | Mini-batch | Validation | Base Learning | | | | (hh:mm:ss) | Accuracy | Accuracy | Loss | Loss | Rate | |======================================================================================================================| | 1 | 1 | 00:00:03 | 9.38% | 20.65% | 2.6408 | 2.6300 | 0.0100 | | 4 | 50 | 00:00:25 | 31.25% | 29.03% | 2.2892 | 2.2954 | 0.0100 | | 8 | 100 | 00:00:45 | 37.50% | 37.42% | 1.9256 | 2.0372 | 0.0100 | | 12 | 150 | 00:01:05 | 53.12% | 47.10% | 1.6398 | 1.7396 | 0.0100 | | 16 | 200 | 00:01:24 | 43.75% | 55.48% | 1.9551 | 1.5172 | 0.0100 | | 20 | 250 | 00:01:44 | 40.62% | 61.29% | 1.7413 | 1.3598 | 0.0100 | | 24 | 300 | 00:02:04 | 50.00% | 60.00% | 1.4652 | 1.2962 | 0.0100 | | 27 | 350 | 00:02:23 | 43.75% | 64.52% | 1.5017 | 1.1762 | 0.0100 | | 31 | 400 | 00:02:42 | 53.12% | 69.03% | 1.2488 | 1.1132 | 0.0100 | | 35 | 450 | 00:03:02 | 50.00% | 69.03% | 1.3160 | 1.0272 | 0.0100 | | 39 | 500 | 00:03:23 | 59.38% | 69.03% | 1.1753 | 1.1366 | 0.0100 | | 43 | 550 | 00:03:44 | 56.25% | 65.81% | 1.1546 | 1.1086 | 0.0100 | | 47 | 600 | 00:04:03 | 68.75% | 65.81% | 0.9808 | 1.0251 | 0.0100 | | 50 | 650 | 00:04:22 | 65.62% | 69.68% | 1.1245 | 1.0136 | 0.0100 | | 54 | 700 | 00:04:42 | 62.50% | 65.16% | 1.2860 | 1.0934 | 0.0100 | | 58 | 750 | 00:05:01 | 59.38% | 68.39% | 1.2466 | 1.0271 | 0.0100 | | 60 | 780 | 00:05:13 | 56.25% | 64.52% | 1.1676 | 1.0798 | 0.0100 | |======================================================================================================================|
Следуя структуре [1], этот пример формирует только набор обучения и проверки из Sydney Urban Objects. Оцените производительность обученной сети с помощью проверки, поскольку она не использовалась для обучения сети.
valLabelSet = transform(dsVal,@(data) data{2});
valLabels = readall(valLabelSet);
outputLabels = classify(voxnet,dsVal);
accuracy = nnz(outputLabels == valLabels) / numel(outputLabels);
disp(accuracy)0.6452
Просмотр матрицы путаницы для изучения точности различных категорий меток
figure plotconfusion(valLabels,outputLabels)

Дисбаланс этикеток, отмеченный в обучающем наборе, является проблемой точности классификации. Таблица путаницы иллюстрирует более высокую точность и отзыва для пешехода, наиболее распространенного класса, чем для менее распространенных классов, таких как фургон. Поскольку целью этого примера является демонстрация базового подхода к обучению сети классификации с данными облака точек, возможные последующие шаги, которые могут быть предприняты для улучшения эффективности классификации, такие как повторная выборка обучающего набора или достижение лучшего баланса меток или использование функции потерь, более надежной для маркировки дисбаланса (например, взвешенная перекрестная энтропия), не будут изучены.
1) Voxnet: Трехмерная сверточная нейронная сеть для распознавания объектов в реальном времени, Даниэль Матурана, Себастьян Шерер, 2015 IEEE/RSJ Международная конференция по интеллектуальным роботам и системам (IROS)
2) StartStolps: быстрые кодеры для обнаружения объектов из точечных облаков, Алекс Х. Лэнг, Sourabh Vora, et al, CVPR 2019
3) Sydney Urban Objects Dataset, Alastair Quadros, Джеймс Андервуд, Bertrand Douillard, Sydney Urban Objects
function datasetPath = downloadSydneyUrbanObjects(dataLoc) if nargin == 0 dataLoc = pwd(); end dataLoc = string(dataLoc); url = "http://www.acfr.usyd.edu.au/papers/data/"; name = "sydney-urban-objects-dataset.tar.gz"; if ~exist(fullfile(dataLoc,'sydney-urban-objects-dataset'),'dir') disp('Downloading Sydney Urban Objects Dataset...'); untar(fullfile(url,name),dataLoc); end datasetPath = dataLoc.append('sydney-urban-objects-dataset'); end function ds = loadSydneyUrbanObjectsData(datapath,folds) % loadSydneyUrbanObjectsData Datastore with point clouds and % associated categorical labels for Sydney Urban Objects dataset. % % ds = loadSydneyUrbanObjectsData(datapath) constructs a datastore that % represents point clouds and associated categories for the Sydney Urban % Objects dataset. The input, datapath, is a string or char array which % represents the path to the root directory of the Sydney Urban Objects % Dataset. % % ds = loadSydneyUrbanObjectsData(___,folds) optionally allows % specification of desired folds that you wish to be included in the % output ds. For example, [1 2 4] specifies that you want the first, % second, and fourth folds of the Dataset. Default: [1 2 3 4]. if nargin < 2 folds = 1:4; end datapath = string(datapath); path = fullfile(datapath,'objects',filesep); % For now, include all folds in Datastore foldNames{1} = importdata(fullfile(datapath,'folds','fold0.txt')); foldNames{2} = importdata(fullfile(datapath,'folds','fold1.txt')); foldNames{3} = importdata(fullfile(datapath,'folds','fold2.txt')); foldNames{4} = importdata(fullfile(datapath,'folds','fold3.txt')); names = foldNames(folds); names = vertcat(names{:}); fullFilenames = append(path,names); ds = fileDatastore(fullFilenames,'ReadFcn',@extractTrainingData,'FileExtensions','.bin'); % Shuffle ds.Files = ds.Files(randperm(length(ds.Files))); end function dataOut = extractTrainingData(fname) [pointData,intensity] = readbin(fname); [~,name] = fileparts(fname); name = string(name); name = extractBefore(name,'.'); name = replace(name,'_',' '); labelNames = ["4wd","building","bus","car","pedestrian","pillar",... "pole","traffic lights","traffic sign","tree","truck","trunk","ute","van"]; label = categorical(name,labelNames); dataOut = {pointCloud(pointData,'Intensity',intensity),label}; end function [pointData,intensity] = readbin(fname) % readbin Read point and intensity data from Sydney Urban Object binary % files. % names = ['t','intensity','id',... % 'x','y','z',... % 'azimuth','range','pid'] % % formats = ['int64', 'uint8', 'uint8',... % 'float32', 'float32', 'float32',... % 'float32', 'float32', 'int32'] fid = fopen(fname, 'r'); c = onCleanup(@() fclose(fid)); fseek(fid,10,-1); % Move to the first X point location 10 bytes from beginning X = fread(fid,inf,'single',30); fseek(fid,14,-1); Y = fread(fid,inf,'single',30); fseek(fid,18,-1); Z = fread(fid,inf,'single',30); fseek(fid,8,-1); intensity = fread(fid,inf,'uint8',33); pointData = [X,Y,Z]; end function dataOut = formOccupancyGrid(data) grid = pcbin(data{1},[32 32 32]); occupancyGrid = zeros(size(grid),'single'); for ii = 1:numel(grid) occupancyGrid(ii) = ~isempty(grid{ii}); end label = data{2}; dataOut = {occupancyGrid,label}; end function dataOut = augmentPointCloudData(data) ptCloud = data{1}; label = data{2}; % Apply randomized rotation about Z axis. tform = randomAffine3d('Rotation',@() deal([0 0 1],360*rand),'Scale',[0.98,1.02],'XReflection',true,'YReflection',true); % Randomized rotation about z axis ptCloud = pctransform(ptCloud,tform); % Apply jitter to each point in point cloud amountOfJitter = 0.01; numPoints = size(ptCloud.Location,1); D = zeros(size(ptCloud.Location),'like',ptCloud.Location); D(:,1) = diff(ptCloud.XLimits)*rand(numPoints,1); D(:,2) = diff(ptCloud.YLimits)*rand(numPoints,1); D(:,3) = diff(ptCloud.ZLimits)*rand(numPoints,1); D = amountOfJitter.*D; ptCloud = pctransform(ptCloud,D); dataOut = {ptCloud,label}; end