news 2026/4/25 18:01:21

pointnet 实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
pointnet 实战

依赖库对 Python 版本的要求

依赖支持的 Python 版本
PyTorch (最新稳定版)3.9 ~ 3.12
tqdm3.7+
plyfile3.7+

推荐选择:Python 3.10

  • PyTorch 对 3.10 支持最成熟,预编译wheel包最完整
  • 3.11/3.12 较新,部分旧版第三方库可能有兼容问题
  • 3.9 已经偏老,未来支持会逐渐减少
  • 3.10 是目前深度学习项目事实上的"最稳定选择"

创建虚拟环境的命令

数据集 shapenetcore_partanno_segmentation_benchmark_v0/ 详解


shapenet数据集来自ShapeNet,斯坦福大学发布的大规模三维形状数据库。

core指的是ShapeNetCore,是 ShapeNet 的核心子集,包含 55 个常见物体类别(飞机、椅子、桌子等),是 ShapeNet 中最常用的部分。


partanno=part annotation(零件标注) 表示这个数据集不只是三维模型,而是对每个模型的每个零件都做了标注

比如飞机被标注成:机身、机翼、发动机、尾翼四个部件。


segmentation表示这个数据集是专门为分割任务准备的,即判断每个点属于哪个零件。


benchmark表示这是一个基准数据集,专门用来评测和对比不同算法的性能。


v0=version 0,第 0 个版本(初始版本)。


shapenetcore_partanno_segmentation_benchmark_v0/
├── synsetoffset2category.txt # 类别ID ↔ 名称映射
├── train_test_split/
│ ├── shuffled_train_file_list.json
│ ├── shuffled_val_file_list.json
│ └── shuffled_test_file_list.json
├── 02691156/ ← Airplane (2690 个模型)
├── 03001627/ ← Chair (3746 个模型)
├── 04379243/ ← Table (5266 个模型)
└── ...共 16 个类别文件夹


每个类别文件夹内结构一致:

02691156/
├── points/ ← 点云文件 (.pts)
├── points_label/ ← 语义标签文件 (.seg)
└── seg_img/ ← 预渲染可视化图片 (.png)

核心文件格式

.pts 文件 — 纯文本,每行一个点的 XYZ 坐标:
0.25047 -0.06912 -0.02763
0.16421 -0.04005 -0.03361
...
每个模型约 2488 个点(均匀采样自原始 ShapeNetCore 网格)。

.seg 文件 — 与 .pts 逐行对应,每行一个整数(part 标签,从 1 开始):
1 ← 第1个点属于 part 1(机身)
1
3 ← 第3个点属于 part 3(机翼)
3
...
Airplane 有 4 个 part(机身/机翼/机尾/发动机),16 个类别共 50 种 part。

.png 文件 — 已经预先渲染好的彩色分割图(最直接的可视化方式)。

---
各类别模型数量

┌──────────┬──────────┬──────┐
│ 类别 │ ID │ 数量 │
├──────────┼──────────┼──────┤
│ Table │ 04379243 │ 5266 │
├──────────┼──────────┼──────┤
│ Chair │ 03001627 │ 3746 │
├──────────┼──────────┼──────┤
│ Airplane │ 02691156 │ 2690 │
├──────────┼──────────┼──────┤
│ Car │ 02958343 │ 1824 │
├──────────┼──────────┼──────┤
│ Lamp │ 03636649 │ 1546 │
├──────────┼──────────┼──────┤
│ Guitar │ 03467517 │ 787 │
├──────────┼──────────┼──────┤
│ ... │ ... │ ... │
├──────────┼──────────┼──────┤
│ Cap │ 02954340 │ 55 │
└──────────┴──────────┴──────┘

---

1.用的是conda(推荐):

conda create -n pointnet python=3.10 conda activate pointnet

2.pip install -e .

场景 A:在项目里训练

你在:

/root/pointnet.pytorch

运行:python train.py

