メインコンテンツ

RandLANet 深層学習を使用した航空 LiDAR のセマンティック セグメンテーション

この例では、航空 LiDAR データのセマンティック セグメンテーションを実行するために RandLANet 深層学習ネットワークに学習させる方法を示します。

航空レーザー スキャン システムから取得した LiDAR データは、地形図作成、都市モデリング、バイオマス測定、災害管理などの用途に使用できます。このデータから意味のある情報を抽出するには、点群の各点に一意のクラス ラベルを割り当てるセマンティック セグメンテーションのプロセスが必要です。

この例では、セマンティック セグメンテーションを実行するために、Dayton Annotated Lidar Earth Scan (DALES) データ セット [2] を使用して、RandLANet [1] ネットワークに学習させます。このデータ セットには、都市部、郊外、農村地域、および商業地域のシーンについて、ラベルが付けられた高密度の航空 LiDAR データが含まれています。データ セットには、建物、自動車、トラック、ポール、電力線、フェンス、地面、植生の 8 つのクラスに対するセマンティック セグメンテーション ラベルがあります。

DALES データの読み込み

DALES データ セットには、40 個のシーンの航空 LiDAR データが含まれています。40 個のシーンのうち、29 個のシーンを学習に使用し、残りの 11 個のシーンをテストに使用します。DALES Web サイトの指示に従って、変数 dataFolder で指定するフォルダーにデータ セットをダウンロードします。学習データとテスト データの保存先のフォルダーを作成します。

dataFolder = fullfile(tempdir,"DALES");
trainDataFolder = fullfile(dataFolder,"dales_las","train");
testDataFolder = fullfile(dataFolder,"dales_las","test");

学習データから点群をプレビューします。

lasReader = lasFileReader(fullfile(trainDataFolder,"5080_54435.las"));
[pc,attr] = readPointCloud(lasReader,"Attributes","Classification");
labels = attr.Classification;

% Select only labeled data.
pc = select(pc,labels~=0);
labels = labels(labels~=0);
classNames = ["ground"; ...
    "vegetation"; ...
    "cars"; ...
    "trucks"; ...
    "powerlines"; ...
    "fences"; ...
    "poles"; ...
    "buildings"];
figure
ax = pcshow(pc.Location,labels);
helperLabelColorbar(ax,classNames)
title("Point Cloud with Overlaid Semantic Labels")

学習データの前処理

学習データを前処理するために、まず学習データのすべての点群ファイルを収集するためのfileDatastoreオブジェクトを作成します。

fds = fileDatastore(trainDataFolder,"ReadFcn",@lasFileReader);

DALES データ セットの各点群は、500×500 メートルの領域をカバーし、約 1000 万点を含みます。このような多数の点を処理すると、ネットワーク処理が遅くなることがあります。メモリ処理を効率的にするために、helperTransformData 補助関数 (この例の最後で定義) を使用して、オーバーラップしない 50×50 メートルのサイズの小さなブロックに点群を分割します。

ブロックの寸法を定義します。

blockSize = [50 50];

学習データストアに変換を適用します。

ldsTransformed = transform(fds,@(x) helperTransformData(x,blockSize));

この例にサポート ファイルとして添付されている helperDatastoreWriteFcn 補助関数を使用して、前処理済みの点群とセマンティック ラベルをそれぞれ PCD ファイルと PNG ファイルとして保存します。前処理済みの学習データが outputFolder に既に存在する場合は、writeFilesfalse に設定できます。

writeFiles = true;
outputFolder = fullfile(dataFolder,"PreprocessedTrainData");
if writeFiles
    writeall(ldsTransformed,outputFolder,WriteFcn = @helperDatastoreWriteFcn,FolderLayout = "flatten");
end
Processing done for file: 5190_54400.las

メモ: 処理には時間がかかる場合があります。このコードは、処理が完了するまで MATLAB® の実行を一時停止します。

コードを再実行する場合は、まず "clear helperDatastoreWriteFcn" コマンドを使用する必要があります。

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

pcread関数を使用して、PCD ファイルを読み取るfileDatastoreオブジェクトを作成します。

pcCropTrainPath = fullfile(outputFolder,"PointCloud");
ldsTrain = fileDatastore(pcCropTrainPath,"ReadFcn",@(x) pcread(x));

pixelLabelDatastoreオブジェクトを使用して、ピクセル ラベル イメージからピクセル単位のラベルを保存します。オブジェクトは、各ピクセル ラベルをクラス名にマッピングし、各クラスに一意のラベル ID を割り当てます。

% Specify label IDs from 1 to the number of classes.
numClasses = numel(classNames);
labelIDs = 1 : numClasses;
labelCropTrainPath = fullfile(outputFolder,"Labels");
pxdsTrain = pixelLabelDatastore(labelCropTrainPath,classNames,labelIDs);

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

