1. 点云数据基础
大家好,我是你们这趟点云之旅的向导。做自动驾驶感知这些年,我每天打交道最多的就是点云数据。说实话,刚入行那会儿,看着屏幕上密密麻麻的点,我也有点懵。但摸爬滚打久了,你会发现——点云这东西,其实挺有意思的。
核心一句话:点云就是三维空间里一堆点的集合,每个点带着坐标 (x,y,z),有时候还带着颜色、强度等信息。
1.1 什么是点云
你想想看,我们人眼看世界,看到的是像素组成的图像。但激光雷达不一样,它发射激光束,打到物体上反射回来,就能知道那个点离我们有多远。成千上万个这样的点拼在一起,就形成了点云。
我在项目中遇到过最直观的例子:一辆车停在路边,激光雷达扫过去,车身表面会生成几千个点。这些点勾勒出车的轮廓,甚至能看出是轿车还是SUV。说白了,点云就是激光雷达的「眼睛」看到的世界。
点云有几个关键特性:
- 稀疏性——点与点之间有间隙,不像图像那么密集
- 无序性——点的排列顺序不影响它代表的物体
- 密度不均匀——近处点密,远处点稀
我的小经验:处理点云时,千万别把它当图像看。图像是规整的网格,点云是散乱的。这个思维转换,我花了大概两周才适应。
1.2 点云数据格式
做点云处理,绕不开三种格式:PCD、PLY、BIN。我一个个说。
PCD 格式
这是 Point Cloud Library(PCL)的亲儿子。我建议初学者从PCD入手,因为它结构清晰,可读性强。
# .PCD v0.7 - Point Cloud Data file format
VERSION 0.7
FIELDS x y z intensity
SIZE 4 4 4 4
TYPE F F F F
COUNT 1 1 1 1
WIDTH 640
HEIGHT 1
POINTS 640
DATA ascii
1.2 3.4 5.6 0.8
2.3 4.5 6.7 0.9
...
你看,头部信息清清楚楚:有哪些字段、每个字段占多少字节、总共多少个点。我个人习惯用PCD做中间格式,调试时方便查看。
PLY 格式
PLY(Polygon File Format)是图形学领域的常用格式。它比PCD多一个优势——可以存储多边形网格信息。如果你要做三维重建,PLY会更合适。
ply
format ascii 1.0
element vertex 640
property float x
property float y
property float z
property uchar red
property uchar green
property uchar blue
end_header
1.2 3.4 5.6 200 100 50
2.3 4.5 6.7 180 90 40
...
注意:PLY的RGB值范围是0-255,而PCD里有些格式用0-1的浮点数。我曾经因为这个搞混过,点云显示出来颜色全不对,排查了半天才发现是范围问题。
BIN 格式
BIN就是二进制格式,没有头部信息,直接存原始数据。KITTI数据集用的就是这种格式。为什么用BIN?一个字——快。读取速度比ASCII格式快10倍以上。
// 读取KITTI bin文件的Python代码
import numpy as np
def load_kitti_bin(bin_path):
points = np.fromfile(bin_path, dtype=np.float32)
points = points.reshape(-1, 4) # x, y, z, reflectance
return points
嗯,这里要注意:BIN格式没有头部,你必须事先知道数据的结构。比如KITTI的数据,每个点占4个float(x,y,z,反射率),一共16字节。搞错了维度,数据就全乱了。
| 格式 | 可读性 | 读取速度 | 存储空间 | 适用场景 |
|---|---|---|---|---|
| PCD | 高 | 中 | 大 | 调试、教学 |
| PLY | 高 | 中 | 大 | 三维重建、图形学 |
| BIN | 低 | 快 | 小 | 大规模数据、实时系统 |
1.3 点云可视化基础
做点云处理,可视化是刚需。你总得看看处理结果对不对吧?我主要用两个库:Open3D和PCL。
Open3D(Python)
Open3D是我现在的主力工具。为什么?因为它Python接口友好,安装简单,可视化效果也不错。
import open3d as o3d
import numpy as np
# 生成随机点云
points = np.random.rand(1000, 3)
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(points)
# 可视化
o3d.visualization.draw_geometries([pcd])
就这么几行代码,你就能看到三维空间里随机分布的点。我记得第一次跑通这个例子时,还挺有成就感的。
PCL(C++)
PCL是C++的库,功能更全,但学习曲线也陡一些。如果你要做工业级应用,PCL是绕不开的。
#include <pcl/visualization/pcl_visualizer.h>
#include <pcl/io/pcd_io.h>
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud(new pcl::PointCloud<pcl::PointXYZ>);
pcl::io::loadPCDFile("example.pcd", *cloud);
pcl::visualization::PCLVisualizer viewer("Point Cloud Viewer");
viewer.addPointCloud(cloud, "cloud");
viewer.spin();
我的建议:初学者先用Open3D上手,等需要高性能处理时再转PCL。别一上来就啃C++,容易劝退。
1.4 点云坐标系与变换
坐标系这块,我吃过不少亏。自动驾驶里常见的坐标系有:
- 激光雷达坐标系——以激光雷达为中心,x向前,y向左,z向上
- 车辆坐标系——以车辆后轴中心为原点,x向前,y向左,z向上
- 世界坐标系——通常是UTM坐标系或经纬度
为什么要搞这么多坐标系?因为不同传感器安装位置不同,数据必须统一到一个坐标系下才能融合。
坐标系变换说白了就是旋转和平移。用齐次坐标表示:
import numpy as np
def transform_point(point, rotation_matrix, translation_vector):
"""
点云变换:p' = R * p + t
"""
return rotation_matrix @ point + translation_vector
# 示例:绕z轴旋转30度,平移(1, 2, 3)
theta = np.radians(30)
R = np.array([
[np.cos(theta), -np.sin(theta), 0],
[np.sin(theta), np.cos(theta), 0],
[0, 0, 1]
])
t = np.array([1, 2, 3])
point = np.array([0, 0, 0])
transformed = transform_point(point, R, t)
print(f"变换后坐标: {transformed}")
我曾经踩过的坑:坐标系方向搞反了。有一次我把激光雷达坐标系和车辆坐标系的y轴方向弄混了,结果点云显示出来车辆左侧的障碍物跑到了右侧。排查了整整一天,最后发现是旋转矩阵写反了。从那以后,我每次做变换都会先画个坐标系示意图。
为什么会这样?因为不同厂商的激光雷达,坐标系定义可能不一样。Velodyne和Hesai的坐标系就有差异。你拿到一个新雷达,第一件事就是确认它的坐标系定义。
好了,第一章的内容就这些。点云基础打牢了,后面讲滤波和障碍物检测时,你才能跟得上。下一章我们聊聊点云滤波——说白了就是怎么把没用的点去掉,留下我们关心的目标。
本章要点回顾:
- 点云是三维点的集合,有稀疏性、无序性、密度不均的特点
- PCD适合调试,PLY适合重建,BIN适合大规模数据
- Open3D上手快,PCL功能全,根据场景选工具
- 坐标系变换是点云处理的基础,搞错了方向后果很严重