You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 07:41:03