第一章:点云数据基础

大家好,我是你们这门课的主讲。做了这么多年激光雷达感知,我最大的感触就是——点云数据是算法的基石。你算法再花哨,数据没搞明白,最后跑起来全是坑。今天咱们就从最基础的点云数据结构、坐标系和可视化工具说起。

本章核心知识点

  • 点云数据结构:XYZI格式详解
  • 三大坐标系:激光雷达坐标系、车体坐标系、世界坐标系
  • 可视化工具:Open3D与PCL实战
点云数据基础 — 知识体系 点云数据结构 坐标系 可视化工具 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 点云可视化工具

做感知算法,可视化是调试的命根子。我见过太多人算法写完了,一跑发现点云是歪的,就是因为没可视化检查。这里我推荐两个工具:Open3DPCL

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