メインコンテンツ

PointPillars 深層学習を使用した LiDAR の 3 次元オブジェクト検出

この例では、PointPillars 深層学習ネットワーク [1] を使用して LiDAR でのオブジェクトを検出する方法を示します。この例では、次を行います。

  • PointPillars オブジェクト検出ネットワークの学習用とテスト用のデータセットを構成する。学習データセットに対するデータ拡張も実行し、ネットワーク効率を向上させる。

  • 学習データからアンカー ボックスを計算して、PointPillars オブジェクト検出ネットワークに学習させる。

  • 関数 pointPillarsObjectDetector を使用して PointPillars オブジェクト検出器を作成し、関数 trainPointPillarsObjectDetector を使用して検出器に学習させる。

この例では、点群内のオブジェクトの検出に使用する事前学習済みの PointPillars オブジェクト検出器も提供します。事前学習済みのモデルは、Pandaset データセットで学習させています。pointpillars オブジェクト検出ネットワークの詳細については、PointPillars 入門を参照してください。

LiDAR データ セットのダウンロード

PandaSet データ セット [2] からセンサー データのサブセットを含む ZIP ファイル (サイズは約 5.2 GB) をダウンロードします。ダウンロードした後、ファイルを解凍します。このファイルには、LidarCuboids という 2 つのメイン フォルダーが含まれており、これには次のデータが含まれています。

  • PointCloud フォルダーには、前処理された 2560 個のオーガナイズド点群が PCD 形式で格納されています。この点群は、エゴ ビークルが正の y 軸に沿って移動するように配置されています。点群がこの向きから外れている場合は、pctransform関数を利用して必要な変換を適用できます。

  • Cuboid フォルダーには、対応するグラウンド トゥルース データがテーブル形式で格納されています。このデータは PandasetLidarGroundTruth.mat ファイルに保存されています。このファイルは、3 つのカテゴリ (自動車、トラック、歩行者) の 3 次元境界ボックスの情報を提供します。点群に変換を適用する場合、bboxwarp関数を使用して境界ボックスに変換を適用する必要があります。

この例の終わりに定義されている補助関数 helperDownloadPandasetData を使用して、指定された URL から PandaSet データセットをダウンロードします。

outputFolder = fullfile(tempdir,'Pandaset');

lidarURL = ['https://ssd.mathworks.com/supportfiles/lidar/data/' ...
    'Pandaset_LidarData.tar.gz'];
helperDownloadPandasetData(outputFolder,lidarURL);

インターネット接続の速度によっては、ダウンロード プロセスに時間がかかることがあります。このコードは、ダウンロード プロセスが完了するまで、MATLAB® の実行を一時停止します。または、Web ブラウザーを使用してデータ セットをローカル ディスクにダウンロードし、ファイルを抽出することもできます。その場合は、コード内の変数 outputFolder を、ダウンロードしたファイルの場所に変更します。

データ セットの読み込み

関数pcreadを使用して、指定されたパスから PCD ファイルを読み込むためのファイル データストアを作成します。

path = fullfile(outputFolder,'Lidar');
lidarData = fileDatastore(path,'ReadFcn',@(x) pcread(x));

自動車とトラックのオブジェクトの 3 次元境界ボックス ラベルを読み込みます。

gtPath = fullfile(outputFolder,'Cuboids','PandaSetLidarGroundTruth.mat');
data = load(gtPath,'lidarGtLabels');
Labels = timetable2table(data.lidarGtLabels);
boxLabels = Labels(:,2:3);

フルビューの点群を表示します。

figure
ptCld = preview(lidarData);
ax = pcshow(ptCld.Location);
set(ax,'XLim',[-50 50],'YLim',[-40 40]);
zoom(ax,2.5);
axis off;

Figure contains an axes object. The hidden axes object contains an object of type scatter.

データの前処理

