ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

深度学习点云语义分割实战:SUPnet+PointNet++与S3DIS全流程解析

深度学习点云语义分割实战:SUPnet+PointNet++与S3DIS全流程解析 简介面向计算机专业毕业设计与课程作业的深度学习实战资源聚焦场景语义分割任务覆盖模型设计、数据处理、训练评估与部署思路。资源共24个文件以Python脚本为主如模型定义、训练入口、数据加载工具配合XML工程配置、TXT运行说明、PNG可视化结果等压缩包约820KB目录结构清晰便于按模块查阅。目前已有157人学习下载。内容包含基于PointNet等架构的场景语义分割实现室内S3DIS数据集的采集、加载与预处理工具以及独立的训练和测试脚本同时附带PyCharm项目配置与README笔记方便直接运行与二次开发。无论是完成课程项目还是毕业设计都能从中获得可复用的代码框架、数据处理链路和模型调参思路有效降低从零搭建语义分割系统的时间成本与理解门槛。1. 基于深度学习的场景语义分割从压缩包到可跑的代码我拆完后的经验拿到这个“毕设课程作业_基于深度学习的场景语义分割.zip”的时候我第一反应是去找里面真正能跑的核心文件。压缩包里的train_SUPnet.py、test_SUPnet.py、pointnet2_utils.py、S3DISDataLoader.py已经说明问题这不是一个常见的2D图像语义分割项目而是一个针对点云场景语义分割的PyTorch工程核心架构基于SUPnet骨干网络明确落到PointNet上。对正在选毕设方向或做课程大作业的人来说这套代码的价值在于把数据预处理、跨场景数据加载、分阶段训练、测试评估完整串了起来比单独看论文复现能省掉大量处理框架粘连的时间。适合有Python和PyTorch基础、想直接在S3DIS这类室内场景数据集上跑通分割结果的人也适合做“基于深度学习的场景语义分割”课题开题前做技术验证。2. 数据准备这关S3DIS 原始点云如何变成训练用的块2.1 为什么要单独做数据预处理点云数据不是图片场景语义分割的训练入口第一步不是train_SUPnet.py而是数据生成。大多数第一次跑这个项目的人会直接跳去训练脚本结果发现S3DISDataLoader.py读的是已经切好的HDF5文件而不是原始的大房间点云。常见做法是先跑collect_indoor3d_data.py配合indoor3d_util.py把原始S3DIS数据集按房间为单位切成固定大小的块block每个块再配置对应的标签。切块的尺寸是一个需要认真对待的参数在SUPnet这类PointNet项目里每个块的尺寸通常按1m×1m设置具体取决于场景物理尺度和PointNet的感受野范围。如果切得太大块内点太多训练时采样压力大切得太小空间上下文丢失分割起不到效果。# indoor3d_util.py 中常见的块采样逻辑示意 def sample_block(point_cloud, labels, block_size1.0, stride0.5): import numpy as np coords point_cloud[:, :3] min_coords coords.min(axis0) - 1e-5 max_coords coords.max(axis0) 1e-5 blocks [] # 按步长在房间长宽方向滑动窗口 cur_coords min_coords while cur_coords[0] max_coords[0]: cur_coords[1] min_coords[1] while cur_coords[1] max_coords[1]: mask_x (coords[:, 0] cur_coords[0]) (coords[:, 0] cur_coords[0] block_size) mask_y (coords[:, 1] cur_coords[1]) (coords[:, 1] cur_coords[1] block_size) mask mask_x mask_y if np.sum(mask) 10: # 过滤掉点太少的空块 blocks.append((point_cloud[mask], labels[mask])) cur_coords[1] stride cur_coords[0] stride return blocks这段逻辑是滑动窗口的典型写法。block_size是块的空间尺寸stride是窗口移动步长。步长小于块尺寸时相邻块之间会有重叠这是为了增加训练样本的多样性但重叠太多会导致数据冗余、训练变慢。我一般会先用strideblock_size跑一次以确认流程能通再根据训练时长决定要不要改为重叠采样。上面这段代码里还做了一个过滤条件块内点数少于10的直接丢弃这是为了避免墙角和空白区域产生大量纯标签的样本影响模型的收敛方向。2.2 S3DISDataLoader跨场景数据加载器的设计边界预处理完成后数据会以HDF5形式保存S3DISDataLoader.py承担的工作是把这些块组织成批次送进网络。这里有一个重要设计是数据增广的开关它会在加载时对点云施加随机旋转、抖动以及随机丢点用于模拟真实传感器扫描噪声。这些增广操作必须放在CPU端做因为GPU要忙着算前向和反向如果也做这些矩阵变换一批数据的时间开销会翻一倍以上。# S3DISDataLoader.py 中数据增广的核心片段 def __getitem__(self, idx): # 加载 h5 块数据 points, labels self.h5_data[idx], self.h5_label[idx] # 随机打散点序防止网络学到固定顺序 order torch.randperm(points.shape[0]) points, labels points[order], labels[order] if self.augment: # 绕 z 轴随机旋转角度范围 [-pi, pi) theta torch.rand(1).item() * 2 * torch.pi - torch.pi rot_matrix torch.tensor([ [torch.cos(torch.tensor(theta)), -torch.sin(torch.tensor(theta))], [torch.sin(torch.tensor(theta)), torch.cos(torch.tensor(theta))] ]) points[:, :2] torch.mm(points[:, :2], rot_matrix) # 随机丢点模拟扫描盲区 if torch.rand(1).item() 0.5: drop torch.rand(points.shape[0]) 0.05 points, labels points[~drop], labels[~drop] return points.float(), labels.long()这里值得注意三个细节。第一点顺序的随机重排是必要的因为PointNet本身对点的顺序不敏感但如果数据集喂进去的顺序总是不变BatchNorm会学到虚假的统计信息第二旋转只做绕z轴因为室内场景的物理方向是一致的做三维全旋转会生成现实中不存在的样本第三丢点比例不是越高越好5%已经是比较激进的值超过这个值模型会把大量注意力放在补全缺失区域上。2.3 训练集 / 验证集划分为什么要留出 Area 5几乎所有S3DIS场景分割项目都会用“Area 5留出法”做验证也就是拿标准的Area 5作为验证集其余五个区域作为训练集。这个划分目的是测试跨场景泛化能力而不是简单地随机抽样一部分房间。如果你把S3DISDataLoader.py里的场景列表改成全部区域混合再随机切分训练时的mIoU会很好看但换到新场景测试时性能往往会掉十几个点。我从这个项目里学到的习惯是跑任何点云分割任务先确认数据列表是按场景/区域隔离的再关心模型结构。数据列表的维护最好单独写一个meta文件把Area名和房间号一一列出来方便排查数据泄漏问题。3. 模型部分PointNet 与 SUPnet 的协同以及两个训练脚本的分工3.1 pointnet2_utils.py 里那些底层算子对分割结果的影响压缩包里的pointnet2_utils.py是PointNet的PyTorch实现SUPnet的骨干网络基本都建在这上面。对整个分割项目来说最重要的几个操作是FPS最远点采样、Ball Query球查询和Grouping特征聚合。FPS用来在点云中选中心点Ball Query用来给每个中心点划邻域Grouping负责把邻域里的特征拼起来。这里有一个常见坑Ball Query的半径参数如果设小了邻域里点太少局部特征不稳定设大了远距离点被拉进来边界会变模糊。默认配置一般会给出两个层次第一层半径是0.2米第二层是0.4米这个量级是针对S3DIS的点密度设计的换到室外场景千万记得缩放。我自己做室外点云分割时等比例放大到0.5和1.0之后效果明显改善。FPS的最远点采样还牵扯到时间开销。当num_points设为4096时FPS的耗时还算可控但直接把原始点云喂到8192点每次FPS的开销会明显拉长训练周期。常见做法是输入层先用FPS降采样到1024或2048个中心点再在Ball Query的邻域里做特征提取这样既保留了局部细节又控制了计算量。3.2 train_FG_F1F2.py 和 train_PW-ATM.py分阶段训练的思路这个项目不只有一个训练入口train_FG_F1F2.py和train_PW-ATM.py的出现说明作者采用了分阶段或分模块训练。从文件命名上看FG可能代表Feature Generation也就是特征生成F1F2大概率对应网络里两个不同层级的特征图PW-ATM则可能是Point-Wise Attention Module也就是逐点注意力模块。常见的设计是先用train_FG_F1F2.py固定一部分参数训练中层特征再用train_PW-ATM.py训练上层的逐点注意力模块。这种做法在SUPnet这种“先分组、再分类”的结构里很常见第一阶段负责让骨干网络学会提取几何特征第二阶段再让分类头在特征上收敛。如果你只跑train_SUPnet.py一步到位训练时间和显存都会变大而且很容易在早期就陷入局部最优。每个训练脚本大概率会保存自己的checkpoint切换阶段时要格外注意是否恢复了上一阶段的权重。我在实际操作中会先看一眼每个脚本开头的pretrained_model参数确认好加载顺序再开始跑避免出现阶段之间权重断档的情况。3.3 train_SUPnet.py 的完整逻辑与常用超参数设定train_SUPnet.py是主入口它的训练循环承载了整体流程。这个脚本里能看到作者对学习率、批大小、采样点数、优化器选择的具体设置。对点云分割这种任务我建议从batch_size4开始逐点采样数设为num_points4096优化器用Adam初始学习率0.001配合每10个epoch衰减0.7的策略。损失函数方面S3DIS是类别不均衡的数据集墙壁和地板占了绝大多数点而桌子、书柜等类别点少。如果直接用交叉熵少数类基本会被压死。常见的改进是加上类别权重让少数类的误差对梯度贡献更大。# train_SUPnet.py 里类别权重计算的一种常见方式 def compute_class_weight(labels, num_classes13): import numpy as np counts np.bincount(labels.flatten(), minlengthnum_classes).astype(np.float32) total counts.sum() # 每个类权重 总数 / (类别数 * 频数)再限制上限防极端 weight total / (num_classes * (counts 1e-6)) weight np.clip(weight, 0.1, 10.0) return torch.from_numpy(weight).float()这份权重计算的逻辑与简单取倒数不同它用均值归一化让整体权重在1附近浮动低于1的类别被抑制高于1的类别被放大。clip操作是为了防止样本数极少的类别产生过大的权重否则训练初期梯度会异常。如果你发现验证集上桌子或门的IoU始终上不去优先检查这个权重数组而不是去调整网络层数因为多数情况下模型已经能分辨特征是梯度被多数类带走了。训练超参数推荐值参数名推荐值说明batch_size4显存不足时降为2num_points4096每个块统一采样的点数初始学习率0.001Adam优化器默认学习率衰减每10个epoch×0.7衰减过快要小心欠拟合类别权重compute_class_weight按频率倒数归一化这些参数不是固定死的。如果你的显卡是RTX 3090或A100batch_size可以提到8或16但学习率要相应调低否则BatchNorm统计量会剧烈抖动。反过来用老显卡时batch_size2也能训练只是每一轮epoch的采样覆盖率差一些需要适当增加总轮数来补偿。4. 复现训练流程从环境配置到跑通 test_SUPnet.py4.1 环境搭建的常见做法与版本兼容动手跑这个项目之前先确认环境。Linux系统是首选Windows下跑PyTorch点云项目虽然可行但DataLoader的多进程在Windows上经常出问题尤其torch.utils.data.DataLoader的num_workers在Windows上容易被误用导致训练卡死或内存泄漏。我一般在Windows上直接把num_workers设为0先保证能跑通再换到Linux做正式训练。Python版本选3.8或3.9比较稳PyTorch用1.8以上都行注意CUDA必须和PyTorch版本匹配否则会报undefined symbol之类的底层错误。这个项目本身没有引入特别冷门的库transforms、h5py、numpy、scipy这些装好就够用。动手之前建议建立一个干净的conda环境用conda create -n supnet python3.8创建再按CUDA版本选择对应的PyTorch安装命令。不要图省事用CPU版本跑GPU训练那个报错非常隐晦经常是在某个算子才暴露排查起来费时费力。4.2 数据预处理执行顺序与参数建议数据预处理的执行顺序在README里通常写得比较简略但实际流程应当是先下载S3DIS原始数据然后跑collect_indoor3d_data.py把每个房间的txt点云收集起来再用indoor3d_util.py进行切块和HDF5转换。注意这两个脚本的执行时间非常长一张房间场景可能要十几分钟整个Area集跑完是按小时计的。如果只是为了验证代码能跑通建议先只处理单个Area的一小段数据代码里通常有block参数可以做小范围测试。处理阶段主要脚本产物格式耗时参考原始数据收集collect_indoor3d_data.py每房间一个txt聚合点云每房间几分钟切块采样indoor3d_util.pyHDF5块文件每个Area数小时训练加载S3DISDataLoader.py内存中的tensor实时数据切块的参数会在脚本开头给出常见的块大小是1.0m×1.0m步长也是1.0m。步长和块大小相等意味着无重叠切块训练样本数少但信息冗余低。如果你显存比较宽裕想提高样本量可以改成stride0.5让相邻块有重叠但要注意重叠区块在训练和验证之间不能交叉否则会造成数据泄漏。4.3 训练命令与日志解读该看哪些指标训练阶段执行train_SUPnet.py脚本启动后会输出每个epoch的loss、mIoU、OA这几个指标。loss是总损失mIoU是平均交并比OA是整体准确率。对于场景语义分割来说mIoU比OA更有参考价值因为OA会被墙壁这类大类带偏哪怕模型把所有点都预测成墙壁OA也可能有50%以上而mIoU会把每类的表现拉平。训练过程中如果loss下降但mIoU不涨说明出现类别混淆此时优先调整类别权重和采样点数量而不是无脑加训练轮数。测试阶段执行test_SUPnet.py它会加载权重对验证集逐块预测然后拼回完整场景输出成可视化结果图。结果图文件名对应的是生成好的PNG图也就是压缩包里那个结果.png通常是由各块的预测标签拼接后着色的。检查结果图时不要只看整体颜色要放大墙面与物体交界处观察是否出现细条状误分类。提示跑测试时一定要确认加载权重的checkpoint路径与模型结构完全一致。SUPnet这类项目经常会因为换了num_classes或输入维度导致加载失败错误信息通常是size mismatch这时不要急着改网络先检查模型保存时的配置和当前配置是否对齐。4.4 训练时间预算与参数量预估关于训练时间S3DIS的Area 1-4加上Area 6做训练集单卡跑20个epoch在常见的GTX 1080Ti或RTX 2080Ti级别显卡上大约需要半天到一天。如果只有CPU我建议放弃完整训练只做数据流调试和模型前向验证因为一次epoch在CPU上可能跑几个小时。模型参数量方面PointNet骨干加分类头大约在几百万参数量级别不大但训练时中间特征图占用的显存很可观batch_size和num_points对显存的影响最大。显存不足时优先减小batch_size其次减小num_points最后才考虑减小网络宽度。5. 避坑指南点云语义分割最常见的五个坑与解法5.1 显存溢出明明显存很大却仍然OOM现象训练刚开始不久就弹出CUDA out of memory或者训练到中途被系统kill。原因点云分割的显存占用不是简单由batch_size决定的而是batch_size × num_points × 特征维度。很多人在2D图像分割时习惯性把batch_size设成16或32放到点云项目里直接爆显存。解决把batch_size降到2或4num_points从8192降到4096。如果还不够检查模型里是否有不必要的中间变量被保存比如在forward里打印特征图这类调试代码它们会让自动求导图多保存一份副本显存占用瞬间上涨。5.2 换版本后DataLoader报错num_workers在Windows上的血泪经验现象在Windows上运行训练脚本num_workers大于0时程序启动后立刻报错或卡死。原因PyTorch在Windows上的多进程数据加载策略与Linux不同子进程的串行化要求会让主进程等待尤其在Jupyter或IDE里运行时还会和Python解释器冲突。解决把num_workers直接设为0或者放在if __name__ __main__:保护块里再启动。如果确实需要多进程加速建议换到Linux环境或者在训练脚本外层包一层torch.multiprocessing.spawn。5.3 类别不均衡导致mIoU低位徘徊现象训练多个epoch后总体accuracy不错但mIoU一直不上涨尤其少数类如桌子、书柜几乎预测为0。原因S3DIS数据集中墙壁和地板占比太高交叉熵损失完全被多数类主导少数类梯度被淹没。解决做完类别的频率统计按频率倒数的平方根设置类别权重。频率太高的类权重压制在0.5以下频率极低的类权重也不要超过5防止训练震荡。同时检查数据加载器的drop_last设置如果最后一个batch过小少数类样本可能根本没被送进网络。5.4 换场景测试时mIoU骤降过拟合明显现象在训练集上mIoU到达70%但测试集换到新的Area后mIoU直接掉到40%以下。原因没有做跨场景划分数据列表里混入了测试场景的块或者划分时把同一房间的块同时放进了训练集和验证集造成数据泄漏。解决严格按Area划分训练列表只包含Area 1-4和6验证列表只包含Area 5。同时检查S3DISDataLoader的索引逻辑避免HDF5文件按房间顺序排列导致batch内包含同一个房间的连续块。如果发现验证集与训练集的mIoU差距过大先怀疑数据泄漏再怀疑模型过拟合。5.5 预处理脚本中途卡住为什么数据生成总是在同一个房间停下来现象跑collect_indoor3d_data.py时程序经常在某一个房间的某一段数据上停止输出CPU占用率低但不继续。原因原始S3DIS房间数据里偶尔有损坏的或点数极少的块滑动窗口在空区域反复移动但点数不够形成样本陷入死循环。解决在代码的while循环里加一个最大迭代保护如果连续的滑动步数超过阈值且没有找到任何有效点就强制把窗口移到下个区域。同时在sample_block里加try...except遇到空数据时就跳过并打印警告而不是让脚本挂在那里。这类问题在数据处理阶段非常典型不打断点很难发现。6. 进阶从剪枝到可视化让测试结果真正可用于分析跑通这个项目之后我强烈建议你写一段快速的逐点可视化检查脚本。点云分割的验证不同于2D图像光看mIoU数值无法定位模型在空间上的失误。常见做法是将Area 5的测试集预测结果逐块保存然后拼接回完整场景生成带标签着色的ply点云在MeshLab或CloudCompare里旋转观察。这个操作能直接暴露出模型的系统性错误比如墙角附近总是混作一团或者天花板与墙面边界处出现条带状误分类。# 快速可视化预测结果把 Block 拼接成完整房间并保存为 ply import numpy as np def save_scene_as_ply(coords_all, label_pred, label_gt, out_path): with open(out_path, w) as f: f.write(ply\nformat ascii 1.0\nelement vertex %d\n % coords_all.shape[0]) f.write(property float x\nproperty float y\nproperty float z\n) f.write(property int pred\nproperty int gt\nend_header\n) for i in range(coords_all.shape[0]): x, y, z coords_all[i] f.write(%f %f %f %d %d\n % (x, y, z, label_pred[i], label_gt[i]))这段代码的重点是把预测标签和真实标签写到同一个ply文件里两个属性列并排。导入CloudCompare之后用pred列着色再看gt列可以快速对比空间分布差异。从这以后我每次跑完分割项目都强制走一遍可视化流程先看预测和真实标签是否在类别边缘错位再统计错分类别的共生矩阵最后才把mIoU数字写进报告。这样的顺序能避免“指标好看但实际效果一塌糊涂”的尴尬。在实际调试中我还习惯把预测置信度低于0.6的点单独标出来这样可以直观看到模型的不确定区域集中在哪些物体表面——如果连墙面都出现大面积低置信度基本可以判断是数据预处理环节出了问题而不是模型结构的问题。希望这个习惯也能帮到你。本文还有配套的精品资源点击获取
返回列表