☰
Python实现激光雷达点云着色:多传感器融合与三维视觉实践
2026/10/7 18:26:03 网站建设 项目流程

简介:本资源是一套基于Python实现的激光雷达点云与图像融合可视化工具,面向自动驾驶、机器人感知及计算机视觉方向的初学者与工程实践者,解决LiDAR点云在相机图像上精确投影并着色渲染的核心问题。资源包共23个文件,包含7张PNG图像、4个BIN点云数据、3个核心Python脚本(main.py、pcd_vis.py、func.py)、3个校准参数文本(支持KITTI单/双文件两种格式)、1份README说明及LICENSE协议,整体压缩包仅8.77MB,轻量易部署。已有3783人学习下载,体现了其在多传感器标定与可视化教学中的广泛实用性。用户可直接运行main.py完成端到端投影:自动读取同名图像与点云,依据R_rect、P_rect、Tr或R/T等标定参数完成坐标系转换,并生成前视图(FV)、鸟瞰图(BEV)及彩色点云渲染结果,配套demo图像直观展示效果,目录结构清晰,模块职责分明,便于理解标定原理与调试修改。

1. 项目概述:从三维世界到二维图像的色彩映射

在自动驾驶、机器人导航和三维重建领域,激光雷达和相机是两大核心传感器。激光雷达提供精确的三维空间点云数据,但通常是“黑白”的,缺乏纹理和颜色信息;相机则能捕捉丰富的二维彩色图像,但丢失了深度。如何将这两者融合,为冰冷的点云数据“上色”,从而获得带有真实世界色彩的3D点云,是感知和理解环境的关键一步。这个项目要做的,正是利用Python,将激光雷达点云精确地投影到对应的相机图像上,并从中“汲取”颜色,生成彩色的点云数据。

这个过程听起来简单,实则涉及传感器标定、坐标变换、图像插值等多个技术环节。一个常见的应用场景是:自动驾驶车辆在行驶中,激光雷达扫描周围环境得到无数个三维点,同时相机拍摄了一帧图像。我们需要知道每一个激光雷达点对应在图像上的哪个像素,然后把这个像素的RGB颜色值“贴”回这个三维点上。最终,我们可以在三维可视化工具中看到一个色彩斑斓的点云世界,树木是绿的,天空是蓝的,车辆是各种颜色的,这极大地提升了数据的可解释性和后续算法(如基于颜色的点云分割、目标识别)的性能。

本文将手把手带你走通整个流程,从理解核心原理、准备数据与工具,到一步步编写代码实现投影与着色,最后处理实际工程中的各种“坑”。无论你是刚接触多传感器融合的新手,还是想寻找一个可靠、可复现的代码参考,这篇文章都将提供详尽的指南。

2. 核心原理拆解:坐标系的舞蹈

要实现点云着色,核心在于完成一系列精确的坐标变换。我们可以把这个过程想象成一场在四个不同舞台(坐标系)间进行的“舞蹈”。理解这场舞蹈的每一步,是成功的关键。

2.1 四大坐标系与它们的转换关系

整个投影流程涉及四个核心坐标系:

  1. 激光雷达坐标系 (Lidar Coordinate System): 原点通常在激光雷达的几何中心。点云数据 (point_cloud) 中的每个点(x, y, z)的坐标就是在这个坐标系下定义的。
  2. 车辆/载体坐标系 (Vehicle/Body Coordinate System): 一个固定在车体上的坐标系,通常作为传感器之间转换的中间桥梁或共同参考系。
  3. 相机坐标系 (Camera Coordinate System): 原点在相机的光心,Z轴沿光轴方向。这是三维点投影到二维图像前的最后一个三维空间。
  4. 图像像素坐标系 (Image Pixel Coordinate System): 我们最终的目的地,以像素为单位,原点通常在图像的左上角。

舞蹈的步骤(变换链)如下:激光雷达点 (X_lidar) -> 车辆坐标系 (X_vehicle) -> 相机坐标系 (X_cam) -> 图像像素坐标 (u, v)

每一步都需要一个变换矩阵。

2.2 关键变换矩阵详解

