pointpillars的理论解读
·
随着学习的加深会不断补充的
文章目录
一、pointpillars是什么?
PointPillars是一种用于三维物体检测(3D目标检测)的深度学习算法,它主要解决自动驾驶或机器人领域中,如何从激光雷达(LiDAR)点云数据中快速、准确地识别出车辆、行人、骑行者等障碍物的位置、大小和朝向问题。其核心思想是将无序、稀疏的三维点云通过垂直方向(Z轴)堆叠成“柱子”(Pillars),从而转化为类似图像的伪二维特征图,进而可以无缝利用高效的二维卷积神经网络(CNN)进行端到端的推理。这种设计在显著降低计算开销的同时,保持了极高的检测精度,使其成为兼顾速度与性能的经典工业级部署方案,尤其适用于对实时性要求苛刻的无人驾驶场景。
二、pointpillars架构
- 特征编码器(将点云“升维”成图):首先,把点云在鸟瞰图平面上划分成一个个网格(柱子,即Pillars)。对每个柱子里的点,用简化的PointNet提取特征,并聚合成一个固定长度的向量。这样,所有柱子就拼成了一张伪图像(伪图像,高H,宽W,通道C),成功把3D稀疏问题转化成了2D密集问题。
- 2D卷积主干(特征提纯):将生成的伪图像送入一个2D卷积网络(通常带下采样,类似FPN结构)。这个主干进一步提取空间和语义信息,输出高分辨率的特征图,用于定位,以及低分辨率的特征图,用于分类和回归。
- 检测头(输出3D框):最后,在特征图的每个位置上预设多个不同大小和朝向的锚框(锚点)。检测头会并行预测:每个锚框是前景还是背景(分类),以及它对真实3D框的残差回归量(中心点偏移、尺寸对数缩放、朝向角正弦值)。最后通过非极大值抑制(NMS)过滤掉冗余框,输出最终的检测结果。

