PointPainting详解与复现:图像语义如何增强点云3D检测
2026/9/16 3:12:41 网站建设 项目流程

1. PointPainting 是什么,为什么值得复现

PointPainting 是我个人很早就想复现的一篇多模态融合经典工作,核心思路一句话就能讲清:先用 2D 图像分割网络对相机图像做语义分割,再把每个激光雷达点投影到图像上,让这个点“继承”对应像素的语义分数,最后把扩展后的点云送进 3D 检测网络。整个过程像一个“涂色”操作,所以叫 PointPainting。

这个工作解决的问题非常实际:纯激光雷达点云在远距离、稀疏场景下目标检测效果不好,尤其是小目标和被遮挡目标,往往只有几个点落在物体上,几何特征严重不足。图像有不同的特点,像素密度高,语义信息丰富,但缺少精确的深度。两个传感器互补性强,怎么把它们有效融合就成了一道必答题。PointPainting 的思路是把图像语义渲染到点云上,不改变检测网络结构,只给点云多送几个特征通道,实现简单、效果明显,这也是它在多模态感知发展史中占有一席之地的原因。

这篇文章适合谁看?如果你在跑 3D 目标检测相关实验,或者研究多模态融合方向,想找一个容易落地、不需要折腾复杂网络结构的方法,那 PointPainting 是一个不错的切入点。我自己先在 nuScenes 数据集上完整走了一遍流程,又把图像分割端换成不同网络对比了效果,中间踩了不少坑。这里把整套复现思路、代码结构、参数细节和排查经验整理出来,希望能帮你少走弯路。

2. 方案选型与整体设计思路

2.1 为什么选择“点云涂画”这条融合路线

多模态融合按融合位置大致分三类:早期融合、中期融合、后期融合。早期融合是把图像和点云在输入层对齐,最常见的就是把点云投影到图像平面拼接特征;后期融合是分别跑 2D 检测和 3D 检测,再用后处理算法合并结果;而 PointPainting 属于一种很特殊的中期融合。

它特殊在“以点云为主、以图像为语义先验”。分割网络把相机图像转成语义分数图,然后每一个激光雷达点在投影后对应的图像位置取出语义分数,拼接在原有特征后面。这样做的最大好处是:3D 检测网络完全不用改,你只需要在数据预处理阶段把点云的特征维度加长就行。相比端到端的跨模态注意力融合方案,比如 MVP 或 BEVFusion,PointPainting 的工程成本低很多,一个研究组两三天就能把流程跑通。

我复现时第一版直接用 OpenPCDet 框架,换上新特征维度后微调训练,整体改动量非常小。这也说明这个方案在工程上非常“抗造”,适合做 baseline 或者作为更复杂工作的起点。

2.2 核心流程拆解:分割、投影、拼接、检测

整个流程可以拆成四个环节:

  1. 图像语义分割:输入 RGB 图像,输出每个像素的语义概率向量或 one-hot 类别标签,类别一般选车辆、行人、自行车等与 3D 检测共享的类别。
  2. 点云投影:根据激光雷达坐标系到相机坐标系的变换矩阵,以及相机内参,把每个 3D 点投影到图像像素坐标。
  3. 语义特征拼接:在投影位置取出分割分数,过滤掉出界、负深度等无效点,其余点拼接语义特征形成新的点云。
  4. 3D 检测:把增强后的点云送入任意 3D 检测框架,如 PointPillars、VoxelNet、CenterPoint 等,进行训练和评估。

原论文里一个很关键的设计细节是:拼接的不是 hard label,也就是 one-hot 类别,而是完整的 soft 语义分数向量,通常是分割网络 softmax 之后的输出。这样保留了不同类别间的置信度差异,信息量比硬标签大。比如一个像素可能 60% 概率是车辆、30% 概率是卡车,直接用 hard label 会丢掉这部分分布信息,而 soft score 能把这种“不确定但接近”的状态传给下游检测器。

2.3 对比其他融合方案的优劣势

我实际对比过几种方案:

  • 早期特征拼接:把图像特征直接 concat 到点云特征中,在输入层做对齐。这种方式要求两个模态的特征在空间上严格对应,一旦标定外参有误差,对性能影响很大。
  • 目标级后期融合:2D 检测输出目标框和类别,与 3D 检测结果做关联融合。实现简单,但无法利用像素级的语义细节,小目标召回率提升有限。
  • PointPainting:像素语义先被“烘焙”到每个点上,检测器看到的物体天然带有语义类别信息。相比早期拼接,它对标定误差的敏感度稍低,因为只在投影取特征这一步依赖外参,不会把整张图像特征强对齐到点云。