2.2.1 外参:从激光雷达到相机外参描述了激光雷达和相机之间的相对位置和姿态关系。它由一个3x3的旋转矩阵R和一个3x1的平移向量T组成,通常合成为一个4x4的齐次变换矩阵T_lidar_to_cam。

[ T_{lidar_to_cam} = \begin{bmatrix} R & T \ 0 & 1 \end{bmatrix} ]

对于一个激光雷达点P_lidar = [x, y, z, 1]^T(齐次坐标),其在相机坐标系下的坐标P_cam计算为: [ P_{cam} = T_{lidar_to_cam} \cdot P_{lidar} ] 这个外参矩阵需要通过传感器联合标定(如使用棋盘格)精确获取,它是整个投影精度的基石。标定不准,投影就会错位,给点云“戴错颜色帽子”。

2.2.2 内参:从相机到图像相机内参矩阵K描述了相机坐标系下的三维点如何投影到二维图像平面。它是一个3x3的矩阵:

[ K = \begin{bmatrix} f_x & 0 & c_x \ 0 & f_y & c_y \ 0 & 0 & 1 \end{bmatrix} ]

  • f_x,f_y: 相机在x和y方向的焦距(以像素为单位)。
  • c_x,c_y: 主点坐标,通常是图像的中心点坐标。

对于相机坐标系下的点P_cam = [X_c, Y_c, Z_c]^T,其对应的图像像素坐标(u, v)通过以下步骤得到:

  1. 归一化平面坐标:[x_n, y_n]^T = [X_c/Z_c, Y_c/Z_c]^T
  2. 像素坐标:[u, v, 1]^T = K \cdot [x_n, y_n, 1]^T

即: [ u = f_x \cdot (X_c / Z_c) + c_x ] [ v = f_y \cdot (Y_c / Z_c) + c_y ]

这里有一个至关重要的细节:只有Z_c > 0的点才会被投影到图像的正前方。Z_c <= 0的点位于相机后方或光心上,在图像上没有对应的投影点,需要被过滤掉。

2.2.3 畸变校正:让图像不再弯曲大多数相机镜头都存在径向畸变和切向畸变,会导致直线在图像边缘变弯。为了获得准确的投影位置,我们通常先对图像进行去畸变处理,或者在内参投影过程中加入畸变系数进行反向校正。常见的畸变模型(如Brown-Conrady模型)包含k1, k2, p1, p2, k3等参数。在投影计算中,我们需要先对归一化坐标(x_n, y_n)进行畸变校正,得到校正后的坐标(x_corrected, y_corrected),然后再乘以内参矩阵得到像素坐标。许多开源数据集(如KITTI)提供的是已校正图像,或同时提供原始图像和畸变参数,处理时需特别注意。

3. 实战准备:数据、工具与环境搭建

在开始编码前,我们需要准备好“食材”和“厨具”。这里以广泛使用的KITTI数据集为例,因为它提供了标准的激光雷达点云(.bin文件)、相机图像以及精确的标定参数文件。

3.1 数据与标定文件解析

下载KITTI数据集的某个序列(例如2011_09_26_drive_0001),我们需要关注以下文件:

  • velodyne_points/data/000000.bin: 激光雷达点云文件,每个点由[x, y, z, reflectance]4个float32数值组成。
  • image_02/data/000000.png: 左侧彩色相机图像。
  • calib_cam_to_cam.txt: 相机之间的标定参数。
  • calib_velo_to_cam.txt: 激光雷达到相机的标定参数。

重点看calib_velo_to_cam.txt:

R: 7.533745e-03 -9.999714e-01 -6.166020e-04 ... (共9个数,按行优先组成3x3矩阵) T: -4.069766e-03 -7.631618e-02 -2.717806e-01 ... (共3个数)

这个文件里的R和T就是之前提到的外参。但需要注意,KITTI数据中的R是3x3旋转矩阵,T是3x1平移向量,单位是米。有时数据提供的是4x4的齐次矩阵,需要能识别其格式。

