mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4mobile wallpaper 5mobile wallpaper 6
1068 字
3 分钟
PointNet++之S3DIS 语义分割训练
2026-05-06

前言#

从下载 S3DIS Aligned Version 到成功启动 train_semseg.py,中间隔着三层深渊:WSL2 路径层级混乱预处理脚本与训练脚本的路径错位、以及NumPy 版本兼容性断层。本文把全流程的坑和指令一次性整理清楚。


1. 数据部署:WSL2 软链接的正确姿势#

1.1 为什么不能用 WSL2 内部 /home/ 存大数据#

WSL2 的 ext4.vhdx 虚拟磁盘是只增不减的饕餮。你把 10GB 数据集解压到 /home/,磁盘就膨胀 10GB;删掉数据,.vhdx 体积不会变小,必须用 diskpart compact 才能回收。更致命的是,C 盘爆满会直接触发 WSL2 无法启动。

铁律:GB 级数据集放在 Windows D 盘,WSL2 通过 /mnt/d/ 访问。

1.2 软链接建错位置的连环坑#

data_utils/ 子目录里执行:

ln -s /mnt/d/... data/s3dis

这会在 data_utils/data/s3dis 创建软链接,而项目根目录的 data/s3dis 根本不存在。后续脚本从项目根目录找 data/s3dis,直接 FileNotFoundError

正确做法:必须在项目根目录执行:

cd ~/projects/pointcloud/Pointnet_Pointnet2_pytorch
mkdir -p data
ln -s /mnt/d/Download/S3DIS/Stanford3dDataset_v1.2_Aligned_Version data/s3dis

1.3 套娃软链接陷阱#

预处理脚本 collect_s3dis_aligned.py.npy 输出到了 data/s3dis/stanford_indoor3d/(D 盘)。训练脚本 train_semseg.py 却期望 data/stanford_indoor3d/

错误操作:在项目根目录再建一个指向软链接的软链接:

ln -s data/s3dis/stanford_indoor3d data/stanford_indoor3d # ❌ 套娃,最终指向空

正确操作:直接指向 D 盘的真实产物路径:

ln -s /mnt/d/Download/S3DIS/Stanford3dDataset_v1.2_Aligned_Version/stanford_indoor3d data/stanford_indoor3d

验证命令

ls data/stanford_indoor3d/ | wc -l
# 必须输出 272(S3DIS 总房间数)

2. 预处理:重写脚本适配 Aligned Version#

2.1 原版脚本为什么全军覆没#

官方 collect_indoor3d_data.py 依赖 meta/anno_paths.txt 硬编码路径,且 try-except 吞掉了具体错误,最终只报:

ValueError: need at least one array to concatenate

根本原因是 Aligned VersionAnnotations/ 目录结构虽然存在,但原版脚本的路径拼接或类别过滤逻辑导致 points_list 为空。

2.2 重写脚本的核心策略#

保存为 data_utils/collect_s3dis_aligned.py

import os
import numpy as np
CLASS_NAMES = [
'ceiling', 'floor', 'wall', 'beam', 'column', 'window', 'door',
'table', 'chair', 'sofa', 'bookcase', 'board', 'clutter'
]
def get_class_id(filename):
prefix = filename.split('_')[0].lower()
if prefix in CLASS_NAMES:
return CLASS_NAMES.index(prefix)
return 12
def collect_from_annotations(room_path):
anno_dir = os.path.join(room_path, 'Annotations')
if not os.path.isdir(anno_dir):
return None, "Annotations 目录不存在"
txt_files = sorted([f for f in os.listdir(anno_dir) if f.endswith('.txt')])
if len(txt_files) == 0:
return None, "Annotations 下无 .txt 文件"
points_list = []
for f in txt_files:
fpath = os.path.join(anno_dir, f)
try:
pts = np.loadtxt(fpath)
if pts.ndim == 1:
pts = pts.reshape(1, -1)
cls_id = get_class_id(f)
labels = np.full((pts.shape[0], 1), cls_id, dtype=np.float32)
pts_labeled = np.concatenate([pts, labels], axis=1)
points_list.append(pts_labeled)
except Exception as e:
print(f" 跳过 {f}: {e}")
if len(points_list) == 0:
return None, "所有文件读取失败"
return np.concatenate(points_list, axis=0), f"合并 {len(points_list)} 个物体"
def main():
root = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
'data/s3dis'
)
output = os.path.join(root, 'stanford_indoor3d')
os.makedirs(output, exist_ok=True)
print(f"扫描目录: {root}\n")
success = 0
failed = []
for area in sorted(os.listdir(root)):
if not area.startswith('Area_'):
continue
area_path = os.path.join(root, area)
rooms = sorted([d for d in os.listdir(area_path)
if os.path.isdir(os.path.join(area_path, d))])
for room in rooms:
room_path = os.path.join(area_path, room)
out_name = f"{area}_{room}.npy"
out_path = os.path.join(output, out_name)
data, info = collect_from_annotations(room_path)
if data is None:
print(f"❌ {area}/{room}: {info}")
failed.append(f"{area}/{room}")
continue
np.save(out_path, data.astype(np.float32))
print(f"✅ {area}/{room} | {info} | shape: {data.shape}")
success += 1
print(f"\n{'='*60}")
print(f"完成: 成功 {success} 个, 失败 {len(failed)} 个")
print(f"输出: {output} ({len(os.listdir(output))} 个 .npy 文件)")
if __name__ == '__main__':
main()

执行