cds = combine(ldsTrain,pxdsTrain);

RandLANet オブジェクトの作成

RandLANet は、LiDAR 点群のセマンティック セグメンテーションによく使用されるニューラル ネットワークです。このネットワークでは、スキップ接続を含む符号化器-復号化器アーキテクチャを使用します。各点の特徴を学習させるために、共有多層パーセプトロン層と 4 つの符号化および復号化層に順に入力を与えます。最後に、3 つの全結合層と 1 つのドロップアウト層により、3 次元点群の各点が自動車、トラック、地面、植生などのクラス ラベルに関連付けられます。

次の図は、RandLANet のアーキテクチャを示しています。ここで、

  • FC — 全結合層

  • LFA — 局所特徴集約

  • RS — ランダム サンプリング

  • MLP — 共有多層パーセプトロン

  • US — アップサンプリング

  • DP — ドロップアウト

2023-07-13_17-45-41.png

各符号化層が点群をランダムにダウンサンプリングし、点密度を大幅に低減します。ダウンサンプリングした点群の主な特徴を保持するために、ネットワークでは各点に局所特徴集約モジュールを使用します。

各復号化層が最近傍内挿により、特徴点セットをアップサンプリングします。ネットワークはそれらのアップサンプリングされたマップと、スキップ接続された符号化層により生成された特徴マップを連結し、連結された特徴マップに共有 MLP を適用します。

randlanet関数を使用して、RandLANet セグメンテーション ネットワークを作成します。

segmenter = randlanet("none",classNames);

学習オプションの指定

Adam 最適化アルゴリズムを使用してネットワークに学習させます。関数trainingOptions (Deep Learning Toolbox)を使用して、ハイパーパラメーターを指定します。

learningRate = 0.01;
numEpochs = 200;
miniBatchSize = 8;
learnRateDropFactor = 0.9886;
executionEnvironment = "auto";

if canUseParallelPool
    dispatchInBackground = true;
else
    dispatchInBackground = false;
end
 
options = trainingOptions("adam", ...
    InitialLearnRate = learningRate, ...
    MaxEpochs = numEpochs, ...
    MiniBatchSize = miniBatchSize, ...
    LearnRateSchedule = "piecewise", ...
    LearnRateDropPeriod = 1,...
    LearnRateDropFactor = learnRateDropFactor, ...
    Plots = "training-progress", ...
    ExecutionEnvironment = executionEnvironment, ...
    dispatchInBackground = dispatchInBackground, ...
    ResetInputNormalization = false, ...
    CheckpointFrequencyUnit = "epoch", ...
    CheckpointFrequency = 10, ...
    CheckpointPath = tempdir, ...
    BatchNormalizationStatistics="moving");

メモ: 学習時のメモリ使用量を制御するには、miniBatchSize の値を増減します。

モデルの学習

RandLANet セグメンテーション ネットワークに学習させるには、trainRandlanet関数を使用し、doTraining 引数を true に設定します。そうしない場合は、事前学習済みのセグメンテーション ネットワークを読み込みます。ネットワークの学習には CPU または GPU を使用できます。GPU を使用するには、Parallel Computing Toolbox™、および CUDA® 対応の NVIDIA® GPU が必要です。詳細については、GPU 計算の要件 (Parallel Computing Toolbox)を参照してください。

doTraining = false;
if doTraining
    segmenter = trainRandlanet(cds,segmenter,options);
else
    segmenter = randlanet("dales");
end

航空点群のセグメント化

複数のテスト点群を読み取るためのfileDatastoreオブジェクトを作成します。ここでは、デモンストレーションの目的で単一の点群を読み取ります。

fdsTest = fileDatastore(fullfile(testDataFolder,"5080_54470.las"),"ReadFcn",@lasFileReader);

学習データで使用したのと同様の変換をテスト データに適用します。

  • 点群と対応するラベルを抽出する。

  • オーバーラップしない 50×50 メートルのサイズのブロックに点群をトリミングする。

ldsTestTransformed = transform(fdsTest,@(x) helperTransformData(x,blockSize));

テスト点群のすべてのブロックを読み取ります。X には、テスト点群のすべてのブロックの pointCloud オブジェクトおよび対応するラベルが含まれていることに注意してください。

X = read(ldsTestTransformed);

サイズが M 行 1 列の pointClouds のブロックの配列を含む、pointCloud オブジェクトを作成します。segmentObjects は入力としてサイズが M 行 1 列の pointCloud 配列のみを受け入れることに注意してください。

ptCloudArray = vertcat(X{:,1});

segmentObjects関数を使用して推定を実行し、予測ラベルを計算します。

[labels,scores] = segmentObjects(segmenter,ptCloudArray);

点群の予測を表示します。

% Concatenate blocks of point cloud.
pc = pccat(ptCloudArray);