calib_cam_to_cam.txt中则包含了相机的内参矩阵P_rect_xx(已校正到rectified图像坐标系)和畸变系数等。对于投影到 rectified 图像,我们直接使用P_rect_xx这个3x4的投影矩阵即可,它已经包含了内参和从本相机坐标系到rectified坐标系的变换。

3.2 Python环境与核心库

我们使用Python进行开发,主要依赖以下库:

  • NumPy: 用于高效的矩阵和数组运算。
  • OpenCV (cv2): 用于图像读取、显示、像素值获取以及畸变校正等操作。
  • Matplotlib: 用于结果的可视化展示。

你可以通过以下命令安装:

pip install numpy opencv-python matplotlib

此外,为了高效处理大量的点云数据并进行3D可视化,我强烈推荐使用Open3D库。它比Matplotlib的3D绘图功能更强大、交互性更好,专门为3D数据处理设计。

pip install open3d

4. 代码实现:一步步生成彩色点云

现在,让我们进入核心的代码环节。我将把整个过程分解为清晰的函数和步骤。

4.1 数据读取与参数加载

首先,我们编写函数来读取点云文件和标定参数。

import numpy as np import cv2 import open3d as o3d def load_velodyne_points(file_path): """ 加载KITTI格式的.bin点云文件。 每个点有4个属性:x, y, z, reflectance(反射强度)。 """ points = np.fromfile(file_path, dtype=np.float32).reshape(-1, 4) # 我们只需要xyz坐标,反射强度可选 return points[:, :3], points[:, 3] # 返回点坐标和反射强度 def load_calibration_params(calib_velo_to_cam_path, calib_cam_to_cam_path, cam_id=2): """ 加载标定参数。 cam_id: KITTI中,2代表左侧彩色相机(image_02)。 返回:外参矩阵T_velo_to_cam, 投影矩阵P_rect。 """ # 读取 velo_to_cam 标定 with open(calib_velo_to_cam_path, 'r') as f: lines = f.readlines() # 解析R和T # 这里需要根据文件实际格式进行解析,以下为KITTI格式示例 for line in lines: if line.startswith('R:'): R = np.array([float(x) for x in line.strip().split(' ')[1:]]).reshape(3, 3) elif line.startswith('T:'): T = np.array([float(x) for x in line.strip().split(' ')[1:]]).reshape(3, 1) # 构建4x4齐次变换矩阵 T_velo_to_cam = np.eye(4) T_velo_to_cam[:3, :3] = R T_velo_to_cam[:3, 3] = T.flatten() # 读取 cam_to_cam 标定,获取指定相机的投影矩阵P_rect with open(calib_cam_to_cam_path, 'r') as f: lines = f.readlines() for line in lines: if line.startswith(f'P_rect_{cam_id:02d}'): P_rect = np.array([float(x) for x in line.strip().split(' ')[1:]]).reshape(3, 4) break return T_velo_to_cam, P_rect

4.2 核心投影函数

这是最关键的函数,负责将激光雷达点云投影到图像像素坐标系。