一般不需要setup.py也可能能跑。


场景 B:在 notebook 里调这个项目

比如你在:

/root/notebooks

打开一个 notebook,想写:

from pointnet.model import PointNetCls

这就属于“在别的地方”。

如果没安装成包,通常导不进来。
如果执行过:pip install -e .

就能导入。

“在别的地方”就是:

你不在项目目录里运行代码,但还想 import 这个项目。

最典型例子:

  • 在另一个项目里 import 它

  • 在 Python 终端里 import 它

  • 在 Jupyter notebook 里 import 它

  • 在任何别的路径下运行脚本时 import 它

setup.py的作用

from setuptools import setup

setup(
name="pointnet",
version="0.0.1",
packages=["pointnet"],
install_requires=["torch", "numpy"]
)

  • name:项目名
  • version:版本号
  • packages:要安装哪些包
  • install_requires:依赖哪些第三方库

3.python train_classification.py --dataset ../shapenetcore_partanno_segmentation_benchmark_v0 --nepoch 25 --dataset_type shapenet --workers 0(windows带 workers 0)

完整模型:


输入: [B, 3, N] B=32批次, 3=xyz坐标, N=2500个点

---
第一阶段:STN3d 输入对齐

class STN3d(nn.Module):
def forward(self, x): # x: [B, 3, N]
x = F.relu(self.bn1(self.conv1(x))) # [B, 3, N] → [B, 64, N]
x = F.relu(self.bn2(self.conv2(x))) # [B, 64, N] → [B, 128, N]
x = F.relu(self.bn3(self.conv3(x))) # [B, 128,N] → [B, 1024,N]
x = torch.max(x, 2, keepdim=True)[0] # [B, 1024,N] → [B, 1024,1] MaxPool
x = x.view(-1, 1024) # [B, 1024]

x = F.relu(self.bn4(self.fc1(x))) # [B, 1024] → [B, 512]
x = F.relu(self.bn5(self.fc2(x))) # [B, 512] → [B, 256]
x = self.fc3(x) # [B, 256] → [B, 9]

# 加上单位矩阵初始化,保证初始时不做变换
iden = torch.eye(3).flatten().repeat(B, 1) # [B, 9]
x = (x + iden).view(-1, 3, 3) # [B, 3, 3] ← 输出对齐矩阵
return x

---
第二阶段:PointNetfeat 特征提取

class PointNetfeat(nn.Module):
def forward(self, x): # x: [B, 3, N]

# ── 用STN3d预测变换矩阵,对点云做对齐 ──
trans = self.stn(x) # [B, 3, 3]
x = x.transpose(2, 1) # [B, N, 3]
x = torch.bmm(x, trans) # [B, N, 3] × [B, 3, 3] = [B, N, 3] 批量矩阵乘法
x = x.transpose(2, 1) # [B, 3, N]

# ── 第一层逐点特征提取 ──
x = F.relu(self.bn1(self.conv1(x))) # [B, 3, N] → [B, 64, N]
pointfeat = x # 保存64维逐点特征,分割任务要用

# ── 可选:STNkd 对64维特征空间再做一次对齐 ──
if self.feature_transform:
trans_feat = self.fstn(x) # [B, 64, 64]
x = x.transpose(2, 1) # [B, N, 64]
x = torch.bmm(x, trans_feat) # [B, N, 64]
x = x.transpose(2, 1) # [B, 64, N]

# ── 继续升维 ──
x = F.relu(self.bn2(self.conv2(x))) # [B, 64, N] → [B, 128, N]
x = self.bn3(self.conv3(x)) # [B, 128, N] → [B, 1024, N]

# ── 全局最大池化:N个点 → 1个全局向量 ──
x = torch.max(x, 2, keepdim=True)[0] # [B, 1024, N] → [B, 1024, 1]
x = x.view(-1, 1024) # [B, 1024]

