别光会下载!手把手教你用Python解析KITTI的.bin点云和.txt标签

发布时间:2026/6/29 0:27:37

别光会下载!手把手教你用Python解析KITTI的.bin点云和.txt标签 从零解析KITTI数据集Python实战点云与3D标注处理指南当你第一次打开KITTI数据集时面对那些神秘的.bin文件和充满数字的.txt文档是否感到无从下手本文将带你深入理解这些数据的结构并手把手教你用Python进行解析和可视化。1. 理解KITTI数据集的核心文件KITTI数据集是自动驾驶领域最常用的基准数据集之一包含丰富的传感器数据。我们需要重点关注以下四种文件类型.bin文件存储LiDAR点云数据二进制格式.txt标签文件包含3D边界框标注信息.txt标定文件记录传感器间的转换关系.png图像文件相机采集的2D图像这些文件看似独立实则相互关联。例如一个典型的场景分析需要将LiDAR点云投影到图像平面或将3D标注框显示在点云中。2. 解析LiDAR点云数据点云数据存储在.bin文件中每个文件对应一帧扫描结果。让我们看看如何用Python读取和解析这些数据import numpy as np def read_bin_file(bin_path): 读取KITTI的.bin点云文件 :param bin_path: .bin文件路径 :return: N×4的numpy数组每行代表一个点[x,y,z,intensity] point_cloud np.fromfile(bin_path, dtypenp.float32).reshape(-1, 4) return point_cloud这个简单的函数就能将二进制文件转换为我们可以处理的numpy数组。每个点包含四个值x, y, z点在LiDAR坐标系中的位置米intensity反射强度表征物体表面的反射特性注意KITTI点云数据使用的是Velodyne HDL-64E激光雷达坐标系x向前y向左z向上。3. 理解标定文件的关键矩阵标定文件如000000.txt包含多个重要矩阵我们需要理解它们的含义矩阵名称尺寸描述P23×4左彩色相机的投影矩阵R0_rect3×3校正旋转矩阵使图像平面共面Tr_velo_to_cam3×4将点从LiDAR坐标系转换到相机坐标系的矩阵def parse_calibration_file(calib_path): 解析KITTI标定文件 :param calib_path: 标定文件路径 :return: 包含标定参数的字典 calib {} with open(calib_path, r) as f: for line in f: if : in line: key, value line.split(:, 1) calib[key.strip()] np.array([float(x) for x in value.strip().split()]) # 重塑矩阵为正确形状 calib[P2] calib[P2].reshape(3, 4) calib[R0_rect] calib[R0_rect].reshape(3, 3) calib[Tr_velo_to_cam] calib[Tr_velo_to_cam].reshape(3, 4) return calib4. 解析3D物体标注标签文件中的每一行代表一个被标注的物体包含丰富的信息Car 0.00 0 -1.57 587.01 173.33 614.12 238.11 1.65 1.67 3.64 -0.65 1.71 46.70 -1.59这些字段依次表示物体类别Car, Pedestrian等截断程度0-1遮挡程度0-3观察角度弧度 5-8. 2D边界框坐标x1,y1,x2,y2 9-11. 3D尺寸高,宽,长单位米 12-14. 3D位置x,y,z相机坐标系单位米旋转角绕Y轴弧度def parse_label_file(label_path): 解析KITTI标签文件 :param label_path: 标签文件路径 :return: 包含标注对象的列表 objects [] with open(label_path, r) as f: for line in f: parts line.strip().split() if len(parts) 15: continue obj { type: parts[0], truncation: float(parts[1]), occlusion: int(parts[2]), alpha: float(parts[3]), bbox: [float(x) for x in parts[4:8]], dimensions: [float(x) for x in parts[8:11]], location: [float(x) for x in parts[11:14]], rotation_y: float(parts[14]) } objects.append(obj) return objects5. 点云与3D标注的可视化理解了数据结构后我们可以使用Open3D库进行可视化import open3d as o3d from matplotlib import pyplot as plt def visualize_point_cloud_with_boxes(points, objects, calib): 可视化点云和3D边界框 :param points: 点云数据(N×4) :param objects: 标注对象列表 :param calib: 标定参数 # 创建点云对象 pcd o3d.geometry.PointCloud() pcd.points o3d.utility.Vector3dVector(points[:, :3]) # 创建坐标系 coordinate_frame o3d.geometry.TriangleMesh.create_coordinate_frame(size3) # 准备可视化 vis_objects [pcd, coordinate_frame] # 为每个对象添加3D边界框 for obj in objects: if obj[type] in [Car, Pedestrian, Cyclist]: box create_3d_box(obj, calib) if box: vis_objects.append(box) # 可视化 o3d.visualization.draw_geometries(vis_objects)完整的3D边界框创建函数需要考虑坐标转换def create_3d_box(obj, calib): 根据标注创建3D边界框 :param obj: 标注对象 :param calib: 标定参数 :return: Open3D线框盒 h, w, l obj[dimensions] x, y, z obj[location] rotation_y obj[rotation_y] # 计算8个角点的局部坐标 corners np.array([ [l/2, l/2, -l/2, -l/2, l/2, l/2, -l/2, -l/2], [0, 0, 0, 0, -h, -h, -h, -h], [w/2, -w/2, -w/2, w/2, w/2, -w/2, -w/2, w/2] ]) # 应用旋转 rot_mat np.array([ [np.cos(rotation_y), 0, np.sin(rotation_y)], [0, 1, 0], [-np.sin(rotation_y), 0, np.cos(rotation_y)] ]) corners rot_mat corners # 应用平移 corners[0, :] x corners[1, :] y corners[2, :] z # 转换为LiDAR坐标系如果需要 if Tr_velo_to_cam in calib: Tr calib[Tr_velo_to_cam] R Tr[:3, :3] t Tr[:3, 3] corners R.T (corners - t.reshape(3, 1)) # 创建线框盒 lines [ [0, 1], [1, 2], [2, 3], [3, 0], # 底面 [4, 5], [5, 6], [6, 7], [7, 4], # 顶面 [0, 4], [1, 5], [2, 6], [3, 7] # 侧面 ] colors [[1, 0, 0] for _ in range(len(lines))] # 红色 line_set o3d.geometry.LineSet() line_set.points o3d.utility.Vector3dVector(corners.T) line_set.lines o3d.utility.Vector2iVector(lines) line_set.colors o3d.utility.Vector3dVector(colors) return line_set6. 将点云投影到图像平面有时我们需要将LiDAR点云与相机图像结合分析这需要将点云投影到图像平面def project_velo_to_image(points, calib): 将LiDAR点投影到图像平面 :param points: N×4的点云数组 :param calib: 标定参数 :return: N×2的图像坐标N×1的深度值 # 提取标定参数 P2 calib[P2] R0_rect calib[R0_rect] Tr_velo_to_cam calib[Tr_velo_to_cam] # 将点云转换为齐次坐标 (N×4) points_hom np.column_stack((points[:, :3], np.ones(points.shape[0]))) # 转换到相机坐标系 points_cam (R0_rect Tr_velo_to_cam points_hom.T).T # 投影到图像平面 points_img (P2 points_cam.T).T # 归一化 points_img[:, 0] / points_img[:, 2] points_img[:, 1] / points_img[:, 2] # 提取2D坐标和深度 img_coords points_img[:, :2] depths points_cam[:, 2] # 相机坐标系下的Z值即深度 return img_coords, depths7. 完整处理流程示例现在我们将所有步骤整合到一个完整的处理流程中def process_kitti_sample(bin_path, label_path, calib_path, image_pathNone): 处理单个KITTI样本 :param bin_path: .bin文件路径 :param label_path: 标签文件路径 :param calib_path: 标定文件路径 :param image_path: 可选图像路径 # 1. 读取点云 points read_bin_file(bin_path) # 2. 解析标定文件 calib parse_calibration_file(calib_path) # 3. 解析标签文件 objects parse_label_file(label_path) # 4. 可视化点云和3D框 visualize_point_cloud_with_boxes(points, objects, calib) # 5. 如果有图像显示点云投影 if image_path: import cv2 img cv2.imread(image_path) img_coords, depths project_velo_to_image(points, calib) # 在图像上绘制点云 for (u, v), depth in zip(img_coords, depths): if 0 u img.shape[1] and 0 v img.shape[0]: color (0, 255, 0) if depth 50 else (0, 0, 255) # 近点绿色远点红色 cv2.circle(img, (int(u), int(v)), 1, color, -1) # 显示结果 plt.figure(figsize(12, 5)) plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) plt.title(LiDAR Points Projected on Image) plt.axis(off) plt.show()8. 实际应用中的注意事项在处理KITTI数据集时有几个关键点需要特别注意坐标系转换LiDAR坐标系x向前y向左z向上相机坐标系z向前x向右y向下图像坐标系原点在左上角u向右v向下数据同步确保使用同一时间戳的数据文件名对应不同传感器数据可能有微小的时间差性能优化对于大规模处理考虑使用多进程点云处理可以使用专门的库如PCL或CUDA加速常见问题排查如果投影点位置不对检查标定矩阵是否正确加载3D框显示异常时确认坐标转换顺序是否正确# 性能优化示例批量处理 from multiprocessing import Pool def process_sample(args): bin_path, label_path, calib_path args try: process_kitti_sample(bin_path, label_path, calib_path) return True except Exception as e: print(fError processing {bin_path}: {str(e)}) return False def batch_process_kitti(data_dir, num_workers4): 批量处理KITTI数据集 :param data_dir: 数据集根目录 :param num_workers: 并行工作进程数 # 收集所有样本路径 bin_files sorted(glob.glob(f{data_dir}/velodyne/*.bin)) tasks [] for bin_path in bin_files: sample_id os.path.basename(bin_path).split(.)[0] label_path f{data_dir}/label_2/{sample_id}.txt calib_path f{data_dir}/calib/{sample_id}.txt if os.path.exists(label_path) and os.path.exists(calib_path): tasks.append((bin_path, label_path, calib_path)) # 并行处理 with Pool(num_workers) as pool: results pool.map(process_sample, tasks) print(fProcessed {sum(results)}/{len(tasks)} samples successfully)掌握这些核心技能后你就可以基于KITTI数据集开展各种3D感知任务的研究和开发了。无论是目标检测、点云分割还是多传感器融合这些基础数据处理能力都是必不可少的。

相关新闻