PandaSet データは、フルビューの点群で構成されています。この例では、標準パラメーター [1] を使用して、フルビューの点群をフロントビューの点群にトリミングします。これらのパラメーターは、ネットワークに渡される入力のサイズを決定します。x、y、z 軸に沿ってより狭い点群の範囲を選択して、原点に近いオブジェクトを検出します。これによって、ネットワークの全体的な学習時間も短縮されます。

xMin = 0.0;     % Minimum value along X-axis.
yMin = -39.68;  % Minimum value along Y-axis.
zMin = -5.0;    % Minimum value along Z-axis.
xMax = 69.12;   % Maximum value along X-axis.
yMax = 39.68;   % Maximum value along Y-axis.
zMax = 5.0;     % Maximum value along Z-axis.
xStep = 0.16;   % Resolution along X-axis.
yStep = 0.16;   % Resolution along Y-axis.

% Define point cloud parameters.
pointCloudRange = [xMin xMax yMin yMax zMin zMax];
voxelSize = [xStep yStep];

この例にサポート ファイルとして添付されている補助関数 cropFrontViewFromLidarData を使用して、次のようにします。

  • 入力のフルビューの点群をトリミングしてフロント ビューを作成。

  • gridParams で指定された ROI の内側にあるボックス ラベルを選択。

[croppedPointCloudObj,processedLabels] = cropFrontViewFromLidarData(...
    lidarData,boxLabels,pointCloudRange);
Processing data 100% complete

トリミングされた点群とグラウンド トゥルース ボックス ラベルを表示します。

pc = croppedPointCloudObj{1,1};
bboxes = [processedLabels.Car{1};processedLabels.Truck{1}];

ax = pcshow(pc);
showShape('cuboid',bboxes,'Parent',ax,'Opacity',0.1,...
        'Color','green','LineWidth',0.5);

Figure contains an axes object. The axes object contains an object of type scatter.

reset(lidarData);

データストア オブジェクトの作成

データ セットを学習セットとテスト セットに分割します。データの 70% をネットワークの学習用に選択し、残りを評価用に選択します。

rng(1);
shuffledIndices = randperm(size(processedLabels,1));
idx = floor(0.7 * length(shuffledIndices));

trainData = croppedPointCloudObj(shuffledIndices(1:idx),:);
testData = croppedPointCloudObj(shuffledIndices(idx+1:end),:);

trainLabels = processedLabels(shuffledIndices(1:idx),:);
testLabels = processedLabels(shuffledIndices(idx+1:end),:);

データストアに容易にアクセスできるように、この例にサポート ファイルとして添付されている補助関数 saveptCldToPCD を使用して、学習データを PCD ファイルとして保存します。学習データがフォルダーに保存されており、そのデータが関数pcreadによってサポートされている場合、writeFiles を "false" に設定できます。

writeFiles = true;
dataLocation = fullfile(outputFolder,'InputData');
[trainData,trainLabels] = saveptCldToPCD(trainData,trainLabels,...
    dataLocation,writeFiles);
Processing data 100% complete

fileDatastoreを使用して、関数pcreadを使用して PCD ファイルを読み込むためのファイル データストアを作成します。

lds = fileDatastore(dataLocation,'ReadFcn',@(x) pcread(x));

boxLabelDatastoreを使用して、3 次元境界ボックス ラベルを読み込むためのボックス ラベル データストアを作成します。

bds = boxLabelDatastore(trainLabels);

関数combineを使用して、学習用に点群と 3 次元境界ボックス ラベルを単一のデータストアに統合します。

cds = combine(lds,bds);

データ拡張の実行

この例では、グラウンド トゥルース データ拡張と他のいくつかのグローバル データ拡張手法を使用して、学習データと対応するボックスにさらに多様性を付加します。LIDAR データによる 3 次元オブジェクト検出ワークフローで使用される典型的なデータ拡張手法の詳細については、Data Augmentations for Lidar Object Detection Using Deep Learningを参照してください。

この例の終わりに定義されている helperShowPointCloudWith3DBoxes 補助関数を使用して、拡張前の点群を読み取って表示します。

