ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

LAS点云数据预处理与PointNet训练全流程解析

LAS点云数据预处理与PointNet训练全流程解析 简介面向需要用自己的LAS点云数据训练PointNet/PointNet分类模型的研究者这套资源提供了从数据处理到模型训练验证的完整脚本可在两种经典点云网络中自由选择。压缩包共19个文件以15个Python脚本为核心覆盖点云数据加载、LAS分类数据集构建、训练与测试等环节另含1份docx说明文档以及2个LAS示例点云文件分别用于训练验证和预测rar包整体仅26KB轻量易用。已有893人学习/下载说明该流程对不少做点云分类的开发者有参考价值。资源基于yanx27的Pointnet_Pointnet2_pytorch开源框架整理并针对带Classification属性的LAS数据做了适配配套文档和示例数据能让读者快速跑通自定义数据训练流程适合入门实践与进一步调参优化。注意开篇直入正题。点云数据的语义分类这事儿真正上手干过的人都知道最卡人的往往不是PointNet和PointNet的网络结构有多深、不是论文里那些新颖模块有多花哨而是数据手头攒了一批LAS格式的点云属性表里带着classification标签想用现成的PointNet仓库把它训起来结果发现官方代码给的例子是ModelNet40和S3DISProcessData脚本全是针对别人家格式写的压根不知道怎么把自己的数据塞进去。这篇文章不聊理论推导专门讲从LAS原始数据到PointNet / PointNet成功收敛的全流程包括LAS的读取与清洗、坐标归一化、Block切分、训练数据组织、参数调整以及我实际踩过的那些坑。1. 动手前先认清任务LAS里的Classification和PointNet的classification不是一回事1.1 LAS的Classification属性到底存了什么LAS是测绘和激光雷达领域最常见的点云存储格式。它和普通xyz文本文件最大的区别在于每条点记录除了三维坐标、强度、回波号这些几何和扫描信息之外还有一整套预定义属性其中最核心的就是classification分类。按照ASPRS的LAS规范classification是一个整数不同取值对应不同地物类型。比如满常见的0代表never classified未分类2代表ground地面3代表low vegetation低矮植被4代表medium vegetation中等植被5代表high vegetation高大植被6代表building建筑。这个属性是逐点的也就是说每个激光脚点都带着一个自己的类别号码。不过我要提醒一句很多项目里的LAS文件并不是严格遵循ASPRS规范来的。我自己就碰到过把building标成6、又把桥梁也标成6、甚至还有标成1unassigned的情况。所以拿到数据的第一个动作不是读进来就训而是先做一次类别摸底。1.2 判断你的任务属于PointNet的哪个分支这是新手最容易混的地方。PointNet官方GitHub仓库里写了两个独立分支classification和segmentation。在PointNet语境里classification指的是shape classification也就是给整个点云或整个物体打一个类别标签比如判断一个杯子还是椅子。它用的是ModelNet40这种数据组织方式每个文件夹是一类物体文件夹内的每个文件是一个完整物体的点云。而LAS文件里那个classification属性是逐点标注。你手里这批数据要做的任务是把每个激光点归类到地面、植被、建筑等类别里。这在PointNet系列的术语体系里对应的是segmentation分支语义分割PointNet工程里是segPointNet工程里是sem_seg或part_seg取决于你用的是哪个官方版本。所以先把这个概念掰扯清楚你的目标不是跑classification文件夹下的classify.py而是跑segmentation/sem_seg下的train.py。如果望着标题里的Classification就直接去改classify.py的输入通道路径就错了后面白折腾。2. 训练数据准备从LAS到模型输入的完整转换2.1 环境准备与laspy读取LAS处理LAS我推荐用laspy这个Python库。它的API简单读写都方便依赖也少。安装直接pip install laspy如果LAS文件是压缩过的laz格式需要额外配laszip后端这个后面的常见问题里我会专门提。先用最常规的读取写一遍提取逻辑import laspy import numpy as np las laspy.read(your_data.las) print(las.point_format) print(np.unique(las.classification, return_countsTrue))读进来之后.x、.y、.z是坐标.classification就是你要的标签。如果LAS还带RGB信息还可以拿.red、.green、.blue当地面真值或者辅助特征。这里有个细节laspy2.x版本中用np.asarray(las.classification)才能拿到整个数组laspy1.x直接las.classification就是numpy数组。如果你照网上老教程写很可能在版本上卡一下。先跑通打印类别分布这个步骤千万别省。2.2 坐标归一化和Block切分PointNet / PointNet输入点云时理想情况是点在有限范围内均匀分布。而LAS数据往往是一片城市级甚至区域级的场景。一个完整的LAS可能有几百万到上亿个点直接把整块场景灌进网络显存想都不要想。而且不经过处理的大规模空旷场景语义上下文也很糟糕。所以通用做法是Block切分也叫体素化分块或规则网格分块。把整个场景按固定尺寸的方格子切开比如每个Block 50米×50米只保留有有效点的格子每个Block再随机采样固定数量的点比如4096个。这个做法和PointNet训练S3DIS数据集时的逻辑一致。import numpy as np def split_blocks(points, labels, block_size50.0, stride50.0): blocks, block_labels [], [] min_bound points[:, :2].min(axis0) max_bound points[:, :2].max(axis0) cur_x min_bound[0] while cur_x max_bound[0]: cur_y min_bound[1] while cur_y max_bound[1]: mask ( (points[:, 0] cur_x) (points[:, 0] cur_x block_size) (points[:, 1] cur_y) (points[:, 1] cur_y block_size) ) block_pts points[mask] block_lbl labels[mask] if len(block_pts) 0: blocks.append(block_pts) block_labels.append(block_lbl) cur_y stride cur_x stride return blocks, block_labels实际使用中stride小于block_size可以产生重叠块增加训练样本数量代价是样本间存在重复区域验证时要注意不要因此产生虚假的性能提升。我通常会先用strideblock_size做一次无重叠切分之后如果样本量不够再缩小stride扩充。每个Block内部的点数差异很大。有建筑和树木的区域点数密集空旷区域可能只有几百个点。训练时统一的采样策略是对每个Block如果点数超过num_point就随机下采样如果不足就重复采样填满。PointNet官方代码里用的就是这种思路。2.3 标签重映射与类别平衡检查LAS里的classification值在0到255之间但实际数值通常不是从0开始的连续序列。比如一个项目里可能出现2、5、6、9这么几个类别中间漏掉一堆数字。PyTorch的CrossEntropyLoss虽然能处理不连续的类别索引但类别数量一旦设定就必须保证每个预测维度的索引和标签数值严格对应。最省心的做法是把原始类别映射成0到C-1的连续整数。举例label_mapping {2: 0, 3: 1, 5: 2, 6: 3, 9: 4} new_labels np.vectorize(lambda x: label_mapping.get(x, -1))(raw_labels)映射后把-1的无效点过滤掉。还有一点要特别当心如果某个类别在整个数据集中只有几百个点而其他类别有几百万个点这种极端不平衡会让网络直接忽略少数类。训练前先打印类别分布如果少数类占比低于百分之一建议要么合并细分类别要么在Block采样阶段做类别加权采样要么只保留主要类别跑第一版baseline。从LAS到训练样本的转换完成后下一步是把数据组织成PointNet仓库能识别的目录格式。3. 数据组织与训练配置按官方仓库的胃口喂数据3.1 目录结构与文件清单PointNet系列官方仓库里喂给semantic segmentation的数据组织方式跟S3DIS一致。以PointNet的train/train_semseg.py为例它期望一个数据根目录里面包含classes.txt、train.txt、val.txt以及每个txt文件里列出的具体样本文件。样本文件可以是纯文本每行是x y z label的5列坐标三项归一化坐标两项结构也可以是npy格式取决于你在data_utils/S3DISDataLoader.py里怎么解析。我在实际项目中习惯直接用npy格式读取速度快省去每轮训练解析txt的开销。目录结构建议长这样data/ classes.txt # 每行一个类别名顺序和标签索引一致 train.txt # 每行一个npy文件的路径 val.txt # 每行一个npy文件的路径 train_samples/ 000.npy # shape: (num_points, 6)前3列坐标后3列归一化坐标 000_label.npy # shape: (num_points,)每个点的类别索引这里有个容易踩的细节官方S3DIS的DataLoader在读取数据时会根据test_area参数在运行时划分训练/验证集而不是直接读train.txt和val.txt里的路径。所以如果你用的不是改过的DataLoader可能得手动改一下加载逻辑让模型按你的路径读数据而不是硬编码找S3DIS的文件夹。我自己是直接fork了官方仓库把S3DISDataLoader.py里的类别列表和文件读取路径改掉别的部分保持不变这样最稳。3.2 训练脚本的关键参数调整数据格式解决后训练脚本里有几个参数直接决定成败。num_point每个Block采样的点数。源码默认4096如果内存吃紧可以降到2048。batch_size官方默认8或16。用单张12G显存的显卡num_point4096时batch_size设4基本是上限。learning_rate初始0.001使用Adam优化器时问题不大。如果loss震荡优先降低到0.0005而不是加batch。epoch默认100或200。LAS数据量通常比S3DIS少很多50轮左右一般就能看到收敛趋势。num_classes务必改成你的映射类别数这个漏改直接cuda error。以PointNet官方sem_seg训练脚本为例关键启动命令大概长这样python train_semseg.py \ --model pointnet2_sem_seg \ --batch_size 8 \ --epoch 100 \ --learning_rate 0.001 \ --num_point 4096 \ --num_classes 5 \ --data_root ./data/这么说显得很顺但真实情况是几乎不可能从头到尾一遍跑通。我建议训练脚本先只跑1个epoch确认数据流、loss曲线、保存的模型文件都正常再放开跑全量。3.3 判断训练状态从loss到可视化训练跑起来之后不要只看training loss降没降。分类任务里训练loss下降、验证准确率不动十有八九是过拟合或者数据泄漏。LAS数据做Block切分时如果验证Block和训练Block在空间上有重叠性能指标会很虚高。所以验证场景最好单独留出一块没参与训练的区域或者按空间去重后的Block做验证。当验证集mIoU开始提升我建议把预测结果可视化出来。可以直接把预测标签重新写回LAS文件然后拖进CloudCompare里看颜色对比。这一步几乎是必须的因为很多错误是类别混淆数值指标看着还行实际地物边缘一塌糊涂。可视化一眼就能发现问题。import laspy import numpy as np # 假设pred_labels是从模型输出的预测结果points是原始坐标 out_las laspy.LasData(las.header) out_las.x points[:, 0] out_las.y points[:, 1] out_las.z points[:, 2] out_las.classification pred_labels out_las.write(predicted.las)CloudCompare打开后按classification字段上色颜色分布跟真实地物的贴合程度比任何指标都直观。4. 常见问题排查与实战心得4.1 数据层面的坑LAS是Laz压缩格式怎么办。laspy读取laz需要后端支持。早期版本要自己装laszip现在laspy2.x可以直接pip install lazrs解决。如果懒用CloudCompare先转成las也行但这个工具在大文件转换时偶尔会丢分类属性转完一定要重新检查一遍类别分布。原始LAS里存在大量重叠点、噪点、无效点。特别是无人机lidar数据偶尔会有坐标完全相同的重复点还有极少数的高程异常值。这些噪点如果不剔除Block切分和归一化时会把坐标范围拉得很夸张导致很多Block稀疏到没法训练。我一般先做一个简单的高度滤波和重复点剔除再进训练流程。标签值不是0起始或中间跳号。这个前面讲过了但值得再强调一次不重映射就训练轻则类别数对不上重则网络把空白索引当成一个隐式类别预测结果里出现你从来没标注过的编号。4.2 训练不收敛的几个原因数据没有做归一化。PointNet系列对输入坐标的尺度非常敏感。LAS坐标系通常是UTM投影x和y的量级是几百公里z是海拔几十米这种尺度差异会让网络在初始阶段疯狂震荡。至少要保证坐标中心化后再除以一个全局尺度最好让每个Block内的坐标分布在-1到1附近。我在代码里用的是Block内独立减去最小值和除以最大范围。类别极度不平衡。大片地面点把建筑和车辆淹没网络最后学成把所有点都预测成地面。这种时候损失函数换加权CrossEntropy或者给少数类别更高的loss权重比一堆花哨的网络改进都管用。num_point设得太大或太小。设太大会把Block里的稀疏区域强制重复采样产生大量重复点等于往数据里注水。设太小则丢失空间上下文。我试过1024、2048、4096在常见机载点云场景里2048到4096是性价比最高的区间。4.3 扩展建议LAS数据量如果特别大比如几十GB的城市场景纯靠Block切分和随机采样效率很低。PointNet官方还支持在训练时做在线采样但速度会慢。更高效的做法是先用阈值分割或语义规则把明显的地面先分离掉再把剩下的部分交给网络这样网络只需专注区分建筑、植被、车辆这些相对难分的类别。另外如果训练自己的数据只是为了工程项目验证而不是发论文可以先在公开数据集上做一次预训练比如S3DIS或Semantic3D再用自己的LAS数据做微调。这个方法在数据量不够时特别实用。我做过一轮对比预训练后微调比从零训练多个数据量的情况下收敛轮数能少三分之一以上。最后再分享一个小技巧训练结束后把模型预测结果和原始标签一起画在CloudCompare里对比时建议把两片点云分别设成不同透明度叠加查看。这样类别的边界误差、椒盐噪声在哪里一眼就能定位到对应Block回去改数据也更有针对性。点云语义分割这门技术数据预处理做得好不好基本决定了最终落地的效果上限。本文还有配套的精品资源点击获取
返回列表