def project_velo_to_image(points_velo, T_velo_to_cam, P_rect, image_shape): """ 将激光雷达坐标系下的点投影到图像平面。 参数: points_velo: (N, 3) 或 (N, 4) 的numpy数组,激光雷达点云。 T_velo_to_cam: (4, 4) 齐次变换矩阵,从激光雷达到相机坐标系。 P_rect: (3, 4) 投影矩阵(已包含内参和rectification)。 image_shape: (height, width) 图像尺寸,用于过滤超出边界的点。 返回: points_2d: (M, 2) 在图像平面内的像素坐标 [u, v]。 indices: (M,) 原始点云中有效投影点的索引。 depths: (M,) 对应点在相机坐标系下的深度值Z_c。 """ # 确保点云是齐次坐标 (N, 4) if points_velo.shape[1] == 3: points_velo_hom = np.hstack([points_velo, np.ones((points_velo.shape[0], 1))]) else: points_velo_hom = points_velo # 假设已经是[x, y, z, 1] # 1. 转换到相机坐标系 # points_cam_hom = T_velo_to_cam @ points_velo_hom.T # (4, N) points_cam_hom = np.dot(T_velo_to_cam, points_velo_hom.T) # (4, N) points_cam = points_cam_hom[:3, :].T # (N, 3), 每一行是[X_c, Y_c, Z_c] # 2. 过滤掉相机后面的点 (Z_c <= 0) front_mask = points_cam[:, 2] > 0 points_cam_front = points_cam[front_mask] indices_front = np.where(front_mask)[0] if len(points_cam_front) == 0: return np.array([]), np.array([], dtype=np.int64), np.array([]) # 3. 投影到归一化平面并应用投影矩阵P_rect # 注意:P_rect是3x4,它已经包含了从某相机坐标系到像素坐标的完整变换。 # 对于KITTI的rectified图像,P_rect可以直接作用于在“rectified相机坐标系”下的点。 # 但我们的points_cam是在原始相机坐标系?这里需要厘清。 # 实际上,KITTI提供的T_velo_to_cam是将点变换到“参考相机”坐标系(通常是cam0)。 # 而P_rect_xx是将“rectified的xx相机”坐标系下的点投影到图像。 # 因此,更通用的做法是: points_rect = R_rect_00 @ points_cam # 然后: points_image_hom = P_rect @ points_rect # 我们需要从calib_cam_to_cam.txt中读取R_rect_00。 # 为了简化,假设我们的T_velo_to_cam已经将点变换到了与P_rect对应的相机坐标系(即rectified坐标系)。 # 那么可以直接: points_cam_front_hom = np.hstack([points_cam_front, np.ones((points_cam_front.shape[0], 1))]) # (M, 4) points_image_hom = np.dot(P_rect, points_cam_front_hom.T).T # (M, 3) # 4. 齐次坐标归一化,得到像素坐标(u, v) points_2d = points_image_hom[:, :2] / points_image_hom[:, 2:3] # (M, 2) # 5. 过滤掉图像边界外的点 height, width = image_shape in_image_mask = (points_2d[:, 0] >= 0) & (points_2d[:, 0] < width) & \ (points_2d[:, 1] >= 0) & (points_2d[:, 1] < height) points_2d_in = points_2d[in_image_mask] indices_in = indices_front[in_image_mask] depths_in = points_cam_front[in_image_mask, 2] # 深度信息 return points_2d_in, indices_in, depths_in

注意:上述代码中的坐标变换路径是一个简化版本。在完整的KITTI流程中,点云先通过T_velo_to_cam变换到cam0坐标系,然后通过R_rect_00(一个4x4的旋转矩阵,作用于齐次坐标)变换到 rectified 的cam0坐标系,最后通过P_rect_xx投影到cam_xx的图像平面。在实际编码时,请务必根据你的标定文件格式理清这个链条。核心公式为:pixel_coords = P_rect_xx * R_rect_00 * T_velo_to_cam * point_velo。

4.3 颜色提取与彩色点云生成

获取到投影点对应的像素坐标后,我们就可以从图像中提取颜色了。

def colorize_point_cloud(points_velo, indices_2d, points_2d, image): """ 根据投影的2D坐标,从图像中提取颜色,并为对应的3D点着色。 参数: points_velo: 原始点云 (N, 3)。 indices_2d: 有效投影点在原始点云中的索引。 points_2d: 对应的2D像素坐标 (M, 2)。 image: 彩色图像 (H, W, 3),BGR格式(OpenCV默认)。 返回: colored_points: (M, 6) 的数组,每行包含 [x, y, z, r, g, b]。 颜色值范围通常为0-1(float)或0-255(int)。 """ # 确保像素坐标为整数 points_2d_int = np.round(points_2d).astype(np.int32) # 从图像中提取颜色 (BGR顺序) colors_bgr = image[points_2d_int[:, 1], points_2d_int[:, 0], :] # 注意行列索引 # 将BGR转换为RGB(如果后续可视化工具需要RGB) colors_rgb = colors_bgr[:, [2, 1, 0]] # 获取对应的3D点 points_3d_selected = points_velo[indices_2d] # 组合成彩色点云 [x, y, z, r, g, b] # 颜色归一化到0-1范围,便于Open3D等工具显示 colored_points = np.hstack([points_3d_selected, colors_rgb.astype(np.float32) / 255.0]) return colored_points

