第2章:点云数据基础

各位同学,欢迎来到点云数据基础这一章。说实话,很多做雷达感知的新人,一上来就急着调算法、跑模型,结果连点云数据长什么样都没搞清楚。我当年也犯过这个错——拿到一个Velodyne的bag文件,直接丢进聚类算法,出来的结果惨不忍睹。后来才明白,基础不牢,地动山摇。

这一章,我们就来把点云数据的基础打扎实。我会从定义、坐标系、数据结构三个维度展开。嗯,都是干货。

2.1 点云定义与特性

点云是什么?说白了,就是一堆三维空间中的点。每个点通常包含(x, y, z)坐标,有时候还带上强度、时间戳、甚至颜色信息。

对于毫米波雷达来说,点云有几个关键特性:

  • 稀疏性:毫米波雷达的点云比激光雷达稀疏得多。一个激光雷达一帧可能有十几万个点,毫米波雷达可能只有几十到几百个点。我刚开始做融合时,总想用激光雷达那套方法处理毫米波点云,结果发现根本行不通——太稀疏了,聚类都聚不起来。
  • 噪声大:毫米波雷达受多径反射、杂波干扰影响很大。你经常能看到一些“飞点”,明明前方是空旷路面,却突然冒出一个速度100km/h的目标。嗯,这就是噪声。
  • 多普勒信息:这是毫米波雷达的独门武器。每个点除了位置,还带有径向速度信息。这个信息在目标检测中非常有用——静止目标和运动目标,一看速度就分出来了。
  • 反射强度:不同材质对毫米波的反射强度不同。金属强,塑料弱,行人更弱。我在做路侧感知项目时,就利用这个特性区分过护栏和行人。

核心要点:毫米波雷达点云不是激光雷达的“低配版”,它有自己独特的物理特性。处理时一定要尊重这些特性,而不是盲目套用激光雷达的算法。

2.2 坐标系与坐标变换

坐标系这个问题,我吃过不少亏。有一次做多传感器标定,雷达和相机对不上,查了两天,最后发现是坐标系定义搞反了——雷达用的是右手系,相机用的是左手系。

常见的坐标系有:

坐标系 定义 典型应用
雷达坐标系 原点在雷达中心,x向前,y向左,z向上(右手系) 原始点云数据
车体坐标系 原点在车辆后轴中心,x向前,y向左,z向上 车辆运动规划
世界坐标系 原点固定在地面某点,通常使用UTM或经纬度 全局定位、地图构建
图像坐标系 原点在图像左上角,u向右,v向下 雷达-相机融合

坐标变换的核心就是旋转和平移。数学上用一个4x4的齐次变换矩阵表示:

// 齐次变换矩阵 T = [R | t; 0 | 1]
// 其中 R 是3x3旋转矩阵,t 是3x1平移向量

// 示例:将雷达坐标系下的点变换到车体坐标系
Eigen::Matrix4f T_radar_to_vehicle;
T_radar_to_vehicle << 
  0.999,  0.017, -0.017,  0.5,
 -0.017,  0.999,  0.017, -0.2,
  0.017, -0.017,  0.999,  0.1,
  0,      0,      0,      1;

Eigen::Vector4f pt_radar(1.0, 0.5, 0.2, 1.0);
Eigen::Vector4f pt_vehicle = T_radar_to_vehicle * pt_radar;

个人经验:我建议所有坐标变换都用齐次矩阵来做,不要手动拆成旋转和平移分开算。为什么?因为齐次矩阵可以串联多个变换,比如 T_world_to_vehicle = T_vehicle_to_radar * T_radar_to_world,写起来非常清晰,不容易出错。

2.3 点云数据结构

实际工程中,点云数据怎么组织?我主要用两种:PCL和ROS。这两种我都深度用过,各有千秋。

2.3.1 PCL(Point Cloud Library)

PCL是点云处理的老牌库。它的核心数据结构是 pcl::PointCloud<T>,其中T是点类型。

// 定义一个包含强度信息的点云
pcl::PointCloud<pcl::PointXYZI> cloud;
cloud.width = 100;    // 点云宽度
cloud.height = 1;     // 无序点云,height=1
cloud.points.resize(cloud.width * cloud.height);