% Concatenate corresponding labels of blocks of point cloud.
labelsDensePred = vertcat(labels{:});

% Convert labels from categorical to numeric for display.
labelsDensePred = single(categorical(labelsDensePred,classNames,cellstr(string(1:numClasses))));
figure
ax = pcshow(pc.Location,labelsDensePred);
axis off
helperLabelColorbar(ax,classNames)
title("Point Cloud Overlaid with Detected Semantic Labels")

ネットワークの評価

テスト データで評価を実行するには、テスト点群からラベルを取得します。計算して labelsDensePred に保存した予測ラベルを使用します。

グラウンド トゥルース ラベルを抽出します。

labelsDenseTarget = vertcat(X{:,2});

関数evaluateSemanticSegmentationを使用して、テスト セットの結果からセマンティック セグメンテーション メトリクスを計算します。

confusionMatrix = segmentationConfusionMatrix(double(labelsDensePred), ...
    double(labelsDenseTarget),Classes = 1:numClasses);
metrics = evaluateSemanticSegmentation({confusionMatrix},classNames,Verbose = false);

Intersection over Union (IoU) メトリクスを使用して、クラスごとのオーバーラップの量を測定できます。

関数evaluateSemanticSegmentationは、データ セット全体、個々のクラス、および各テスト イメージのメトリクスを返します。データ セット レベルでメトリクスを表示するには、metrics.DataSetMetrics プロパティを検査します。

metrics.DataSetMetrics
ans=1×4 table
    GlobalAccuracy    MeanAccuracy    MeanIoU    WeightedIoU
    ______________    ____________    _______    ___________

       0.95846           0.8537       0.70479      0.92468  

データ セット メトリクスは、ネットワーク パフォーマンスの概要を提供します。全体のパフォーマンスに対する各クラスの影響を確認するには、metrics.ClassMetrics プロパティを検査して各クラスのメトリクスを表示します。

metrics.ClassMetrics
ans=8×2 table
                  Accuracy      IoU  
                  ________    _______

    ground        0.97714     0.95606
    vegetation    0.91161     0.89449
    cars          0.95408     0.73238
    trucks        0.34457     0.23184
    powerlines    0.90194     0.85462
    fences        0.89157     0.48109
    poles         0.87155     0.56866
    buildings     0.97715     0.91921

サポート関数

helperTransformData 関数は、データに次の変換を適用します。

  • 点群と対応するラベルを抽出する。

  • DALES データ セットの各点群は、500×500 メートルの領域をカバーします。これは、回転式地上 LiDAR の点群でカバーされる一般的な領域よりもはるかに広いものです。メモリ処理を効率的にするために、オーバーラップしない 50×50 メートルのサイズの小さなブロックに点群を分割します。

function out = helperTransformData(lasReader,blocksize)

[ptCloud,attr] = readPointCloud(lasReader,"Attributes","Classification");
labels = attr.Classification;

% Select only labeled data.
ptCloud = select(ptCloud,labels~=0);
labels = labels(labels~=0);

% Block the input point cloud.
numGridsX = round((diff(ptCloud.XLimits)+eps)/blocksize(1));
numGridsY = round((diff(ptCloud.YLimits)+eps)/blocksize(2));
[~,~,~,indx,indy] = histcounts2(ptCloud.Location(:,1),ptCloud.Location(:,2), ...
    [numGridsX, numGridsY],XBinLimits = ptCloud.XLimits, YBinLimits = ptCloud.YLimits);
ind = sub2ind([numGridsX, numGridsY],indx,indy);

out = cell(numGridsX*numGridsY,2);

    for num = 1:numGridsX*numGridsY
        idx = ind == num;

        if(any(idx))
            out{num,1} = select(ptCloud,idx);
            out{num,2} = labels(idx);
        end
    end

end

参考文献

[1] Hu, Qingyong, Bo Yang, Linhai Xie, Stefano Rosa, Yulan Guo, Zhihua Wang, Niki Trigoni, and Andrew Markham. “RandLA-Net: Efficient Semantic Segmentation of Large-Scale Point Clouds.”In 2020 IEEE/CVF Conference on Computer Vision and Pattern Recognition (CVPR), 11105–14. Seattle, WA, USA: IEEE, 2020. https://doi.org/10.1109/CVPR42600.2020.01112.

[2] Varney, Nina, Vijayan K. Asari, and Quinn Graehling. “DALES: A Large-Scale Aerial LiDAR Data Set for Semantic Segmentation.”In 2020 IEEE/CVF Conference on Computer Vision and Pattern Recognition Workshops (CVPRW), 717–26.Seattle, WA, USA: IEEE, 2020. https://doi.org/10.1109/CVPRW50498.2020.00101.

参考

オブジェクト

関数

アプリ

トピック