如何为KITTI数据集的Velodyne点云逐点添加标签以训练神经网络?
给KITTI Velodyne点云添加标签列的实现方案
我之前做KITTI点云相关任务时也碰到过这个需求,下面给你整理了一套清晰的实现步骤,附代码示例供参考:
一、先搞懂KITTI的标注逻辑
KITTI里每帧Velodyne点云(.bin文件)都对应一个同名的标签文件(.txt),标签里每行记录一个目标的信息,核心字段包括:
type:目标类别(比如Car、Pedestrian、Cyclist,建议忽略DontCare类)height/width/length:3D框的尺寸x/y/z:3D框中心在相机坐标系下的坐标rotation_y:3D框绕y轴的旋转角
要给每个点加标签,本质是判断每个点属于哪个目标的3D框内,然后分配对应的类别ID。
二、具体实现步骤
1. 读取点云与标注文件
首先用numpy读取.bin格式的点云,解析成(N,4)的数组(x,y,z,intensity);同时读取对应的.txt标签文件,提取每个有效目标的3D框参数。
2. 坐标转换(关键!)
KITTI的3D框标注是在相机坐标系下的,而Velodyne点云是在LiDAR坐标系下的,所以需要用校准文件(calib.txt)里的转换矩阵,把3D框转到LiDAR坐标系,这样就能直接在LiDAR坐标系下判断点是否在框内。
校准文件里的Tr_velo_to_cam矩阵(4x4)的逆矩阵,可实现相机坐标到LiDAR坐标的转换。
3. 判断点是否在3D框内
对每个目标的3D框,我们先把它转到LiDAR坐标系,然后通过以下步骤判断点是否在框内:
- 计算点相对于框中心的相对坐标
- 绕y轴旋转相对坐标,抵消框的旋转角
- 判断旋转后的坐标是否落在
[-length/2, length/2]、[-width/2, width/2]、[-height/2, height/2]范围内
4. 分配标签并保存
初始化一个全0的标签数组(0代表背景),然后对每个目标,把符合条件的点的标签设为对应类别ID(比如Car=1,Pedestrian=2,Cyclist=3),最后把x,y,z,intensity,label合并成(N,5)的数组,保存成新的.bin或.npy文件。
三、Python代码示例
import numpy as np # 定义类别到ID的映射,可按需扩展 CLASS_TO_ID = {'Car':1, 'Pedestrian':2, 'Cyclist':3} def read_velodyne_bin(file_path): # 读取Velodyne点云,返回(N,4)数组(x,y,z,intensity) point_cloud = np.fromfile(file_path, dtype=np.float32).reshape(-1,4) return point_cloud def read_kitti_label(file_path): # 读取标签文件,返回有效目标的3D框参数列表 targets = [] with open(file_path, 'r') as f: lines = f.readlines() for line in lines: parts = line.strip().split() obj_type = parts[0] if obj_type not in CLASS_TO_ID: continue # 跳过无关类别 # 提取3D框核心参数 h, w, l = float(parts[8]), float(parts[9]), float(parts[10]) x, y, z = float(parts[11]), float(parts[12]), float(parts[13]) rot_y = float(parts[14]) targets.append({ 'type': obj_type, 'h': h, 'w': w, 'l': l, 'x': x, 'y': y, 'z': z, 'rot_y': rot_y }) return targets def read_calib(file_path): # 读取校准文件,获取相机转LiDAR的转换矩阵 calib = {} with open(file_path, 'r') as f: lines = f.readlines() for line in lines: if 'Tr_velo_to_cam' in line: values = list(map(float, line.strip().split()[1:])) tr_velo_to_cam = np.array(values).reshape(3,4) # 转成4x4齐次矩阵 tr_velo_to_cam_homo = np.vstack([tr_velo_to_cam, [0,0,0,1]]) # 计算逆矩阵,实现相机坐标到LiDAR坐标的转换 tr_cam_to_velo_homo = np.linalg.inv(tr_velo_to_cam_homo) calib['tr_cam_to_velo'] = tr_cam_to_velo_homo break return calib def point_in_box(point, box, tr_cam_to_velo): # 把3D框中心从相机坐标系转到LiDAR坐标系 box_center_cam = np.array([box['x'], box['y'], box['z'], 1.0]) box_center_velo = tr_cam_to_velo @ box_center_cam box_center_velo = box_center_velo[:3] # 计算点相对于框中心的坐标 rel_point = point[:3] - box_center_velo # 绕y轴旋转,抵消框的旋转角(注意符号适配KITTI标注规则) rot_angle = -box['rot_y'] cos_rot = np.cos(rot_angle) sin_rot = np.sin(rot_angle) rot_matrix = np.array([ [cos_rot, 0, sin_rot], [0, 1, 0], [-sin_rot, 0, cos_rot] ]) rotated_rel_point = rot_matrix @ rel_point # 判断点是否在3D框范围内 in_x = (-box['l']/2 <= rotated_rel_point[0] <= box['l']/2) in_y = (-box['h']/2 <= rotated_rel_point[1] <= box['h']/2) in_z = (-box['w']/2 <= rotated_rel_point[2] <= box['w']/2) return in_x and in_y and in_z def add_labels_to_pointcloud(velo_path, label_path, calib_path, save_path): # 读取所有数据 point_cloud = read_velodyne_bin(velo_path) targets = read_kitti_label(label_path) calib = read_calib(calib_path) tr_cam_to_velo = calib['tr_cam_to_velo'] # 初始化标签数组,默认0代表背景 labels = np.zeros((point_cloud.shape[0], 1), dtype=np.int32) # 遍历每个目标,给对应点分配标签 for target in targets: class_id = CLASS_TO_ID[target['type']] # 找出所有在当前框内的点 mask = np.array([point_in_box(p, target, tr_cam_to_velo) for p in point_cloud]) labels[mask] = class_id # 合并点云和标签,得到(N,5)数组 labeled_pointcloud = np.hstack([point_cloud, labels]) # 保存为bin文件(也可以保存为npy格式,后续训练读取更便捷) labeled_pointcloud.tofile(save_path) print(f"带标签的点云已保存到: {save_path}") # 示例调用,替换为你的数据集路径 if __name__ == "__main__": velo_file = "data/velodyne/000000.bin" label_file = "data/labels/000000.txt" calib_file = "data/calib/000000.txt" save_file = "data/labeled_velodyne/000000.bin" add_labels_to_pointcloud(velo_file, label_file, calib_file, save_file)
四、注意事项
- 类别过滤:代码里跳过了
DontCare类,如果你需要保留这些标注,可以调整CLASS_TO_ID映射 - 坐标转换验证:如果发现点和框的匹配不对,先检查校准矩阵的读取和逆矩阵计算是否正确
- 效率优化:处理450帧数据时,循环判断可能较慢,可以用向量化操作或Numba加速
- 标签保存格式:如果后续用PyTorch等框架训练,保存为
.npy格式会比.bin更方便读取
内容的提问来源于stack exchange,提问作者Omar Abo Elsoud
相关产品推荐
相关产品推荐