// 填充数据
for (size_t i = 0; i < cloud.points.size(); ++i) {
    cloud.points[i].x = i * 0.1;
    cloud.points[i].y = 0.0;
    cloud.points[i].z = 0.0;
    cloud.points[i].intensity = 50.0;  // 强度值
}

PCL支持的点类型很多:PointXYZ(只有坐标)、PointXYZI(坐标+强度)、PointXYZRGB(坐标+颜色)等。对于毫米波雷达,我常用的是自定义点类型,加上速度信息:

// 自定义毫米波雷达点类型
struct PointRadar {
    float x, y, z;      // 位置
    float intensity;    // 反射强度
    float velocity;     // 径向速度
    float snr;          // 信噪比
    uint64_t timestamp; // 时间戳
};

避坑指南:我曾经在PCL中使用 pcl::visualization 显示毫米波点云,发现默认的渲染方式会把所有点画成同样大小,根本看不出强度差异。后来我手动设置了点的大小和颜色映射,才把强度信息可视化出来。记住:可视化不是小事,它能帮你快速发现数据问题。

2.3.2 ROS(Robot Operating System)

ROS是机器人领域的标准中间件。在ROS中,点云数据通过 sensor_msgs::PointCloud2 消息传递。

// ROS中发布点云
sensor_msgs::PointCloud2 cloud_msg;
cloud_msg.header.frame_id = "radar_link";
cloud_msg.header.stamp = ros::Time::now();
cloud_msg.width = 100;
cloud_msg.height = 1;
cloud_msg.is_dense = true;

// 设置点云字段
sensor_msgs::PointCloud2Modifier modifier(cloud_msg);
modifier.setPointCloud2FieldsByString(2, "xyz", "intensity");
modifier.resize(100);

// 填充数据
sensor_msgs::PointCloud2Iterator<float> iter_x(cloud_msg, "x");
sensor_msgs::PointCloud2Iterator<float> iter_y(cloud_msg, "y");
sensor_msgs::PointCloud2Iterator<float> iter_z(cloud_msg, "z");
sensor_msgs::PointCloud2Iterator<float> iter_intensity(cloud_msg, "intensity");

for (int i = 0; i < 100; ++i, ++iter_x, ++iter_y, ++iter_z, ++iter_intensity) {
    *iter_x = i * 0.1;
    *iter_y = 0.0;
    *iter_z = 0.0;
    *iter_intensity = 50.0;
}

// 发布
pub.publish(cloud_msg);

ROS的 PointCloud2 比PCL更灵活——它用字段描述符来定义每个点的数据布局。你可以任意添加字段,比如速度、SNR等,不需要预先定义点类型。但代价是访问速度稍慢,因为每次都要通过字符串查找字段索引。

2.3.3 PCL与ROS的互转

实际项目中,我经常需要在PCL和ROS之间转换数据。ROS提供了现成的转换函数:

// ROS -> PCL
pcl::PointCloud<pcl::PointXYZI>::Ptr cloud_pcl(new pcl::PointCloud<pcl::PointXYZI>());
pcl::fromROSMsg(cloud_msg, *cloud_pcl);

// PCL -> ROS
sensor_msgs::PointCloud2 cloud_msg_out;
pcl::toROSMsg(*cloud_pcl, cloud_msg_out);
cloud_msg_out.header.frame_id = "radar_link";

我的建议:如果你在做离线数据处理,用PCL更顺手,API丰富,处理效率高。如果你在做在线系统(比如自动驾驶),用ROS更合适,因为它天然支持多节点通信和时间同步。我个人习惯是:离线用PCL,在线用ROS,中间用转换函数衔接。

2.4 本章小结

这一章我们讲了点云的定义、坐标系变换、以及两种主流数据结构。你想想看,这些内容看起来基础,但实际工程中80%的bug都出在这些地方——坐标系搞反了、字段类型不对、时间戳没对齐。嗯,基础的东西往往最容易被忽视。

下一章,我们会进入点云预处理,包括滤波、降采样、地面去除等。到时候我会分享一些实际项目中踩过的坑,比如怎么处理多径反射产生的鬼影点。敬请期待。