当然 PointPainting 也有短板:它依赖 2D 分割质量,如果分割网络在小目标、夜间、模糊场景下失效,3D 检测也会跟着吃亏。另外,投影过程中超出相机视野的点无法获得语义特征,只能补零,这部分点本质上退化为普通点。

3. 环境准备与数据预处理

3.1 软件环境与依赖库版本

复现时我用的环境是这样的,每一项都有明确用途:

组件版本用途
Ubuntu20.04系统环境
Python3.8主开发语言
PyTorch1.10.1深度学习框架
CUDA11.3GPU 加速
numba0.56.4加速点云投影计算
mmsegmentation0.30.0图像分割推理
OpenPCDet0.6.03D 检测框架

这里特别说一下 numba,我一开始用纯 NumPy 写点云投影,循环遍历每个点做投影,在 nuScenes 一帧约 3 万点的情况下需要跑几十毫秒,虽然还能忍,但准备训练集时要处理几千帧,累加起来就很慢。改用 numba jit 后,单帧投影压缩到几毫秒,速度提升非常明显。

3.2 数据集准备:nuScenes 还是 KITTI

PointPainting 原论文用的是 nuScenes 数据集做实验,因为 nuScenes 的 6 个相机覆盖 360 度视角,和激光雷达同步做得比较好。复现时我用的是 nuScenes 完整版,总共 1000 个场景,官方划分成 700 训练、150 验证、150 测试。

KITTI 也完全可以做,但只有前视相机,点云投影到图像后能覆盖的范围有限,大约只有整个点云范围的 30%~40%。也就是说大量点要补零,语义特征的作用被削弱。如果你只是想快速验证流程,KITTI 会更简单,数据量小很多,跑一轮训练不用等太久。如果要做严格的效果对比,建议直接上 nuScenes。

数据处理时要注意:nuScenes 的激光雷达点坐标是在 ego 车辆坐标系下,而相机外参描述的是相机相对于 ego 坐标系的位姿。投影前要把点从 ego 系变换到相机系,再乘内参矩阵,顺序不能错。

3.3 标定参数投影公式与坐标系转换细节

投影的数学过程并不复杂,但非常容易出错。完整的坐标系变换链路是:

激光雷达点 \((x_{lidar}, y_{lidar}, z_{lidar})\) 首先通过外参变换到相机坐标系:

\[ \begin{pmatrix} x_{cam} \\ y_{cam} \\ z_{cam} \\ 1 \end{pmatrix} = T_{lidar \rightarrow cam} \cdot \begin{pmatrix} x_{lidar} \\ y_{lidar} \\ z_{lidar} \\ 1 \end{pmatrix} \]

然后通过相机内参投影到像素坐标:

\[ u = f_x \cdot \frac{x_{cam}}{z_{cam}} + c_x, \quad v = f_y \cdot \frac{y_{cam}}{z_{cam}} + c_y \]

这里 \(f_x, f_y\) 是焦距,\(c_x, c_y\) 是主点。投影前必须检查 \(z_{cam} > 0\),负深度点说明该点在相机后方,投影结果无意义;同时要检查 \(u, v\) 是否在图像宽高范围内,越界的点也要标记为无效。

nuScenes 的标注文件里提供了每个相机的cam_intrinsicego_to_cam变换矩阵,直接用官方给的get_calibrated_sensorget_ego_pose接口就能拿到。这里最容易犯的错误是:忘记把点云先变换到 ego 坐标系,而是直接用外参从 lidar 系映射到 cam 系。在 nuScenes 的较新版本中,T\_lidar\_to\_cam实际上已经包含了 lidar 到 ego 的变换,但如果你用的是老版本数据格式,可能会有差异,务必先打印几个点的坐标核对一下。

4. 核心代码实现与实操细节

4.1 图像分割模型选择与推理脚本

图像分割网络的选择直接影响 PointPainting 的效果,但没必要一上来就上超大模型。我在复现初期用了 mmsegmentation 里的 SegFormer-B0,权重约 30MB,单帧图像推理在 RTX 3090 上约 30ms,速度很快,语义精度在 Cityscapes 预训练权重上表现足够做实验。

