Main Content

このページの翻訳は最新ではありません。ここをクリックして、英語の最新版を参照してください。

3 次元点群内のオブジェクトを分類するための分類ネットワークの学習

この例では、[1] に記述されている方法について説明します。この方法では、点群データを前処理してボクセルに符号化してから、単純な 3 次元畳み込みニューラル ネットワーク アーキテクチャで直接使用し、オブジェクト分類を行います。点群データの符号化は、[2] などの比較的新しい方法ではより複雑で、分類、オブジェクト検出、およびセグメンテーション タスクを行うネットワークと共にエンドツーエンドで学習した学習済み符号化であることもあります。しかし、不規則な順番付けされていない点を ConvNet に渡せるグリッド構造に移行する基本的パターンは、これらすべての方法においてほぼ同じです。

データのインポートと解析

この例では、Sydney Urban Objects Dataset を使用します。この例では、データのフォールド 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)

ヒストグラムから、学習データにクラスの不均衡という問題があることがわかります。CarPedestrian など特定のクラスが、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});

ネットワーク アーキテクチャの定義

この例では、[1] で説明した単純な 3 次元分類アーキテクチャを使用します。

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);

学習オプションの設定

モーメンタム項付き確率的勾配降下法を、学習率スケジュールに対する区分的な調整と共に使用します。この例は TitanX GPU で実行しましたが、メモリの小さい GPU の場合はバッチ サイズを小さくする必要があるかもしれません。3 次元 ConvNet には概念が単純であるという利点がありますが、学習時に使用するメモリ量が多いという欠点があります。

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 CPU.
|======================================================================================================================|
|  Epoch  |  Iteration  |  Time Elapsed  |  Mini-batch  |  Validation  |  Mini-batch  |  Validation  |  Base Learning  |
|         |             |   (hh:mm:ss)   |   Accuracy   |   Accuracy   |     Loss     |     Loss     |      Rate       |
|======================================================================================================================|
|       1 |           1 |       00:00:12 |        0.00% |        3.23% |       2.6579 |       2.6466 |          0.0100 |
|       4 |          50 |       00:01:53 |       31.25% |       29.03% |       2.1520 |       2.3095 |          0.0100 |
|       8 |         100 |       00:03:33 |       28.12% |       36.77% |       2.2633 |       2.1510 |          0.0100 |
|      12 |         150 |       00:05:11 |       43.75% |       46.45% |       2.0506 |       1.9057 |          0.0100 |
|      16 |         200 |       00:06:49 |       37.50% |       52.26% |       1.8627 |       1.6161 |          0.0100 |
|      20 |         250 |       00:08:35 |       50.00% |       59.35% |       1.8573 |       1.4587 |          0.0100 |
|      24 |         300 |       00:10:14 |       34.38% |       58.06% |       1.8636 |       1.4360 |          0.0100 |
|      27 |         350 |       00:11:51 |       62.50% |       61.94% |       1.4174 |       1.3093 |          0.0100 |
|      31 |         400 |       00:13:31 |       65.62% |       64.52% |       1.1966 |       1.2727 |          0.0100 |
|      35 |         450 |       00:15:09 |       56.25% |       61.94% |       1.3562 |       1.2473 |          0.0100 |
|      39 |         500 |       00:16:49 |       62.50% |       66.45% |       1.2819 |       1.1354 |          0.0100 |
|      43 |         550 |       00:18:27 |       56.25% |       65.16% |       1.4563 |       1.1351 |          0.0100 |
|      47 |         600 |       00:20:05 |       56.25% |       66.45% |       1.3096 |       1.1142 |          0.0100 |
|      50 |         650 |       00:21:40 |       56.25% |       65.16% |       1.0104 |       1.1023 |          0.0100 |
|      54 |         700 |       00:23:21 |       75.00% |       70.32% |       0.9403 |       1.0848 |          0.0100 |
|      58 |         750 |       00:25:00 |       65.62% |       71.61% |       1.0909 |       1.1003 |          0.0100 |
|      60 |         780 |       00:25:59 |       65.62% |       72.26% |       0.9628 |       1.0406 |          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.7226

混同行列を表示して、さまざまなラベル カテゴリの精度を調べます。

confusionchart(valLabels,outputLabels) 

学習用セットに見つかったラベルの不均衡は、分類精度に問題をもたらします。混同チャートは、最も多いクラスである歩行者の適合率と再現率が、バンなどの少ないクラスよりも高いことを示しています。この例の目的は、点群データを使用した基本的な分類ネットワークの学習方法を示すことです。次のステップとして、学習セットのリサンプリングなどによって分類性能を向上させることや、ラベルの均衡を改善することや、ラベルの不均衡に対してよりロバストな損失関数 (加重クロスエントロピーなど) を使用することが考えられますが、ここでは説明しません。

参考文献

1) Voxnet: A 3d convolutional neural network for real-time object recognition, Daniel Maturana, Sebastian Scherer, 2015 IEEE/RSJ International Conference on Intelligent Robots and Systems (IROS)

2) PointPillars: Fast Encoders for Object Detection from Point Clouds, Alex H. Lang, Sourabh Vora, et al, CVPR 2019

3) Sydney Urban Objects Dataset, Alastair Quadros, James Underwood, 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(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