cd ~/projects/pointcloud/Pointnet_Pointnet2_pytorch/data_utils
python3 collect_s3dis_aligned.py

预期输出

完成: 成功 272 个, 失败 0 个
输出: .../data/s3dis/stanford_indoor3d (272 个 .npy 文件)

3. 训练指令与参数详解#

3.1 完整启动命令#

cd ~/projects/pointcloud/Pointnet_Pointnet2_pytorch
conda activate pointnet
python3 train_semseg.py \
--model pointnet2_sem_seg \
--log_dir pointnet2_sem_seg \
--batch_size 16 \
--epoch 32 \
--test_area 5

3.2 参数说明#

参数含义
--modelpointnet2_sem_seg语义分割模型(Encoder-Decoder + FP 上采样)
--log_dirpointnet2_sem_seg日志与权重保存目录
--batch_size163090 24GB 安全值;OOM 时降为 8
--epoch32语义分割收敛快,32 轮足够
--test_area5S3DIS 6 个区域,留 Area_5 做测试,其余训练

3.3 训练前必做检查#

# 1. 确认数据路径正确
ls data/stanford_indoor3d/ | wc -l
# 输出: 272
# 2. 顺手修语法警告(S3DISDataLoader.py 的 Python 3.8+ 兼容性问题)
sed -i 's/is -1/== -1/g' data_utils/S3DISDataLoader.py
sed -i 's/is not -1/!= -1/g' data_utils/S3DISDataLoader.py
# 3. 确认 GPU 可用
nvidia-smi

4. NumPy 兼容性陷阱:np.float 的死刑#

4.1 现象#

Epoch 1 训练成功(loss 0.908,accuracy 73.9%),评估阶段直接崩溃:

AttributeError: module 'numpy' has no attribute 'float'.
`np.float` was a deprecated alias for the builtin `float`.

4.2 根因#

NumPy 版本np.float 状态
< 1.20正常可用
1.20 ~ 1.23废弃,报 Warning
≥ 1.24直接移除,报 AttributeError

你的 conda 环境装的是 NumPy 2.x,而仓库代码写于 2019 年。

4.3 修复#

cd ~/projects/pointcloud/Pointnet_Pointnet2_pytorch
sed -i 's/np\.float)/float)/g' train_semseg.py
sed -i 's/np\.float,/float,/g' train_semseg.py
sed -i 's/dtype=np\.float/dtype=float/g' train_semseg.py
# 验证残留
grep -n "np\.float" train_semseg.py
# 预期:只剩 np.float32 / np.float64,没有裸 np.float

5. 分割指标迷思:accuracy vs mIoU#

5.1 为什么分割也有 accuracy#

语义分割本质是逐点分类,所以当然可以算:

accuracy = 预测正确的点数 / 总点数

5.2 为什么 accuracy 不可靠#

S3DIS 中墙面、地面、天花板占 70% 以上,椅子、桌子只占 5%。如果模型无脑全猜”地面”,accuracy 也能到 70%,但完全没价值。

5.3 金标准:mIoU#

IoU_类别 = 交集 / 并集
mIoU = mean(IoU_0, IoU_1, ..., IoU_12)
指标公平性用途
accuracy❌ 偏向大类快速观察收敛,不做最终评价
mIoU✅ 每类平等论文、比赛、工程验收的唯一标准

S3DIS 基线:PointNet++ 训练 32 epoch 后,mIoU 应达到 45~55%


6. 训练监控与预期#

6.1 时长预估(3090 + WSL2)#

阶段耗时
单 epoch 训练~30 分钟
单 epoch 评估~10 分钟
32 epoch 总计1620 小时

建议:夜间挂机,避免中断。

6.2 实时监控#

另开终端:

watch -n 2 nvidia-smi

关注:

  • GPU-Util:应接近 100%(低于 50% 说明 DataLoader IO 瓶颈)
  • 显存占用:8~12GB(接近 24GB 时有 OOM 风险)

6.3 断点续训说明#

yanx27 仓库不支持 resume。如果 epoch 15 断电,只能从头开始。保持电源稳定,不要同时跑其他 GPU 程序。


7. 完整流程速查表#

步骤命令验证
1. 建软链接ln -s /mnt/d/Download/S3DIS/Stanford3dDataset_v1.2_Aligned_Version data/s3disls data/s3dis/Area_1/
2. 预处理python3 data_utils/collect_s3dis_aligned.py输出 272 个 .npy 文件
3. 建训练软链接ln -s /mnt/d/Download/S3DIS/.../stanford_indoor3d data/stanford_indoor3dls data/stanford_indoor3d/ | wc -l272
4. 修语法警告sed -i 's/is -1/== -1/g' data_utils/S3DISDataLoader.py
5. 修 NumPysed -i 's/np\.float)/float)/g' train_semseg.pygrep "np\.float" train_semseg.py 无裸 np.float
6. 开跑python3 train_semseg.py --model pointnet2_sem_seg --log_dir pointnet2_sem_seg --batch_size 16 --epoch 32 --test_area 5看到 Totally 47623 samples in train set.

至此,从数据部署、预处理、路径修复到训练启动的全链路打通。修复 np.float 后重新执行训练命令,静待 32 个 epoch 后验收 mIoU。

分享

如果这篇文章对你有帮助,欢迎分享给更多人!

PointNet++之S3DIS 语义分割训练
https://fredsblog-2dc.pages.dev/posts/note-pointnet-senseg-train/
作者
Fredzhe
发布于
2026-05-06
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时