augData = preview(cds);
[ptCld,bboxes,labels] = deal(augData{1},augData{2},augData{3});

% Define the classes for object detection.
classNames = {'Car','Truck'};

% Define colors for each class to plot bounding boxes.
colors = {'green','magenta'};

helperShowPointCloudWith3DBoxes(ptCld,bboxes,labels,classNames,colors)

Figure contains an axes object. The axes object contains an object of type scatter.

sampleLidarData関数を使用して、3 次元境界ボックスとそれに対応する点を学習データからサンプリングします。

sampleLocation = fullfile(outputFolder,'GTsamples');
[ldsSampled,bdsSampled] = sampleLidarData(cds,classNames,'MinPoints',20,...                  
                            'Verbose',false,'WriteLocation',sampleLocation);
cdsSampled = combine(ldsSampled,bdsSampled);

pcBboxOversample関数を使用して、固定数の自動車クラスとトラック クラスのオブジェクトをすべての点群にランダムに追加します。関数transformを使用して、グラウンド トゥルース データとカスタム データの拡張を学習データに適用します。

numObjects = [10 10];
cdsAugmented = transform(cds,@(x)pcBboxOversample(x,cdsSampled,classNames,numObjects));

この例の終わりに定義されている helperAugmentLidarData 補助関数を使用して、すべての点群に次の追加のデータ拡張手法を適用します。

  • 5% のランダムなスケーリング

  • [-pi/4, pi/4] の範囲で z 軸に沿ってランダムに回転

  • x 軸、y 軸、z 軸に沿って、それぞれ [0.2, 0.2, 0.1] メートルずつランダムに平行移動

cdsAugmented = transform(cdsAugmented,@(x)helperAugmentData(x));

この例の終わりに定義されている helperShowPointCloudWith3DBoxes 補助関数を使用して、拡張された点群と拡張されたグラウンド トゥルース ボックスを表示します。

augData = preview(cdsAugmented);
[ptCld,bboxes,labels] = deal(augData{1},augData{2},augData{3});
helperShowPointCloudWith3DBoxes(ptCld,bboxes,labels,classNames,colors)

Figure contains an axes object. The axes object contains an object of type scatter.

PointPillars オブジェクト検出器の作成

関数pointPillarsObjectDetectorを使用して、PointPillars オブジェクト検出ネットワークを作成します。PointPillars ネットワークの詳細については、PointPillars 入門を参照してください。

図は、PointPillars オブジェクト検出器のネットワーク アーキテクチャを示しています。PointPillars ネットワークはディープ ネットワーク デザイナー (Deep Learning Toolbox)アプリを使用して作成できます。

PPNetwork.png

この例にサポート ファイルとして添付されている補助関数 calculateAnchorsPointPillars を使用して、学習データのアンカー ボックスを推定します。

anchorBoxes = calculateAnchorsPointPillars(trainLabels);

PointPillars 検出器を定義します。

detector = pointPillarsObjectDetector(pointCloudRange,classNames,anchorBoxes,...
    'VoxelSize',voxelSize); 

学習オプションの指定

関数trainingOptions (Deep Learning Toolbox)を使用して、ネットワーク学習パラメーターを指定します。学習が中断された場合に、保存したチェックポイントから学習を再開できます。

CPU または GPU を使用して検出器に学習させます。GPU を使用するには、Parallel Computing Toolbox™、および CUDA® 対応の NVIDIA® GPU が必要です。詳細については、GPU 計算の要件 (Parallel Computing Toolbox)を参照してください。使用できる GPU が存在するか自動的に検出するには、executionEnvironment"auto" に設定します。GPU がない場合、または学習で GPU を使用しない場合は、executionEnvironment"cpu" に設定します。学習に GPU が使用されるようにするために、executionEnvironment"gpu" に設定します。

executionEnvironment = "auto";

