BEVDet代码学习(一)——create_data_bevdet.py

一、学习目标

        初学BEV,目前0基础,项目需要创建自定义数据集。因此,根据create_data_bevdet.py文件了解数据集 .pkl 与配置文件 .json 的创建流程。

二、BEVDet部署参考

        BEVDet部署:BEVDet代码复现实践_bevdet复现过程-CSDN博客

三、代码部分

 3.1主函数运行流程

if __name__ == '__main__':
    dataset =            'nuscenes'    # 数据集名称
    version =       'v1.0-trainval'    # 数据集版本
    root_path =   './data/nuscenes'    # 数据集路径
    extra_tag = 'bevdetv3-nuscenes'    # 数据集额外标签

    # 调用函数准备nuScenes数据集的数据
    nuscenes_data_prep(
        root_path=root_path,
        info_prefix=extra_tag,
        version=version,
        max_sweeps=10)            # 输入连续帧的数量

    # 为数据集添加额外的标注信息
    add_ann_adj_info(extra_tag)
    
    # 创建地面实况数据库,该函数在tools/data_converter/create_gt_database.py中
    create_groundtruth_database('NuScenesDataset',
                                root_path,
                                extra_tag,
                                f'{root_path}/{extra_tag}_infos_train.pkl')

 3.2根据Nuscenes数据集生成自定义类别

        定义字典 map_name_from_general_to_detection ,用于将NuScenes数据集中的一般类别名称映射到特定的检测类别。其中,“ignore”类别的对象在检测任务中直接忽略。

map_name_from_general_to_detection = {
    # 成人、儿童、警察、建筑工人均被视为pedestrian。
    'human.pedestrian.adult': 'pedestrian',
    'human.pedestrian.child': 'pedestrian',
    'human.pedestrian.police_officer': 'pedestrian',
    'human.pedestrian.construction_worker': 'pedestrian',
    # 轮椅、婴儿车、个人移动设备、动物被标记为ignore,意味着在检测中不考虑这些类别。
    'human.pedestrian.wheelchair': 'ignore',
    'human.pedestrian.stroller': 'ignore',
    'human.pedestrian.personal_mobility': 'ignore',
    'animal': 'ignore',
    # 汽车、摩托车、自行车、各种类型的卡车、公交车、拖车、施工车辆分别对应各自的类别。
    'vehicle.car': 'car',
    'vehicle.motorcycle': 'motorcycle',
    'vehicle.bicycle': 'bicycle',
    'vehicle.bus.bendy': 'bus',
    'vehicle.bus.rigid': 'bus',
    'vehicle.truck': 'truck',
    'vehicle.construction': 'construction_vehicle',
    'movable_object.barrier': 'barrier',
    'movable_object.trafficcone': 'traffic_cone',
    'vehicle.trailer': 'trailer',
    # 救护车、警车也被标记为ignore,可能是因为在某些场景下它们不是主要的检测目标。
    'vehicle.emergency.ambulance': 'ignore',
    'vehicle.emergency.police': 'ignore',
    # 可推拉物品、碎片、自行车架都被标记为ignore。
    'movable_object.pushable_pullable': 'ignore',
    'movable_object.debris': 'ignore',
    'static_object.bicycle_rack': 'ignore',
}

    classes 列表则包含了模型在检测任务中需要识别的具体类别。它列出了模型在执行检测任务时需要识别的具体对象类别,这些类别是基于 map_name_from_general_to_detection 字典中定义的规则筛选出来的。在模型训练或评估阶段,只有classes列表中的类别会被考虑,而被标记为ignore的类别将被排除在外。

classes = [
    'car', 'truck', 'construction_vehicle', 'bus', 'trailer', 'barrier',
    'motorcycle', 'bicycle', 'pedestrian', 'traffic_cone'
]

3.3根据Nuscenes和上一节的字典生成GT

        从给定的信息字典中提取并转换地面实况(Ground Truth,GT)标签,特别是针对3D目标检测任务。此函数主要用于处理来自NuScenes数据集的数据,将原始的标注信息转换为模型训练所需的格式。