4.4 主流程与可视化

最后,我们将所有步骤串联起来,并展示结果。

def main(): # 路径配置(请替换为你的实际路径) velodyne_path = 'path/to/000000.bin' image_path = 'path/to/000000.png' calib_velo_to_cam_path = 'path/to/calib_velo_to_cam.txt' calib_cam_to_cam_path = 'path/to/calib_cam_to_cam.txt' # 1. 加载数据 points_velo, _ = load_velodyne_points(velodyne_path) image = cv2.imread(image_path) # BGR格式 height, width = image.shape[:2] # 2. 加载标定参数 T_velo_to_cam, P_rect = load_calibration_params(calib_velo_to_cam_path, calib_cam_to_cam_path, cam_id=2) # 3. 投影 points_2d, indices, depths = project_velo_to_image(points_velo, T_velo_to_cam, P_rect, (height, width)) print(f"原始点云数量: {points_velo.shape[0]}") print(f"成功投影到图像内的点数量: {points_2d.shape[0]}") if points_2d.shape[0] == 0: print("没有点被投影到图像内,请检查标定参数或点云范围。") return # 4. 着色 colored_points = colorize_point_cloud(points_velo, indices, points_2d, image) # 5. 可视化 # 5.1 在2D图像上绘制投影点 img_projected = image.copy() for (u, v) in points_2d.astype(np.int32): # 可以根据深度赋予不同颜色,这里简单用红色 cv2.circle(img_projected, (u, v), radius=1, color=(0, 0, 255), thickness=-1) cv2.imshow('Projected Points on Image', img_projected) cv2.waitKey(0) cv2.destroyAllWindows() # 5.2 使用Open3D可视化3D彩色点云 pcd = o3d.geometry.PointCloud() pcd.points = o3d.utility.Vector3dVector(colored_points[:, :3]) pcd.colors = o3d.utility.Vector3dVector(colored_points[:, 3:6]) # RGB颜色,范围0-1 # 创建一个坐标系辅助观察 coord_frame = o3d.geometry.TriangleMesh.create_coordinate_frame(size=5.0, origin=[0, 0, 0]) o3d.visualization.draw_geometries([pcd, coord_frame], window_name='Colored LiDAR Point Cloud', width=1024, height=768, point_show_normal=False) # 可选:保存彩色点云为PLY格式 # o3d.io.write_point_cloud("colored_point_cloud.ply", pcd) if __name__ == '__main__': main()

运行这段代码,你应该能看到两幅图:一幅是带有红色投影点的相机图像,另一幅是彩色的三维点云。你可以用鼠标在Open3D窗口中旋转、缩放点云,从不同角度观察被“上色”后的三维世界。

5. 工程实践中的关键细节与避坑指南

代码跑通只是第一步。在实际项目中,你会遇到各种问题导致投影不准、颜色错乱或效率低下。以下是几个最常见的“坑”及其解决方案。

5.1 标定参数的对齐与验证

问题:投影结果明显偏移,物体轮廓对不上。排查:

  1. 坐标系定义:首先确认所有标定参数(外参、内参、畸变)是基于怎样的坐标系定义(右手系还是左手系?X/Y/Z轴方向?)。激光雷达、相机、车辆坐标系的定义必须一致。KITTI使用的是相机坐标系:X向右,Y向下,Z向前的右手系。你的数据如果来源不同,务必进行转换。
  2. 变换链完整性:确保你应用的变换链是完整的。例如,KITTI数据中,T_velo_to_cam是将点从激光雷达坐标系变换到非Rectified的相机坐标系(通常是cam0)。而P_rect_xx矩阵隐含了从Rectified相机坐标系到像素坐标的变换。因此,中间缺了一个R_rect_00变换。完整的公式应为:pixel_coords = P_rect_xx * R_rect_00 * T_velo_to_cam * point_velo你需要从calib_cam_to_cam.txt中读取R_rect_00(一个4x4的矩阵,作用于齐次坐标)。
  3. 单位:检查平移向量T的单位是否为米(m),与点云坐标单位一致。