if self.global_feat:
return x, trans, trans_feat # 分类任务:只返回全局特征
else:
# 分割任务:把全局特征广播回每个点,拼接逐点特征
x = x.view(-1, 1024, 1).repeat(1, 1, N) # [B, 1024, N]
return torch.cat([x, pointfeat], 1), ... # [B, 1088, N]

---
第三阶段:PointNetCls 分类头

class PointNetCls(nn.Module):
def forward(self, x): # x: [B, 3, N]
x, trans, trans_feat = self.feat(x) # x: [B, 1024]

x = F.relu(self.bn1(self.fc1(x))) # [B, 1024] → [B, 512]
x = F.relu(self.bn2(self.dropout(self.fc2(x)))) # [B, 512] → [B, 256]
x = self.fc3(x) # [B, 256] → [B, 16]
return F.log_softmax(x, dim=1), trans, trans_feat # [B, 16]

方法公式结果范围中心在哪里适合场景
Min-Max 归一化(x - min) / (max - min)[0, 1]通常在 0.5 附近图像像素、普通数值特征
标准化(x - mean) / std不固定均值为 0普通机器学习特征
点云单位球归一化(p - mean) / max_distance半径 ≤ 1 的球原点[0,0,0]点云坐标
数据处理流程

root 目录

读取 synsetoffset2category.txt

得到 self.cat 类别映射
类别名 -> 类别编号
例如:
Chair -> 03001627
Airplane -> 02691156

如果指定 class_choice,例如 ['Chair']
就只保留 Chair 这一类

得到 self.id2cat 反向类别映射
类别编号 -> 类别名

例如:
03001627 -> Chair
02691156 -> Airplane

读取 train_test_split/shuffled_train_file_list.json

得到当前 split 里面有哪些样本

例如:
shape_data/03001627/abc123
shape_data/03001627/def456

从每一条 file 里拆出:
_ , category, uuid = file.split('/')
其中:
category = 03001627
uuid = abc123

用 category 判断这个样本是否属于当前需要的类别
if category in self.cat.values()

用 self.id2cat[category] 把类别编号转回类别名
例如:
self.id2cat['03001627'] = 'Chair'

整理到 self.meta
每个类别下面保存自己的点云路径和标签路径

例如:
self.meta = {
'Chair': [
(
root/03001627/points/abc123.pts,
root/03001627/points_label/abc123.seg
),
(
root/03001627/points/def456.pts,
root/03001627/points_label/def456.seg
)
]
}

把 self.meta 拉平成 self.datapath
例如:
self.datapath = [
(
'Chair',
root/03001627/points/abc123.pts,
root/03001627/points_label/abc123.seg
),
(
'Chair',
root/03001627/points/def456.pts,
root/03001627/points_label/def456.seg
)
]

__getitem__(index)

通过 self.datapath[index] 找到一个样本

读取 .pts 点云坐标,shape = (N, 3)
读取 .seg 点级标签,shape = (N,)

随机采样 choice,shape = (2500,)

point_set = point_set[choice, :],shape = (2500, 3)
seg = seg[choice],shape = (2500,)

点云中心化,shape 不变

点云归一化到单位球,shape 不变

随机绕 y 轴旋转,shape 不变

随机 jitter 抖动,shape 不变

转 torch tensor

返回 point_set, seg

DataLoader 拼 batch

points: (B, 2500, 3)
seg: (B, 2500)

输入模型

具体数值实例

原始 .pts 点云
point_set: (6, 3)

点0 [1, 0, 0]
点1 [2, 0, 0]
点2 [1, 1, 0]
点3 [1, 0, 1]
点4 [2, 1, 1]
点5 [0, 1, 1]

原始 .seg 标签
seg: (6,)

点0 -> 10
点1 -> 10
点2 -> 20
点3 -> 30
点4 -> 30
点5 -> 20

随机采样
choice = [3, 0, 4, 3]

采样后 point_set: (4, 3)