def get_gt(info):
    """
    从给定的信息中生成地面真实标签(GT labels)。

    输入参数:
        info (dict): 包含生成GT标签所需信息的字典,如摄像头参数和标注信息。

    返回:
        list: GT边界框(bboxes),在全局坐标系中的位置。
        list: GT类别标签。
    """
    # 从信息中提取前摄像头到全局坐标的旋转矩阵
    ego2global_rotation = info['cams']['CAM_FRONT']['ego2global_rotation']
    # 提取前摄像头到全局坐标的平移向量
    ego2global_translation = info['cams']['CAM_FRONT']['ego2global_translation']
    
        # info: 这是输入的字典。
        # info['cams']: 在字典info中,有一个键(key)为'cams'的条目。
        # 这个键指向的是一个子字典,里面包含了所有摄像头的信息。
        # info['cams']['CAM_FRONT']: 继续深入,
        # 'cams'键下的子字典中有一个为'CAM_FRONT'的条目,这代表了前向摄像头的具体信息。
        # 'CAM_FRONT'可能包含摄像头的位置、角度、分辨率、帧率等信息,但在这个特定的上下文中,我们关注的是与坐标变换相关的信息。
        # info['cams']['CAM_FRONT']['ego2global_rotation']: 最后,'CAM_FRONT'键下的字典中还有一个键为'ego2global_rotation'的条目。
        # 这个条目存储了一个四元数,表示从自车(ego vehicle)坐标系到全局坐标系的旋转变换四元数。

    # 计算反向平移向量,将平移向量转换成NumPy数组。
    trans = -np.array(ego2global_translation)

    # 创建逆旋转矩阵Quaternion对象,赋值给rot
    rot = Quaternion(ego2global_rotation).inverse
    
    # 初始化GT边界框列表
    gt_boxes = []
    # 初始化GT标签列表
    gt_labels = []
    
    # 遍历所有标注信息
    # 遍历 info['ann_infos'] 列表中的每一个标注信息字典。
    # 对于列表中的每一个字典,用 ann_info 来访问和处理单个标注的详细信息。
    for ann_info in info['ann_infos']:
        # 在车体坐标系下
        
        # 当前标注必须在class内 且 有lidar、radar的点云数据
        if (map_name_from_general_to_detection[ann_info['category_name']] not in classes
                or ann_info['num_lidar_pts'] + ann_info['num_radar_pts'] <= 0):
            continue
        
        # 创建3D边界框,Box对象
        box = Box(
            ann_info['translation'],   # 位置
            ann_info['size'],          # 尺寸
            Quaternion(ann_info['rotation']),  # 旋转姿态
            velocity=ann_info['velocity'],    # 速度
        )
        
        # 将边界盒从车辆坐标转换至全局坐标
        box.translate(trans)
        box.rotate(rot)
        
        # 获取边界盒中心坐标,为Numpy数组
        box_xyz = np.array(box.center)

        # 获取边界盒尺寸(深度、宽度、高度)
        # wlh 属性代表宽度(Width)、长度(Length)和高度(Height)
        # 列表 [1, 0, 2] 指定了新的顺序
        box_dxdydz = np.array(box.wlh)[[1, 0, 2]]

        # 获取边界框偏航角
        box_yaw = np.array([box.orientation.yaw_pitch_roll[0]])

        # 获取边界框速度(前两维)
        box_velo = np.array(box.velocity[:2])
        
        # 合并边界框数据:位置、尺寸、航向角、速度
        gt_box = np.concatenate([box_xyz, box_dxdydz, box_yaw, box_velo])
        
        # 将GT边界盒添加到列表
        gt_boxes.append(gt_box)

        # 将GT标签添加到列表
        # classes.index(...): index 方法用于查找列表中某个元素的索引位置。
        # classes.index(...) 返回map_name_from_general_to_detection[ann_info['category_name']] 所表示的类别在 classes 列表中的索引。
        gt_labels.append(classes.index(map_name_from_general_to_detection[ann_info['category_name']]))
    
    # 返回GT边界框和GT标签
    return gt_boxes, gt_labels

3.4 预处理NuScenes数据

调用./tools/data_converter/nuscene_converter.py中的nuscenes_converter函数对数据进行预处理

def nuscenes_data_prep(root_path, info_prefix, version, max_sweeps=10):
    """
    准备与nuScenes数据集相关的数据。

    相关数据包括记录基本信息、2D标注和地面实况数据库的'.pkl'文件。

    参数:
        root_path (str): 数据集的路径。
        info_prefix (str): 信息文件名的前缀。
        version (str): 数据集版本。
        max_sweeps (int, 可选): 输入连续帧的数量,默认为10。
    """
    nuscenes_converter.create_nuscenes_infos(
        root_path, info_prefix, version=version, max_sweeps=max_sweeps)

3.5向数据集加入额外的标注信息

该函数用于给NuScenes数据集的训练和验证集添加注释和相邻帧信息。具体步骤如下:

  1. 定义NuScenes数据集的版本和数据路径。
  2. 初始化NuScenes数据集。
  3. 遍历训练集和验证集。
  4. 加载数据集的元信息。
  5. 对于每个样本,获取其注释信息。
  6. 计算注释的速度,并处理NaN值。
  7. 将速度信息添加到注释信息中。
  8. 使用get_gt函数处理注释信息。
  9. 添加场景token到样本信息中。
  10. 根据场景名称和样本token生成路径。
  11. 将更新后的数据集保存到文件中。