验证方法:一个简单的验证方法是,将一些已知的、易于识别的三维点(如车辆顶部、地面标志点)投影到图像上,看其像素位置是否落在对应的物体上。也可以反向操作,在图像上选择几个角点,利用深度图或假设一个平面,反投影回3D空间,看其与点云的吻合程度。

5.2 点云过滤与投影效率优化

问题:点云数据量巨大(例如64线激光雷达一帧超过10万个点),全部投影计算耗时,且很多点(如天空、后方)根本不在相机视野内。优化策略:

  1. 基于距离和角度的预过滤:在投影前,先过滤掉明显不在相机视野内的点。例如,在激光雷达坐标系下,过滤掉Z值(车前方向)为负(车后)的点,或者距离原点过远(如>100米)的噪点。
  2. 视锥体剔除:在将点变换到相机坐标系后,可以基于相机视野(FOV)进行快速剔除。对于一个典型的相机,其水平FOV和垂直FOV是已知的。可以计算点在相机坐标系下的角度:theta_x = arctan2(X_c, Z_c),theta_y = arctan2(Y_c, Z_c)然后判断abs(theta_x) < FOV_x/2且abs(theta_y) < FOV_y/2。这比投影到图像再判断边界更高效。
  3. 使用矩阵运算,避免循环:就像我们示例代码中那样,始终使用NumPy的矩阵运算(如np.dot)来处理整个点云数组,绝对避免对每个点使用Python for循环进行坐标变换,这会有数量级的性能差异。

5.3 图像边界处理与颜色插值

问题:投影后的像素坐标是浮点数,直接取整(round或astype(int))会导致颜色分配出现锯齿状的不连续,特别是在点云稀疏或物体边缘。解决方案:

  1. 双线性插值:对于浮点坐标(u, v),取其周围的四个整数像素点(u0, v0),(u1, v0),(u0, v1),(u1, v1),其中u0 = floor(u),u1 = ceil(u),v同理。然后根据(u, v)与这四个点的距离进行加权平均,得到更平滑的颜色。OpenCV的cv2.remap函数或手动实现都很方便。
    def bilinear_interpolate(image, u, v): u0, v0 = int(np.floor(u)), int(np.floor(v)) u1, v1 = u0 + 1, v0 + 1 # 处理边界 u0 = np.clip(u0, 0, image.shape[1]-1) u1 = np.clip(u1, 0, image.shape[1]-1) v0 = np.clip(v0, 0, image.shape[0]-1) v1 = np.clip(v1, 0, image.shape[0]-1) # 权重 w_u1, w_v1 = u - u0, v - v0 w_u0, w_v0 = 1 - w_u1, 1 - w_v1 # 插值 color = (w_u0 * w_v0 * image[v0, u0] + w_u1 * w_v0 * image[v0, u1] + w_u0 * w_v1 * image[v1, u0] + w_u1 * w_v1 * image[v1, u1]) return color
    在colorize_point_cloud函数中,对每个(u, v)调用此函数代替直接索引。
  2. 处理投影到图像外的点:我们的代码已经通过边界检查过滤了这些点。但在某些应用中,你可能想保留这些点并赋予默认颜色(如黑色或灰色)。这取决于你的需求。

5.4 时间同步与运动畸变补偿

问题:激光雷达旋转扫描一帧需要时间(如100ms),相机曝光是瞬间的。如果车辆在运动,激光雷达在不同时刻扫描到的点,其对应的车辆位姿是不同的。直接将一整帧点云投影到同一时刻的图像上,会导致运动物体(或自身运动)上的点云出现“拖影”或错位。解决方案:这是一个高级话题,但非常重要。

  1. 时间戳对齐:确保你使用的点云和图像是在尽可能接近的时间戳下采集的。数据采集系统应提供精确的时间同步。
  2. 运动畸变补偿:如果车辆运动剧烈,需要对点云进行运动补偿。这需要知道每一束激光脉冲发射时的精确时间戳(通常点云数据中会提供,如timestamp或time字段),以及对应时间段的车辆位姿变化(来自IMU或轮速计,通过SLAM或定位算法估计)。然后根据每个点的时间戳,将其位置修正到同一参考时刻(通常是帧的中心时刻)。补偿后的点云再用于投影,精度会大幅提升。对于KITTI数据集,其点云已经过运动补偿,但许多其他数据集或实车数据没有,需要自己处理。