1.柱子是怎么划分的
PointPillars首先在鸟瞰图平面上将预设的感兴趣区域划分成固定尺寸的二维网格,每个网格单元即一个“柱子”(Pillar),代表一个垂直延伸、高度不限的空间柱体。随后,根据每个原始点云的(x,y)坐标将其映射到对应的柱体中,并对每个柱子内包含的点进行统一采样或填充,使每个柱子固定包含N个点。接着,对每个柱子内的这N个点,利用一个简化的PointNet(由共享MLP和最大池化组成)进行特征聚合,将每个柱子内所有点的特征压缩为一个固定长度的特征向量。这样,所有柱子就被转换为一个形如(H×W×C)的二维伪图像特征图,其中H和W对应网格的行列数,C为聚合后的特征通道数,从而成功将稀疏、无序的3D点云转化为结构化、密集的2D图像表示,以便后续使用高效的2D卷积神经网络进行特征提取和目标检测。
1.1 鸟瞰图 是什么意思
- 前视图(正面看):就像你开车时透过挡风玻璃看到的景象。能清晰看到物体的高度和正面轮廓,但距离和深度信息很难判断。
- 鸟瞰图(俯视看):就像你在高空中看地面。物体之间的左右距离(X方向)和前后距离(Y方向)被完美保留,且不会互相遮挡,但物体的高度信息(Z轴)被“压扁”了。
为什么自动驾驶和PointPillars要使用鸟瞰图?
- 物理尺度恒定:在鸟瞰图下,无论物体离车是近是远,同一个物体(比如一辆车)的像素大小基本是固定的(除非它真的变大)。而在前视图下,近处物体很大,远处很小,这不利于卷积神经网络学习尺度特征。
- 无遮挡问题:在鸟瞰图中,车辆、行人之间几乎不存在遮挡。而在前视图中,前车会完全挡住后车,导致网络无法检测到被遮挡的物体。
- 方便回归位置:3D框的中心点(x, y)在鸟瞰图上就是一个像素点,网络直接在这个平面上回归位置(x,y)最方便。而高度(z)和朝向(绕Z轴的旋转角)则单独作为额外的属性去回归。
四 pointpillars配置环境
KITTI 是自动驾驶和3D视觉领域最经典、最权威的公开数据集之一
4.1 前处理操作
## 删除之前数据集预处理产⽣的⽂件
rm -rf ./data/custom/gt_database
rm -rf ./data/custom/custom_dbinfos_train.pkl
rm -rf ./data/custom/custom_infos_train.pkl
rm -rf ./data/custom/custom_infos_test.pkl
rm -rf ./data/custom/custom_infos_val.pkl
## 数据预处理
python -m pcdet.datasets.custom.custom_dataset create_custom_infos tools/cfgs/dataset_configs/persontruck_dataset.yaml
4.2pointpillar前置数据配置文件的讲解
DATASET: 'CustomDataset' #声明使用自定义数据集格式(非KITTI原生)
DATA_PATH: './data/custom'
# POINT_CLOUD_RANGE: [-75.2, -75.2, -2, 75.2, 75.2, 4]
# 这里调的是输入点云的范围,超过范围的点就直接删掉
POINT_CLOUD_RANGE: [0., -3., -3., 10.8, 6.92, 3.]
MAP_CLASS_TO_KITTI: { #将你的“Bucket”类映射到KITTI的“Car”类
'Bucket': 'Car',
}
# 这里是OpenPCDet中不同类别和KITTI中的类别的对应,因为还是基于KITTI进行设计的各个算法
# MAP_CLASS_TO_KITTI: {
# 'Bucket': 'Bucket',
# 'Person': 'Person',
# }
DATA_SPLIT: {
'train': train,
'test': val
}
INFO_PATH: {
'train': [custom_infos_train.pkl],
'test': [custom_infos_val.pkl],
}
POINT_FEATURE_ENCODING: {
encoding_type: absolute_coordinates_encoding,
used_feature_list: ['x', 'y', 'z', 'intensity'],
src_feature_list: ['x', 'y', 'z', 'intensity'],
}
DATA_AUGMENTOR:
DISABLE_AUG_LIST: ['placeholder']
AUG_CONFIG_LIST:
# 下面都是数据增强方法: (下面四个参考最后的流程PDF中的【预处理2】)
# - NAME: gt_sampling
# USE_ROAD_PLANE: False
# DB_INFO_PATH:
# - custom_dbinfos_train.pkl
# PREPARE: {
# filter_by_min_points: ['Bucket:5'],
# }
# SAMPLE_GROUPS: ['Bucket:20', ]
# NUM_POINT_FEATURES: 4
# DATABASE_WITH_FAKELIDAR: False
# REMOVE_EXTRA_WIDTH: [0.0, 0.0, 0.0]
# LIMIT_WHOLE_SCENE: True
# - NAME: random_world_flip
# ALONG_AXIS_LIST: ['x', 'y']
# - NAME: random_world_rotation
# WORLD_ROT_ANGLE: [-0.78539816, 0.78539816]
# - NAME: random_world_scaling
# WORLD_SCALE_RANGE: [0.95, 1.05]
DATA_PROCESSOR:
- NAME: mask_points_and_boxes_outside_range
REMOVE_OUTSIDE_BOXES: True
- NAME: shuffle_points
SHUFFLE_ENABLED: {
'train': True,
'test': False
}
- NAME: transform_points_to_voxels
# 控制体系的尺寸
VOXEL_SIZE: [0.125, 0.0625, 10.]
MAX_POINTS_PER_VOXEL: 32
MAX_NUMBER_OF_VOXELS: {
'train': 150000,
'test': 150000
}
4.3pointpillars.yaml配置文件讲解
# 类名
CLASS_NAMES: ['Car', 'Pedestrian', 'Cyclist']#代表要检测的类型
# 数据配置
DATA_CONFIG:
_BASE_CONFIG_: cfg/dataset_configs/kitti_dataset.yaml # 数据文件基本配置
POINT_CLOUD_RANGE: [0, -39.68, -3, 69.12, 39.68, 1] # 点云范围 [x_min, y_min, z_min, x_max, y_max, z_max]超出此范围的点云会被直接裁剪丢弃,这直接影响检测距离和计算量
# 数据处理器
DATA_PROCESSOR:
- NAME: mask_points_and_boxes_outside_range
REMOVE_OUTSIDE_BOXES: True # 移出boxes
- NAME: shuffle_points
SHUFFLE_ENABLED: {
'train': True,
'test': False
}
- NAME: transform_points_to_voxels
VOXEL_SIZE: [0.16, 0.16, 4] # 体素大小
MAX_POINTS_PER_VOXEL: 32 # 每个体素的最大点数
MAX_NUMBER_OF_VOXELS: { # 最大体素数
'train': 16000,
'test': 40000
}
# 数据增强器
DATA_AUGMENTOR:
DISABLE_AUG_LIST: ['placeholder']
AUG_CONFIG_LIST:
- NAME: gt_sampling
USE_ROAD_PLANE: True
DB_INFO_PATH:
- kitti_dbinfos_train.plk
PREPARE: {
filter_by_min_points: ['Car:5', 'Pedestrian:5', 'Cyclist:5'],
filter_by_difficulty: [-1],
}
SAMPLE_GROUPS: ['Car:15', 'Pedestrian:15', 'Cyclist:15']
NUM_POINT_FEATURES: 4
DATABASE_WITH_FAKELIDAR: False
REMOVE_EXTRAD_WIDTH: [0.0, 0.0, 0.0]
LIMIT_WHOLE_SCENE: False
- NAME: random_world_flip
ALONG_AXIS_LIST: ['x']
- NAME: random_world_rotation
WORLD_ROT_ANGLE: [-0.78539816, 0.78539816]
- NAME: random_world_scaling
WORLD_SCALE_RANGE: [0.95, 1.05]
# 模型
MODEL:
NAME: PointPillar
VFE:
NAME: PillarVFE
WITH_DISTANCE: False
USE_ABSLOTE_XYZ: True
USE_NORM: True
NUM_FILTERS: [64]
MAP_TO_BEV:
NAME: PointPillarScatter
NUM_BEV_FEATURES: 64
BACKBONE_2D:
NAME: BaseBEVBackbone
LAYER_NUMS: [3, 5, 5]
LAYER_STRIDES: [2, 2, 2]
NUM_FILTERS: [64, 128, 256]
UPSAMPLE_STRIDES: [1, 2, 4]
NUM_UPSSAMPLE_FILTERS: [128, 128, 128]
DENSE_HEAD:
NAME: AnchorHeadSingle
CLASS_AGNOSTIC: False
USE_DIRECTION_CLASSIFIER: True
DIR_OFFSET: 0.78539
DIR_LIMIT_OFFSET: 0.0
NUM_DIR_BINS: 2
ANCHOR_GENERATOR_CONFIG: [
{
'class_name': 'Car',
'anchor_sizes': [[3.9, 1.6, 1.56]],
'anchor_rotations': [0, 1.57],#旋转:每个位置都生成0°和90°两个朝向的Anchor。
'anchor_bottom_heights': [-1.78],#底部高度 -1.78(中心点离地)
'align_center': False,
'feature_map_stride': 2,
'matched_threshold': 0.6,
'unmatched_threshold': 0.45
},
{
'class_name': 'Pedestrian',
'anchor_sizes': [[0.8, 0.6, 1.73]],
'anchor_rotations': [0, 1.57],
'anchor_bottom_heights': [-0.6],
'align_center': False,
'feature_map_stride': 2,
'matched_threshold': 0.5,
'unmatched_threshold': 0.35
},
{
'class_name': 'Cyclist',
'anchor_sizes': [[1.76, 0.6, 1.73]],
'anchor_rotations': [0, 1.57],
'anchor_bottom_heights': [-0.6],
'align_center': False,
'feature_map_stride': 2,
'matched_threshold': 0.5,
'unmatched_threshold': 0.35
}
] #anchor 调参数的重点,很重要的一部分
TARGET_ASSIGNER_CONFIG:
NAME: AxisAlignedTargetAssigner
POS_FRACTION: -1.0
SAMPLE_SIZE: 512
NORM_BY_NUM_EXAMPLES: False
MATCH_HEIGHT: False
BOX_CODER: ResidualCoder
LOSS_CONFIG:
LOSS_WEIGHTS: {
'cls_weight': 1.0,
'loc_weight': 2.0,
'dir_weight': 0.2,
'code_weights': [1.0, 1.0, 1.0, 1.0, 1.0,1.0, 1.0]
}
POST_PROCESSING:
RECALL_THRESH_LIST: [0.3, 0.5, 0.7]
SCORE_THRESH: 0.1
OUTPUT_RAW_SCORE: False
EVAL_METRIC: kitti
NMS_CONFIG:#Nms用来去除多余框的,NMS_THRESH: 0.01:注意,常规KITTI通常设0.1,这里设为极低(0.01)。文档注释指出,这是为了防止因Z轴预测偏差导致框被误过滤,属于保留更多候选框的暴力策略。
MULTI_CLASSES_NMS: False
NMS_TYPE: nms_gpu
NMS_THRESH: 0.01
NMS_PRE_MAXSIZE: 4096
NMS_POST_MAXSIZE: 500
# 优化
OPTIMIZATION:
BATCH_SIZE_PER_GPU: 4
NUM_EPOCHS: 80
OPTIMIZER: adam_oneycle
LR: 0.003
WEIGHT_DECAY: 0.01
MOMENTUM: 0.9
MOMS: [0.95, 0.85]
PCT_START: 0.4
DIV_FACTOR: 10
DECAY_STEP_LIST: [35, 45]
LR_DECAY: 0.1
LR_CLIP: 0.0000001
LR_WARMUP: False
WARMUP_EPOCH: 1
GRAD_NORM_CLIP: 10
配置文件的存放
OpenPCDet/
├── tools/
│ └── cfgs/
│ ├── dataset_configs/
│ │ └── bucket_dataset.yaml # 这份数据配置文件放这里
│ └── setup_models/
│ └── bucket_pp.yaml # 主训练配置放这里(你文档中也有)
└── data/
└── custom/ # 你的点云、标签、ImageSets软链接到这里
在OpenPCDet(以及源自KITTI风格的大多数3D检测框架)中,ImageSets文件夹不是用来存放图像数据的,它的核心作用是:
它是整个数据集的“花名册”或“索引清单”,专门用来告诉程序:哪些点云文件用来训练,哪些用来验证(测试)。
对于点云啊,标签的存放
OpenPCDet # OpenPCDet 项目根目录
└── data # 存放所有数据
└── custom # 你的自定义数据集文件夹
├── ImageSets # 数据集划分文件
│ ├── train.txt # 训练集样本ID列表
│ └── val.txt # 验证集样本ID列表
├── points # 存放点云数据 (.npy)
│ ├── 000000.npy
│ ├── 000001.npy
│ └── ...
└── labels # 存放标注数据 (.txt)
├── 000000.txt
├── 000001.txt
└── ...
更多推荐



所有评论(0)