options = trainingOptions('adam',...
    Plots = "training-progress",...
    MaxEpochs = 60,...
    MiniBatchSize = 3,...
    GradientDecayFactor = 0.9,...
    SquaredGradientDecayFactor = 0.999,...
    LearnRateSchedule = "piecewise",...
    InitialLearnRate = 0.0002,...
    LearnRateDropPeriod = 15,...
    LearnRateDropFactor = 0.8,...
    ExecutionEnvironment= executionEnvironment, ...
    PreprocessingEnvironment = 'parallel',...
    BatchNormalizationStatistics = 'moving',...
    ResetInputNormalization = false,...
    CheckpointFrequency = 10, ...
    CheckpointFrequencyUnit = 'epoch', ...
    CheckpointPath = userpath);

PointPillars オブジェクト検出器の学習

doTraining が "true" の場合、関数trainPointPillarsObjectDetectorを使用して PointPillars オブジェクト検出器に学習させます。そうでない場合は、事前学習済みの検出器を読み込みます。

doTraining = false;
if doTraining    
    [detector,info] = trainPointPillarsObjectDetector(cdsAugmented,detector,options);
else
    pretrainedDetector = load('pretrainedPointPillarsDetector.mat','detector');
    detector = pretrainedDetector.detector;
end

メモ: 事前学習済みネットワーク pretrainedPointPillarsDetector.mat は、Pandar 64 センサーによって取得された点群データで学習させます。エゴ ビークルの方向は正の y 軸に沿うようにします。

カスタム データセットでこの事前学習済みネットワークを使用して正確な検出を生成するには、次のようにします。

  1. エゴ ビークルが正の y 軸に沿って移動するように点群データを変換します。

  2. 関数pcorganizeを使用し、Pandar 64 のパラメーターでデータを再構築します。

検出の生成

学習済みネットワークを使用して、テスト データに含まれるオブジェクトを検出します。

  • テスト データから点群を読み取る。

  • テスト用の点群に対して検出器を実行し、予測された境界ボックスと信頼スコアを取得する。

  • この例の終わりに定義されている補助関数 helperDisplay3DBoxesOverlaidPointCloud を使用して、境界ボックス付きの点群を表示する。

ptCloud = testData{1,1};

% Run the detector on the test point cloud.
[bboxes,score,labels] = detect(detector,ptCloud);

% Display the predictions on the point cloud.
helperShowPointCloudWith3DBoxes(ptCloud,bboxes,labels,classNames,colors)

Figure contains an axes object. The axes object contains an object of type scatter.

テスト セットを使用した検出器の評価

大規模な点群データ セットで学習済みのオブジェクト検出器を評価し、パフォーマンスを測定します。

numInputs = 50;

% Generate rotated rectangles from the cuboid labels.
bds = boxLabelDatastore(testLabels(1:numInputs,:));
groundTruthData = transform(bds,@(x)createRotRect(x));

detectionResults = detect(detector,testData(1:numInputs,:),...
                         'Threshold',0.25);

% Convert the bounding boxes to rotated rectangles format and calculate
% the evaluation metrics.
for i = 1:height(detectionResults)
    box = detectionResults.Boxes{i};
    detectionResults.Boxes{i} = box(:,[1,2,4,5,9]);
    detectionResults.Labels{i} = detectionResults.Labels{i}';
end

% Evaluate the object detector using average orietation similarity metric
metrics = evaluateObjectDetection(detectionResults,groundTruthData,"AdditionalMetrics","AOS");
[datasetSummary,classSummary] = summarize(metrics)
datasetSummary=1×5 table
    NumObjects    mAPOverlapAvg    mAP0.5     mAOSOverlapAvg    mAOS0.5
    __________    _____________    _______    ______________    _______

       511           0.70556       0.70556       0.68208        0.68208

classSummary=2×5 table
             NumObjects    APOverlapAvg     AP0.5     AOSOverlapAvg    AOS0.5 
             __________    ____________    _______    _____________    _______

    Car         480          0.76271       0.76271       0.74664       0.74664
    Truck        31          0.64841       0.64841       0.61751       0.61751

