第3章:点云坐标系与变换
做点云处理,坐标系绕不开。
我刚开始接触点云时,就被各种坐标系搞晕过。世界坐标系、相机坐标系、还有后面要讲的局部坐标系……说白了,它们就是给三维空间里的点一个「身份地址」。地址不同,同一个点的坐标值就不同。
3.1 世界坐标系与相机坐标系
先讲世界坐标系。你可以把它想象成一个「绝对参考系」。不管你的传感器怎么动,世界坐标系是固定不变的。在自动驾驶里,它通常以车辆启动位置为原点;在三维重建里,它往往以场景中心为原点。
相机坐标系呢?它跟着相机走。相机在哪儿,原点就在哪儿。Z轴指向相机前方,X轴向右,Y轴向下——嗯,这是OpenCV的习惯。
我遇到过不少新手,直接把相机坐标系下的点当成世界坐标来用。结果呢?点云全歪了。你想想看,相机转了个角度,点云也跟着转,这显然不对。
为什么需要区分它们?因为传感器采集的数据天然在相机坐标系下。你要做全局建图、多传感器融合,就必须统一到世界坐标系。
3.2 刚体变换:旋转矩阵与平移向量
刚体变换,说白了就是「旋转+平移」。物体形状不变,只是位置和朝向变了。
旋转矩阵是个3x3的方阵。它描述了一个坐标系相对于另一个坐标系的朝向关系。我习惯用R表示。平移向量是个3x1的列向量,用t表示。
公式很简单:
P_world = R * P_camera + t
其中P_camera是相机坐标系下的点,P_world是世界坐标系下的点。
反过来呢?
P_camera = R^T * (P_world - t)
注意,旋转矩阵的逆等于它的转置。这个性质在代码里很实用。
齐次变换矩阵长这样:
T = [[R, t],
[0, 1]]
用Open3D的话,你可以这样构建:
import numpy as np
import open3d as o3d
# 定义旋转矩阵(绕Z轴旋转45度)
theta = np.pi / 4
R = np.array([
[np.cos(theta), -np.sin(theta), 0],
[np.sin(theta), np.cos(theta), 0],
[0, 0, 1]
])
# 定义平移向量
t = np.array([1.0, 2.0, 0.5])
# 构建齐次变换矩阵
T = np.eye(4)
T[:3, :3] = R
T[:3, 3] = t
3.3 使用Open3D进行点云旋转与平移
Open3D处理变换非常方便。它内置了transform()方法,直接传入4x4矩阵就行。
来看个完整例子:
import open3d as o3d
import numpy as np
# 生成一个随机点云
pcd = o3d.geometry.PointCloud()
points = np.random.rand(100, 3) # 100个随机点
pcd.points = o3d.utility.Vector3dVector(points)
# 定义变换矩阵
theta = np.pi / 6 # 30度
R = np.array([
[np.cos(theta), -np.sin(theta), 0],
[np.sin(theta), np.cos(theta), 0],
[0, 0, 1]
])
t = np.array([0.5, 0.0, 0.0])
T = np.eye(4)
T[:3, :3] = R
T[:3, 3] = t
# 应用变换
pcd_transformed = pcd.transform(T)
# 可视化
o3d.visualization.draw_geometries([pcd, pcd_transformed])
运行这段代码,你会看到原始点云(白色)和变换后的点云(红色)同时显示。旋转加平移,一步到位。
另外,Open3D也支持单独设置旋转和平移:
# 单独旋转
pcd.rotate(R, center=(0, 0, 0))
# 单独平移
pcd.translate(t)
rotate()的center参数指定旋转中心。默认是原点。如果你想绕点云中心旋转,先计算质心:
center = pcd.get_center()
pcd.rotate(R, center=center)
这个技巧在配准和拼接时特别有用。
3.4 知识体系总览
下面这张图帮你理清本章的核心逻辑:
从图中可以看出,世界坐标系和相机坐标系通过刚体变换(R和t)相互转换。而Open3D提供了现成的API来实现这些变换。
好了,坐标系和变换就讲到这里。记住一句话:坐标系是点云的「身份证」,变换是点云的「搬家工具」。掌握好它们,后面的课程你会走得很顺。
公众号:蓝海资料掘金营,微信deep3321