def add_ann_adj_info(extra_tag):

    # 指定NuScenes数据集的版本和根目录
    nuscenes_version = 'v1.0-trainval'
    dataroot = './data/nuscenes/'

    # 初始化NuScenes对象
    # NuScenes: 这是一个类,它封装了对NuScenes数据集的访问,提供了多种方法来查询和操作数据集中的信息,例如获取样本、注释、传感器数据等。
    # 执行 nuscenes = NuScenes(nuscenes_version, dataroot) 这行代码时,以下事情会发生:
    # 实例化:创建了一个 NuScenes 类的新实例,并将其赋值给变量 nuscenes。
    # 初始化:调用了 NuScenes 类的构造函数,该构造函数接受数据集版本和数据根目录作为参数。
    # 加载元数据:构造函数会加载数据集的元数据,包括样本信息、注释、类别定义、传感器校准数据等。这些元数据存储在数据集的JSON文件中。
    # 建立索引:为了加快后续的查询速度,构造函数可能还会建立各种内部索引或缓存。
    nuscenes = NuScenes(nuscenes_version, dataroot)
    
    # 遍历训练集和验证集
    for set in ['train', 'val']:

        # 加载数据集dataset
        # ./data/nuscenes/%s_infos_%s.pkl: 这是一个文件路径模板,其中%s是占位符,会被后面的变量替换。这里%s会被extra_tag和set变量的值替换,生成实际的文件路径
        # (extra_tag, set): 这是一个元组,其中extra_tag和set是变量名,它们的值将被用来生成具体的文件名。
        # 'rb': 打开文件的模式,r表示读取,b表示以二进制模式读取
        # pickle.load()函数被调用,它从打开的文件中读取字节流,并将其反序列化为Python对象。
        # 反序列化后的对象被赋值给dataset变量
        dataset = pickle.load(open(f'./data/nuscenes/{extra_tag}_infos_{set}.pkl', 'rb'))

        # 遍历dataset中的info
        for id in range(len(dataset['infos'])):
            # 打印进度
            if id % 10 == 0:
                print(f'{id}/{len(dataset["infos"])}')
            
            # 根据id获取 info
            info = dataset['infos'][id]

            # nuscenes.get('sample', info['token']) 是调用 NuScenes 类的一个方法 get() 来获取数据集中的一个样本
            # 'sample' 指定了要获取的元数据类型,在这里是样本数据。
            # info['token'] 唯一标识符,用于定位数据集中的特定样本。这个 token 通常是在 info 字典中找到的,它是NuScenes数据集中用于唯一标识每个样本的字段
            sample = nuscenes.get('sample', info['token'])

            # 初始化标注信息列表
            ann_infos = list()

            # 遍历样本的所有标注
            for ann in sample['anns']:
                # 获取标注详细信息
                ann_info = nuscenes.get('sample_annotation', ann)

                # 获得速度
                velocity = nuscenes.box_velocity(ann_info['token'])

                # 处理速度中可能存在的NaN值
                if np.any(np.isnan(velocity)):
                    velocity = np.zeros(3)

                # 将速度添加到标注信息中
                ann_info['velocity'] = velocity

                # 将处理后的标注信息添加到列表
                ann_infos.append(ann_info)

            # 更新数据集中的标注信息
            dataset['infos'][id]['ann_infos'] = ann_infos

            # 获取地面标签GT 并更新数据集
            dataset['infos'][id]['ann_infos'] = get_gt(dataset['infos'][id])

            # 添加场景令牌,scene_token用于唯一标识场景的令牌。每个场景包含多个连续的样本,而 scene_token 用于将样本关联到正确的场景
            # 将 sample 字典中找到的 scene_token 值复制到 dataset['infos'][id] 字典的 scene_token 键中
            dataset['infos'][id]['scene_token'] = sample['scene_token']

            # 获取场景信息
            scene = nuscenes.get('scene', sample['scene_token'])

            # 设置占用栅格路径
            dataset['infos'][id]['occ_path'] = f'./data/nuscenes/gts/{scene["name"]}/{info["token"]}'

        # 保存更新后的数据集
        # open() 函数用于打开或创建一个文件,这里使用了字符串格式化来构建文件路径。
        # 'wb' 参数表示以二进制写入模式打开文件。b 表示二进制,这是因为pickle序列化后的数据通常不是纯文本格式,而是字节流
        # with 语句用于安全地管理文件的打开和关闭。即使在写入数据时发生异常,with 语句也会确保文件被正确关闭,避免资源泄漏
        # fid 是一个文件对象,它是在 with 语句中打开的文件
        with open('./data/nuscenes/%s_infos_%s.pkl' % (extra_tag, set),
                  'wb') as fid:
            pickle.dump(dataset, fid)
            # pickle.dump() 函数用于将Python对象序列化并写入到一个文件中。
            # fid 是前面打开的文件对象,pickle.dump() 将把 dataset 对象转换成字节流并写入到这个文件中。

四、小结

        从3.5节NuScenes类中可得,具体数据标注在Nuscenes的配置文件Json中,本着按需求学习的原则,先看.json配置文件结合NuScenes官网、OpenMM的教程,学习如何创建自定义数据集。所以先忽略其他两个文件函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值