新点0 = 原点3 = [1, 0, 1]
新点1 = 原点0 = [1, 0, 0]
新点2 = 原点4 = [2, 1, 1]
新点3 = 原点3 = [1, 0, 1]

采样后 seg: (4,)

新点0 -> 30
新点1 -> 10
新点2 -> 30
新点3 -> 30

中心化
减去 mean = [1.25, 0.25, 0.75]

point_set: (4, 3)

[-0.25, -0.25, 0.25]
[-0.25, -0.25, -0.75]
[ 0.75, 0.75, 0.25]
[-0.25, -0.25, 0.25]

归一化
除以最大距离 dist ≈ 1.090

point_set: (4, 3)

[-0.229, -0.229, 0.229]
[-0.229, -0.229, -0.688]
[ 0.688, 0.688, 0.229]
[-0.229, -0.229, 0.229]

随机绕 y 轴旋转
假设 theta = 90°

point_set: (4, 3)

[ 0.229, -0.229, 0.229]
[-0.688, -0.229, 0.229]
[ 0.229, 0.688, -0.688]
[ 0.229, -0.229, 0.229]

随机 jitter 抖动

point_set: (4, 3)

[ 0.239, -0.229, 0.219]
[-0.688, -0.209, 0.229]
[ 0.219, 0.688, -0.678]
[ 0.229, -0.249, 0.229]

seg 不变: (4,)
[30, 10, 30, 30]

转 torch tensor

point_set: torch.FloatTensor, shape = (4, 3)
seg: torch.LongTensor, shape = (4,)

DataLoader, batch_size = 2

points: (2, 4, 3)
seg: (2, 4)

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/25 17:57:04

终极指南:如何用Python快速解锁QQ群聊天记录的隐藏价值

终极指南:如何用Python快速解锁QQ群聊天记录的隐藏价值 【免费下载链接】chatLog QQ群聊天记录分析 项目地址: https://gitcode.com/gh_mirrors/ch/chatLog 你是否曾好奇QQ群里的活跃模式?哪些话题最受欢迎?谁才是真正的"水群之王…

作者头像 李华
网站建设 2026/4/25 17:48:20

3步搞定B站视频下载难题:BilibiliDown高效下载实战指南

3步搞定B站视频下载难题:BilibiliDown高效下载实战指南 【免费下载链接】BilibiliDown (GUI-多平台支持) B站 哔哩哔哩 视频下载器。支持稍后再看、收藏夹、UP主视频批量下载|Bilibili Video Downloader 😳 项目地址: https://gitcode.com/gh_mirrors/…

作者头像 李华
网站建设 2026/4/25 17:41:06

仓储小店囤货资金占用成本统计,优化库存周转实操。

一、实际应用场景描述某社区仓储型小店(如便利店、烟酒茶店、小型五金店):- 商品种类:50–300 SKU- 采购方式:批量囤货- 销售节奏:波动较大- 资金特点:- 大量资金沉淀在库存中- 部分商品长期滞销…

作者头像 李华
网站建设 2026/4/25 17:36:07

别再只打包.exe了!Electron Builder一键生成安装包,让你的Vue应用更专业

从Vue到专业桌面应用:Electron Builder全流程进阶指南 当你完成了一个精美的Vue应用,接下来面临的挑战是如何让它走出浏览器,成为用户桌面上的一款专业软件。传统的electron-packager虽然简单,但面对真正的产品化需求时往往力不从…

作者头像 李华
网站建设 2026/4/25 17:31:21

3分钟极速备份:用GetQzonehistory永久保存你的QQ空间青春记忆

3分钟极速备份:用GetQzonehistory永久保存你的QQ空间青春记忆 【免费下载链接】GetQzonehistory 获取QQ空间发布的历史说说 项目地址: https://gitcode.com/GitHub_Trending/ge/GetQzonehistory 还记得那些年在QQ空间留下的青春印记吗?从青涩的学…

作者头像 李华