第一章:点云数据基础
大家好,我是你们这门课的主讲。做了这么多年激光雷达感知,我最大的感触就是——点云数据是算法的基石。你算法再花哨,数据没搞明白,最后跑起来全是坑。今天咱们就从最基础的点云数据结构、坐标系和可视化工具说起。
本章核心知识点
- 点云数据结构:XYZI格式详解
- 三大坐标系:激光雷达坐标系、车体坐标系、世界坐标系
- 可视化工具:Open3D与PCL实战
1.1 点云数据结构:XYZI格式
点云说白了就是一堆三维点的集合。每个点至少包含 X、Y、Z 三个坐标值。但在实际工程中,我们通常还会存一个 I(强度值),这就是所谓的 XYZI 格式。
我刚开始做激光雷达时,以为点云就是简单的 (x,y,z) 列表。后来发现,强度值 I 才是宝藏。它反映了目标表面对激光的反射率,比如车道线反射强、沥青路面反射弱,靠这个就能做路面分割。
💡 我的经验
在项目中,我习惯把点云数据组织成 N×4 的数组(N 个点,每点 4 个字段)。这样内存连续,访问快,也方便传给 GPU 做加速。
来看一个典型的 XYZI 点云结构定义:
// C++ 结构体定义
struct PointXYZI {
float x; // X 坐标(米)
float y; // Y 坐标(米)
float z; // Z 坐标(米)
float intensity; // 强度值(0-255 或归一化 0.0-1.0)
};
实际存储时,不同厂商的激光雷达会有差异。比如 Velodyne 的强度值是 0-255 整数,而 Ouster 的强度值可能是 0-65535。嗯,这里要注意——读取数据后一定要先归一化,否则不同雷达的数据没法统一处理。
| 字段 | 含义 | 典型范围 | 备注 |
|---|---|---|---|
| x | 前向距离 | -100 ~ 100 m | 车头方向为正 |
| y | 左侧距离 | -100 ~ 100 m | 符合右手系 |
| z | 高度 | -5 ~ 50 m | 向上为正 |
| intensity | 反射强度 | 0 ~ 255 | 越高越亮 |
⚠️ 避坑指南
我曾经在项目里直接拿原始强度值做特征,结果换了台雷达后模型全崩了。后来才意识到——不同雷达的强度值物理意义不同,必须做归一化或标定。切记!
1.2 点云坐标系
坐标系是点云感知里最容易出bug的地方。你想想看,激光雷达装在车上,车在世界上跑,每个环节都有自己的坐标系。搞混了,点云就全歪了。
1.2.1 激光雷达坐标系
这是最原始的坐标系。原点在激光雷达的几何中心,X 轴朝前,Y 轴朝左,Z 轴朝上(右手系)。我见过不少新手把 Y 轴方向搞反,结果点云左右颠倒。
1.2.2 车体坐标系
车体坐标系的原点通常在车辆后轴中心或质心。X 轴朝车头,Y 轴朝左,Z 轴朝上。激光雷达装在车顶,所以从雷达坐标系到车体坐标系,需要做一次 平移 + 旋转 变换。
我个人习惯把外参标定矩阵写成 4×4 的齐次变换矩阵:
// 激光雷达到车体坐标系的变换矩阵
Eigen::Matrix4f T_lidar_to_vehicle;
T_lidar_to_vehicle <<
1, 0, 0, 0.5, // 雷达在车体前方 0.5 米
0, 1, 0, 0.0, // 左右居中
0, 0, 1, 1.2, // 高度 1.2 米
0, 0, 0, 1;
1.2.3 世界坐标系
世界坐标系是全局参考系,通常用 GPS/IMU 给出的经纬度或 UTM 坐标。车体坐标系到世界坐标系的变换,依赖于车辆的定位信息。
🔑 核心要点
三个坐标系的关系可以概括为:
原始点云 → 激光雷达坐标系 → 车体坐标系 → 世界坐标系
每一步都是一个 4×4 的齐次变换矩阵。
1.3 点云可视化工具
做感知算法,可视化是调试的命根子。我见过太多人算法写完了,一跑发现点云是歪的,就是因为没可视化检查。这里我推荐两个工具:Open3D 和 PCL。
1.3.1 Open3D(推荐新手)
Open3D 是 Intel 开源的库,API 设计得很现代,Python 接口尤其友好。我个人现在主力用 Open3D,因为它画图快、代码短。
import open3d as o3d
import numpy as np
# 生成随机点云(XYZI格式)
points = np.random.rand(1000, 3) # 1000个点
intensity = np.random.rand(1000, 1)
# 构建Open3D点云对象
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points)
# 给点云上色(用强度值映射到颜色)
colors = np.tile(intensity, (1, 3)) # 灰度显示
pcd.colors = o3d.utility.Vector3dVector(colors)
# 可视化
o3d.visualization.draw_geometries([pcd], window_name="我的第一个点云")
💡 小技巧
Open3D 的 draw_geometries 支持鼠标交互——按住左键旋转、滚轮缩放、右键平移。调试时我经常用它来检查点云是否对齐。
1.3.2 PCL(工业级选择)
PCL 是点云领域的元老,功能极其全面,但 API 偏 C++ 风格,学习曲线陡一些。如果你做的是量产项目,PCL 的稳定性是经过验证的。
#include <pcl/io/pcd_io.h>
#include <pcl/point_types.h>
#include <pcl/visualization/pcl_visualizer.h>
int main() {
// 创建点云对象
pcl::PointCloud<pcl::PointXYZI>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZI>);
// 填充数据
cloud->width = 1000;
cloud->height = 1;
cloud->is_dense = false;
cloud->points.resize(cloud->width * cloud->height);
for (size_t i = 0; i < cloud->points.size(); ++i) {
cloud->points[i].x = 1024 * rand() / (RAND_MAX + 1.0f);
cloud->points[i].y = 1024 * rand() / (RAND_MAX + 1.0f);
cloud->points[i].z = 1024 * rand() / (RAND_MAX + 1.0f);
cloud->points[i].intensity = 128; // 固定强度
}
// 可视化
pcl::visualization::PCLVisualizer viewer("PCL点云查看器");
viewer.addPointCloud<pcl::PointXYZI>(cloud, "cloud");
while (!viewer.wasStopped()) {
viewer.spinOnce();
}
return 0;
}
⚠️ 避坑指南
我曾经在 Ubuntu 上编译 PCL 时被依赖库折腾了一整天。建议直接用 Docker 镜像或者 conda 安装(conda install -c conda-forge pcl),省心很多。
本章小结
这一章我们聊了三个核心内容:
- XYZI 格式:每个点包含位置和强度,强度值记得归一化
- 三大坐标系:雷达→车体→世界,用齐次矩阵做变换
- 可视化工具:Open3D 适合快速验证,PCL 适合量产项目
说实话,这些基础内容看起来简单,但我在面试时发现很多候选人栽在坐标系变换上。你想想看,点云数据本身没错,但坐标系没对齐,后面所有算法都是白费功夫。所以,先把地基打牢,后面咱们再聊滤波、分割、目标检测这些硬核内容。
公众号:蓝海资料掘金营,微信deep3321