サポート関数

helperDownloadPandasetData 関数は、Pandaset データをダウンロードします。

function helperDownloadPandasetData(outputFolder,lidarURL)
% Download the data set from the given URL to the output folder.

    lidarDataTarFile = fullfile(outputFolder,'Pandaset_LidarData.tar.gz');
    
    if ~exist(lidarDataTarFile,'file')
        mkdir(outputFolder);
        
        disp('Downloading PandaSet Lidar driving data (5.2 GB)...');
        websave(lidarDataTarFile,lidarURL);
        untar(lidarDataTarFile,outputFolder);
    end
    
    % Extract the file.
    if (~exist(fullfile(outputFolder,'Lidar'),'dir'))...
            &&(~exist(fullfile(outputFolder,'Cuboids'),'dir'))
        untar(lidarDataTarFile,outputFolder);
    end

end

helperShowPointCloudWith3DBoxes 関数は、点群およびそれに関連付けられた境界ボックスを表示します。その際、クラスを簡単に区別できるように、クラスごとに異なる色を使用します。

function helperShowPointCloudWith3DBoxes(ptCld,bboxes,labels,classNames,colors)
    % Validate the length of classNames and colors are the same
    assert(numel(classNames) == numel(colors), 'ClassNames and Colors must have the same number of elements.');
    
    % Get unique categories from labels
    uniqueCategories = categories(labels); 

    % Create a mapping from category to color
    colorMap = containers.Map(uniqueCategories, colors); 
    labelColor = cell(size(labels));

    % Populate labelColor based on the mapping
    for i = 1:length(labels)
        labelColor{i} = colorMap(char(labels(i)));
    end

    figure;
    ax = pcshow(ptCld); 
    showShape('cuboid', bboxes, 'Parent', ax, 'Opacity', 0.1, ...
        'Color', labelColor, 'LineWidth', 0.5);
    zoom(ax,1.5);
end

helperAugmentData 関数は、データに次の拡張を適用します。

  • 5% のランダムなスケーリング。

  • [-pi/4, pi/4] の範囲で z 軸に沿ってランダムに回転。

  • x 軸、y 軸、z 軸に沿って、それぞれ [0.2, 0.2, 0.1] メートルずつランダムに平行移動。

function data = helperAugmentData(data)
    % Apply random scaling, rotation and translation.
    pc = data{1};
    
    minAngle = -45;
    maxAngle = 45;
    
    % Define outputView based on the grid-size and XYZ limits.
    outView = imref3d([32,32,32],[-100,100],...
        [-100,100],[-100,100]);
    
    
    theta = minAngle + rand(1,1)*(maxAngle - minAngle);
    tform = randomAffine3d('Rotation',@() deal([0,0,1],theta),...
        'Scale',[0.95,1.05],...
        'XTranslation',[0,0.2],...
        'YTranslation',[0,0.2],...
        'ZTranslation',[0,0.1]);
    
    % Apply the transformation to the point cloud.
    ptCloudTransformed = pctransform(pc,tform);
    
    % Apply the same transformation to the boxes.
    bbox = data{2};
    [bbox,indices] = bboxwarp(bbox,tform,outView);
    if ~isempty(indices)
        data{1} = ptCloudTransformed;
        data{2} = bbox;
        data{3} = data{1,3}(indices,:);
    end

end

参考文献

[1] Lang, Alex H., Sourabh Vora, Holger Caesar, Lubing Zhou, Jiong Yang, and Oscar Beijbom. "PointPillars: Fast Encoders for Object Detection From Point Clouds." In 2019 IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR), 12689-12697. Long Beach, CA, USA: IEEE, 2019. https://doi.org/10.1109/CVPR.2019.01298.

[2] Hesai and Scale.PandaSet. https://scale.com/open-datasets/pandaset.

参考

オブジェクト

関数

アプリ

トピック