6. 进阶应用与扩展思路

掌握了基础投影与着色后,你可以在此基础上做很多有趣且实用的扩展。

6.1 生成带颜色的点云数据文件

我们的代码在内存中生成了彩色点云(colored_points)。你可以将其保存为通用的点云格式,供其他软件(如CloudCompare, MeshLab)或后续算法使用。

  • PLY格式:Open3D可以方便地保存和加载。
    o3d.io.write_point_cloud("output_colored.ply", pcd)
  • CSV/TXT格式:简单易读。
    np.savetxt("output_colored.txt", colored_points, fmt='%.6f', delimiter=',', header='x,y,z,r,g,b')
  • 增强格式:你还可以将反射强度reflectance、深度depth、甚至语义标签等信息一同保存,形成更丰富的点云属性。

6.2 融合其他信息:深度图与语义分割图

除了颜色,我们还可以从图像中提取更多信息来增强点云。

  • 生成稠密深度图:将投影后的点,根据其深度值Z_c,填充到一个与图像同尺寸的数组中,即可生成一个稀疏的深度图。可以通过插值算法(如最近邻、移动最小二乘)将其变为稠密深度图,用于三维重建。
  • 点云语义着色:如果你有图像的语义分割结果(每个像素都有一个类别标签,如“车”、“人”、“路”),你可以将类别标签(通常用颜色表示)像提取RGB颜色一样,赋予对应的三维点。这样你就得到了带有语义信息的3D点云,这对于自动驾驶的感知任务(如基于点云的语义分割模型训练)极具价值。

6.3 反向投影:从图像像素生成3D点

有时我们需要根据单目或双目图像的像素和深度信息,反投影生成3D点。这本质上是上述过程的逆运算。 给定像素坐标(u, v)和深度值d(即Z_c):

  1. 利用相机内参逆矩阵K_inv,计算归一化坐标:[x_n; y_n; 1] = K_inv * [u; v; 1]
  2. 得到相机坐标系下的点:[X_c; Y_c; Z_c] = d * [x_n; y_n; 1]
  3. 利用相机到激光雷达的外参逆矩阵T_cam_to_velo,将点变换到激光雷达坐标系:P_velo = T_cam_to_velo * P_cam

这在融合深度学习检测框(2D)与点云(3D)时非常有用,可以粗略估计物体在3D空间中的位置。

6.4 处理多相机与多激光雷达系统

对于拥有多个相机(前视、侧视、环视)和多个激光雷达的复杂系统,你需要为每一对“激光雷达-相机”建立独立的投影关系。流程是类似的:

  1. 为每个激光雷达和每个相机进行标定,得到各自到车体坐标系的外参T_lidarX_to_body,T_camY_to_body。
  2. 对于任意一个激光雷达点P_lidarX,要投影到相机Y的图像上,其变换链为:P_camY = T_camY_to_body^{-1} * T_lidarX_to_body * P_lidarX然后再用相机Y的内参进行投影。
  3. 你需要一个统一的调度逻辑,决定将每个激光雷达的点云投影到哪个(或哪些)相机的图像上,通常基于重叠的视野(FOV)来判断,避免不必要的计算。

这个过程对标定数据的准确性和一致性要求极高,是所有高级自动驾驶感知系统的底层基础。通过本文的实践,你已经掌握了为点云“赋予色彩”这一核心技能的关键步骤与全部细节。从理解原理、编写代码到避开实践中的各种陷阱,希望这份详尽的指南能成为你探索三维视觉世界的坚实起点。在实际操作中,最耗时的部分往往是数据的准备、标定参数的核对以及异常情况的调试,耐心和细致是成功的关键。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询