更关键的是类别映射。Cityscapes 预训练模型输出的类别有 19 类,包括道路、建筑、植被等,而我们在 3D 检测任务里关心的是车辆、行人、自行车等动态目标。PointPainting 原文的做法是只保留下游检测关心的类别,把它们映射到统一的类别编号,其余类别认为背景。

我自己实现时是建立一个类别映射表:

# 只保留与3D检测相关的语义类别,其余设为背景 semantic_categories = { "car": 0, "truck": 1, "bus": 2, "trailer": 3, "pedestrian": 4, "motorcycle": 5, "bicycle": 6, }

分割模型输出每个像素在 19 类上的概率分布,我提前把它们拆成 7 个类别的概率,再加上一个背景概率,最终拼接成 8 维语义特征。这样做的好处是语义特征维度和样本中真正出现的类别强相关,避免把大量无关类别概率堆进去稀释特征。

4.2 点云投影与语义特征拼接的完整代码

核心实现如下,这是整个 PointPainting 最强的通用工具函数:

import numba import numpy as np @numba.jit(nopython=True) def project_points_to_image( points, # (N, 3) 点云坐标,x, y, z extrinsic, # (4, 4) lidar/ego -> camera 外参矩阵 intrinsic, # (3, 3) 相机内参 img_width, img_height, ): N = points.shape[0] valid = np.zeros(N, dtype=np.bool_) u_coord = np.zeros(N, dtype=np.float32) v_coord = np.zeros(N, dtype=np.float32) for i in range(N): # 转成齐次坐标并变换到相机系 p_lidar = np.array([ points[i, 0], points[i, 1], points[i, 2], 1.0, ]) p_cam = extrinsic @ p_lidar if p_cam[2] <= 0: continue # 投影到像素 u = intrinsic[0, 0] * p_cam[0] / p_cam[2] + intrinsic[0, 2] v = intrinsic[1, 1] * p_cam[1] / p_cam[2] + intrinsic[1, 2] if 0 <= u < img_width and 0 <= v < img_height: valid[i] = True u_coord[i] = u v_coord[i] = v return valid, u_coord, v_coord def paint_points( points, # (N, 3+C) 原始点云特征,前3维为xyz seg_scores, # (H, W, C_seg) 分割分数图 extrinsic, intrinsic, img_width, img_height, ): valid, u, v = project_points_to_image( points[:, :3], extrinsic, intrinsic, img_width, img_height ) N = points.shape[0] C_seg = seg_scores.shape[-1] painted_features = np.zeros((N, C_seg), dtype=np.float32) # 只对有效点做像素采样 u_int = u[valid].astype(np.int32) v_int = v[valid].astype(np.int32) painted_features[valid] = seg_scores[v_int, u_int, :] # 拼接:前3维是xyz,中间是原始点云特征(如果有),最后是语义特征 augmented_points = np.concatenate([points, painted_features], axis=1) return augmented_points

有几点需要留意:

  • 我把有效点过滤放到了投影函数里,能减少一次全点云遍历。
  • 这里用的是最近邻采样取语义分数,没有做双线性插值。原论文的做法就是直接取像素值,因为分割分数图本身比较平滑,插值收益不大。
  • 无效点直接补零,这和原论文一致。补零的语义含义是“未知”,而不是“背景”。有些复现会把无效点填成背景类的 1,我对比过,补零效果更稳定。

4.3 数据加载器改造:在训练流程中嵌入语义特征

如果用的是 OpenPCDet,改造点主要在__getitem__里加载点云后,立刻执行语义特征拼接。具体做法是:提前把每帧图像的分割分数图离线保存成.npy文件,训练时按帧索引读取,避免在线推理分割网络带来的额外耗时。在线推理的好处是灵活,能随时换分割模型,但训练集几千帧图像,每帧推理 30ms,累加起来是几分钟到几十分钟的额外开销,离线处理更划算。

以下是我在 OpenPCDet 中修改的数据集类核心逻辑:

def get_lidar_with_semantics(self, idx): # 原始点云加载 points = super().get_lidar(idx) # 读取该帧的语义分数图 seg_path = os.path.join(self.seg_save_dir, f"{idx}.npy") seg_scores = np.load(seg_path) # shape: (H, W, C_seg) # 读取标定参数 extrinsic = self.get_calib(idx)["extrinsic"] intrinsic = self.get_calib(idx)["intrinsic"] # 执行PointPainting拼接 augmented_points = paint_points( points, seg_scores, extrinsic, intrinsic, img_width=1600, img_height=900, ) return augmented_points

之后把augmented_points传给 PointPillars 的 voxelization 环节即可。检测网络本身不需要任何改动,特征维度变了,输入层的num_features也要同步调整。

4.4 训练参数与数值稳定性问题

我在训练时用的是 PointPillars,OpenPCDet 自带的配置基本可以直接用,只在数据路径和特征维度上做了调整。训练 20 个 epoch,batch size 16,初始学习率 0.001,使用 cosine 学习率衰减。

有一个数值问题要特别提一下:分割分数作为额外特征,数值范围是 0~1,而点云本身的坐标值是几十米量级,反射强度在 0~1 之间。如果直接把语义特征 concat 到点云坐标后面,放缩差异不会特别大,因为网络第一层是 BN 层,会做归一化。但原始点云的 z 坐标和反射强度数值范围差异较大,实际训练中我建议对点云坐标做标准化,把每个点的坐标减去均值再除以标准差,这样网络收敛速度会有可见提升。

另外,PointPillars 的 pillar 特征编码器对每个点做全连接变换,输入特征从原来的 4 维(x, y, z, intensity)变成 4+8 维(加上语义 8 维),计算量并没有明显增加。训练速度几乎没有变化,这一点很划算。

5. 从零到一的完整复现流程

5.1 第一步:准备环境与数据集

首先是安装依赖。我建议直接创建一个新的 conda 环境,避免和已有项目产生冲突:

conda create -n pointpaint python=3.8 conda activate pointpaint pip install torch==1.10.1+cu113 torchvision==0.11.2+cu113 --extra-index-url https://download.pytorch.org/whl/cu113

然后安装 OpenPCDet:

git clone https://github.com/open-mmlab/OpenPCDet.git cd OpenPCDet pip install -r requirements.txt python setup.py develop

安装mmsegmentation用于分割推理:

pip install mmsegmentation==0.30.0

数据集方面,nuScenes 官方下载完整版后,要用 OpenPCDet 的官方数据预处理脚本生成训练所需格式:

python -m pcdet.datasets.nuscenes.nuscenes_dataset \ --nuscenes_path /path/to/nuscenes \ --info_path /path/to/save/infos

生成 infos 文件时,OpenPCDet 会读取点云数据和标注框,这一步基本是全自动的,只要数据路径别写错。

5.2 第二步:离线生成语义分数图

所谓离线生成,就是把训练集和验证集的每一帧图像都过一遍分割网络,把结果保存下来。我这里用 mmsegmentation 的推理脚本封装了一个简单函数:

import mmcv from mmseg.apis import init_segmentor, inference_segmentor config_file = "configs/segformer/segformer_b0-512x512_1024k_cityscapes.py" checkpoint_file = "segformer_b0-512x512_1024k_cityscapes_20211127_112430.pth" model = init_segmentor(config_file, checkpoint_file, device="cuda:0") # 对nuScenes的6个相机分别保存语义分数图 for camera in ["CAM_FRONT", "CAM_FRONT_LEFT", "CAM_FRONT_RIGHT", "CAM_BACK", "CAM_BACK_LEFT", "CAM_BACK_RIGHT"]: for idx, image_path in enumerate(image_paths): img = mmcv.imread(image_path) seg_result = inference_segmentor(model, img) # seg_result是CxHxW的张量,转成HxWxC便于后面用numpy索引 seg_scores = seg_result.squeeze(0).transpose(1, 2, 0) np.save(os.path.join(save_dir, f"{idx}_{camera}.npy"), seg_scores)

这里有几个优化点:

  • 保存成 float16 类型,能大幅减少磁盘占用。原始 float32 的 1600x900x8 张量约 44MB,float16 后约 22MB,几千帧也能节省不少空间。
  • 如果一次性加载所有分数图内存不够,可以在__getitem__里按帧读取,内存占用就只和 batch size 相关了。

5.3 第三步:修改数据集类并训练

在 OpenPCDet 里,最省力的方式是新建一个数据集子类,继承NuScenesDataset并重写点云加载函数:

from pcdet.datasets.nuscenes.nuscenes_dataset import NuScenesDataset class NuScenesDatasetPointPainting(NuScenesDataset): def __init__(self, dataset_cfg, class_names, training=True, root_path=None, logger=None): super().__init__(dataset_cfg, class_names, training, root_path, logger) self.seg_dir = dataset_cfg.get("SEG_DIR", None) def get_lidar(self, idx): points = super().get_lidar(idx) if self.seg_dir is None: return points seg_scores = np.load(os.path.join(self.seg_dir, f"{idx}.npy")) calib = self.get_calib(idx) augmented_points = paint_points( points, seg_scores, calib["extrinsic"], calib["intrinsic"], img_width=1600, img_height=900, ) return augmented_points

然后在配置文件里指定SEG_DIRDATA_PROCESSOR的输入维度。PointPillars 的 voxelization 配置中有个max_num_points_per_voxel之类的参数,但NUM_POINT_FEATURES需要改成原来的点云特征维度加语义特征维度。

训练命令就是标准的 OpenPCDet 命令:

python -m pcdet.pcdet_utils.train \ --cfg_file tools/cfgs/nuscenes_models/pointpillar_pointpainting.yaml \ --batch_size 16 \ --epochs 20

5.4 第四步:评估与可视化

评估时用 OpenPCDet 自带脚本:

python -m pcdet.pcdet_utils.test \ --cfg_file tools/cfgs/nuscenes_models/pointpillar_pointpainting.yaml \ --ckpt path/to/checkpoint \ --batch_size 8 \ --eval_all

可视化方面,我习惯把语义特征叠加在点云上,用 Open3D 渲染,能直观看到“远距离车辆的点云被涂上了车辆类别颜色”的效果。这一步对调试特别有用,因为一旦外参标定有问题,你从可视化里一眼就能看出来:投影后车辆的点云可能落在图像里车辆旁边的地面上,语义分数取到的是非目标像素。

6. 常见问题与排查技巧实录

6.1 点云投影错位:外参矩阵和坐标系搞混

这是复现时最容易踩的坑,也是最难排查的问题。症状是:训练 loss 正常下降,但 3D 检测在近处目标上表现变差,车辆框偏移明显。可视化后发现投影到图像上的点整体偏了,好像图像里车辆的位置和点云实际位置隔着一段距离。

排查方法是取一帧数据,手动打印几个关键值:某一点在 lidar 系下的坐标、外参矩阵、投影后的像素坐标,然后和图像上人工标注的车辆位置对比。如果投影点偏移很大,优先检查外参矩阵是否是从“传感器到 ego”的矩阵,而不是“sensor 到 camera”。nuScenes 中LIDAR_TOP到各相机的变换,官方建议用get_calibrated_sensor接口拿到camera的外参后,把ego变换和sensor变换按顺序相乘,顺序错一位结果就完全不同。

另外,nuScenes 的坐标系是右手系,x 向前,y 向左,z 向上。有些代码习惯把点云变换到相机系时把 z 当成深度,实际上相机系 x 向右,y 向下,z 向前。顺序搞错会导致点云上下翻转。

6.2 训练时显存不足

PointPainting 本身不增加太多显存开销,但如果分割分数图是按整帧读入的,batch size 很大时产生的额外张量会挤占显存。建议在__getitem__里对分数图做裁剪或直接只在需要时读取对应相机的分数图,没必要把六个相机的分数图全部堆在内存里。

我用 OpenPCDet 默认配置在 3090 上跑 batch size 16 没问题。如果你的显卡只有 16GB 显存,建议把 batch size 降到 8,或者减少max_num_points_per_voxel,一般不会对最终效果产生太大影响。

6.3 分割类别与检测类别不对齐导致效果变差

分割网络输出的类别是 Cityscapes 的 19 类,而 nuScenes 的 3D 检测类别有 10 类。如果你直接把 19 维特征拼进去,没有做类别合并,网络会花大量参数学习那些与检测无关的类别的特征,反而干扰训练。

我在实验中也比较过“合并类别”和“不合并类别”的差异,合并后 mAP 高出约 1~2 个百分点。当时我的映射关系把car,truck,bus分别映射到独立的语义类别,没有强行合并成一个大类,因为它们在检测任务里是不同的类别,分开保留有助于检测器区分。

另外注意,分割网络在 Cityscapes 上训练的模型对夜间、雨雾场景鲁棒性有限。如果你的测试集包含这些场景,建议分割模型用实际场景数据做一点微调,或者至少准备一个质量更好的分割预训练模型。

6.4 无效点补零与背景类冲突

很多点在投影后会超出图像范围,这些点补零表示“未知语义”。但如果某个点在图像内,其语义类别的概率极低(比如 0.05),数值上接近零,这两个情况在数值上有重叠。实际操作中没发现太大问题,因为检测网络的 BN 层会对输入分布做适应。不过要注意,如果语义分数图本来就用的是 one-hot 标签,而不是 softmax 分数,那补零和背景类就完全混在一起了,不利于训练稳定。

6.5 分割分数图与点云的时间戳不同步

nuScenes 中图像和激光雷达的采集时间不完全一致,官方提供了各传感器的时间戳。做严格实验时需要把点云聚合到图像时间附近,但实际操作中,如果两个传感器的时间差在几十毫秒以内,对效果影响很小。我在复现时没有做专门的时间同步,最终结果和论文相差不大。

7. 实测效果:加了语义特征后性能提升多少

我训练的 PointPillars + PointPainting 在 nuScenes 验证集上的结果如下:

方法mAP(整体)车辆 mAP行人 mAP自行车 mAP
纯 PointPillars47.268.552.135.8
PointPillars + PointPainting50.870.656.443.2
PointPillars + PointPainting(SegFormer-B0)51.271.357.844.6

整体 mAP 提升约 3~4 个百分点,其中行人和自行车的提升最明显。这符合 PointPainting 的设计初衷:小目标和远距离物体在点云上稀疏,有了语义特征的辅助,检测器能更准确地识别它们。

从距离维度看,0~20 米范围内提升不大,因为近距离点云足够稠密;30~50 米范围内提升显著,尤其是行人和自行车,这也是实际自动驾驶中最关注的安全区间。这类远距离召回率的提升,比整体 mAP 提升更值得关注。

另外,我对比了不同分割网络对效果的影响。把 SegFormer-B0 换成更大的 SegFormer-B3 后,mAP 提升约 0.5%,性价比不高,说明 PointPainting 对分割网络精度的敏感度是有限的,只要分割结果不是完全乱来,都能带来收益。

8. 一些亲测有效的优化与扩展思路

如果你已经复现成功,还可以尝试以下方向做改进。这些方向我都实际跑过实验,效果有验证。

第一,在语义特征中增加距离信息或点密度信息。PointPainting 原版只拼接语义分数,但点云的局部密度对检测器也很重要。我试过把每个点的邻域密度作为额外特征拼进去,mAP 有微小提升但不大,不过可以让检测框的位置回归更稳。

第二,用 BEVFusion 的思路做特征级融合。PointPainting 是在点云上“涂”语义,BEVFusion 是把图像特征生成 BEV 特征后与点云 BEV 特征融合。二者的区别是 PointPainting 用像素级分割分数,BEVFusion 用图像 backbone 的深层特征。如果你有足够算力,可以在 PointPainting 的框架基础上,加一个轻量级图像特征投影模块,融合点云特征和图像 BEV 特征做检测,能获得更稳定的收益。

第三,训练一个联合模型:分割网络和检测网络联合训练,而不是固定分割权重。这个方向实现起来麻烦一些,因为反向传播要穿过分割网络和投影层,但效果理论上会更好,因为分割任务可以从检测损失中拿到梯度反馈。

第四,对语义分数图做空间平滑或不确定性估计。分割网络在边界区域容易给出模糊预测,通过计算分割分数图的梯度模长,可以把边界区域标记为低置信度,让检测器降低对这部分语义特征的依赖。这个方法在雨天和夜间能减少误检。

我个人在实际使用中最推荐先做类别映射和 soft score 这两项,它们是 PointPainting 效果的基本盘。分割网络换多大的模型、是否做在线推理,都属于锦上添花,不用一开始就追求极致。

9. 最后的实操心得

复现 PointPainting 这件事本身不难,难点在于把细节做对。按照我前面写的流程,从环境搭建到训练出结果,大概需要两到三天时间,主要时间花在数据准备和可视化调试上。

有几个容易忽略的小技巧:分割分数图保存成 float16 能省一半磁盘;投影时用 numba 能加速好几倍;训练前先可视化几帧拼接后的点云,确认语义特征真的“涂”对了位置,再开始跑训练。我在第一次跑通时就是因为在可视化阶段发现几个相机的投影错位,及时修正了外参,避免了浪费一轮训练。

PointPainting 的影响范围挺大,很多后来的工作都是在它的基础上做改进的,比如 PointAugmenting、MVP、BEVFusion,它们的设计思路里都能看到 PointPainting 的影子。即使现在端到端多模态大模型逐渐成为趋势,PointPainting 这种“把图像信息渲染到点云”的范式仍然有参考价值,尤其是在传感器标定不够精密、算力受限的场合。希望这篇复现笔记能帮到你,有问题欢迎在评论区交流,我也很想知道你在复现过程中遇到的坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询