【Centerfusion】nuScenes数据转化为COCO格式CenterFusion/src/tools/convert_nuScenes.py

# Copyright (c) Xingyi Zhou. All Rights Reserved
'''
nuScenes pre-processing script.
This file convert the nuScenes annotation into COCO format.
'''
import os
import json
import numpy as np
import cv2
import copy
import matplotlib.pyplot as plt
from nuscenes.nuscenes import NuScenes
from nuscenes.utils.geometry_utils import BoxVisibility, transform_matrix
from nuScenes_lib.utils_kitti import KittiDB
from nuscenes.eval.detection.utils import category_to_detection_name
from pyquaternion import Quaternion

import _init_paths
from utils.ddd_utils import compute_box_3d, project_to_image, alpha2rot_y
from utils.ddd_utils import draw_box_3d, unproject_2d_to_3d
from utils.pointcloud import RadarPointCloudWithVelocity as RadarPointCloud
from nuScenes_lib.utils_radar import map_pointcloud_to_image
import time

DATA_PATH = '../../data/nuscenes/'
'''
这个路径,也就是 nuscenes 数据集的路径
它其实就是在 CenterFusion-master/data/nuscenes 这个路径下。
'''

OUT_PATH = DATA_PATH + 'annotations'
'''
这个路径,也就是最后 COCO 格式的文件输出的路径
得到的值为 '../../data/nuscenes/annotations'
'''

SPLITS = {
          'mini_val': 'v1.0-mini',
          'mini_train': 'v1.0-mini',
          'train': 'v1.0-trainval',
          'val': 'v1.0-trainval',
          'test': 'v1.0-test',
          }
'''
划分数据集
从 v1.0-mini 划分出训练集 mini_train 和测试集 min_val
从 v1.0-trainval 划分出训练集 train 和测试集 val
从 test 划分出验证集 test(换个名称而已)
'''

DEBUG = False
'''
就是赋个 False 值
'''

CATS = ['car', 'truck', 'bus', 'trailer', 'construction_vehicle',
        'pedestrian', 'motorcycle', 'bicycle', 'traffic_cone', 'barrier']
'''
定义一些目标物体类别,有车、树、公交车等等
'''

SENSOR_ID = {'RADAR_FRONT': 7, 'RADAR_FRONT_LEFT': 9,
  'RADAR_FRONT_RIGHT': 10, 'RADAR_BACK_LEFT': 11,
  'RADAR_BACK_RIGHT': 12,  'LIDAR_TOP': 8,
  'CAM_FRONT': 1, 'CAM_FRONT_RIGHT': 2,
  'CAM_BACK_RIGHT': 3, 'CAM_BACK': 4, 'CAM_BACK_LEFT': 5,
  'CAM_FRONT_LEFT': 6}
'''
自定义 12 种传感器的 id
'''

USED_SENSOR = ['CAM_FRONT', 'CAM_FRONT_RIGHT',
  'CAM_BACK_RIGHT', 'CAM_BACK', 'CAM_BACK_LEFT',
  'CAM_FRONT_LEFT']
'''
定义 CenterFusion 项目需要使用的相机传感器
'''

RADARS_FOR_CAMERA = {
  'CAM_FRONT_LEFT':  ["RADAR_FRONT_LEFT", "RADAR_FRONT"],
  'CAM_FRONT_RIGHT': ["RADAR_FRONT_RIGHT", "RADAR_FRONT"],
  'CAM_FRONT':       ["RADAR_FRONT_RIGHT", "RADAR_FRONT_LEFT", "RADAR_FRONT"],
  'CAM_BACK_LEFT':   ["RADAR_BACK_LEFT", "RADAR_FRONT_LEFT"],
  'CAM_BACK_RIGHT':  ["RADAR_BACK_RIGHT", "RADAR_FRONT_RIGHT"],
  'CAM_BACK':        ["RADAR_BACK_RIGHT","RADAR_BACK_LEFT"]}
'''
关联相机与毫米波雷达
'''
NUM_SWEEPS = 3
'''
annotations_3sweeps中的数值
'''

suffix1 = '_{}sweeps'.format(NUM_SWEEPS) if NUM_SWEEPS > 1 else ''
OUT_PATH = OUT_PATH + suffix1 + '/'
'''
其实就是改变 OUT_PATH 的值,在前面赋值的基础上,多加了个尾巴
现在值为 '../../data/nuscenes/annotations_3sweeps'
'''

CAT_IDS = {v: i + 1 for i, v in enumerate(CATS)}
'''
循环自定义目标物体类别的 id
结果就是 CAT_IDS = {'car':1, 'truck':2, 'bus':3, ...}
enumerate() 函数就是枚举
'''

def _rot_y2alpha(rot_y, x, cx, fx):
    """
    Get rotation_y by alpha + theta - 180
    alpha : Observation angle of object, ranging [-pi..pi]
    x : Object center x to the camera center (x-W/2), in pixels
    rotation_y : Rotation ry around Y-axis in camera coordinates [-pi..pi]
    """
    """
    Get rotation_y by alpha + theta - 180
    alpha : 目标观测角度,范围 [-pi..pi]
    x : 物体中心 x 到相机中心 (x- w /2),单位为像素
    rotation_y : 在摄像机坐标中绕 y 轴旋转 ry [-pi..pi]
    """
    alpha = rot_y - np.arctan2(x - cx, fx)
    '''
    np.arctan2(x,y):根据坐标点x,y得出角度,范围[-pi..pi]
    '''
    if alpha > np.pi:
      alpha -= 2 * np.pi
    if alpha < -np.pi:
      alpha += 2 * np.pi
    return alpha
    '''
    这个函数根据 box 的数据,计算得出 box 的观测角度
    '''

def _bbox_inside(box1, box2):
  return box1[0] > box2[0] and box1[0] + box1[2] < box2[0] + box2[2] and \
         box1[1] > box2[1] and box1[1] + box1[3] < box2[1] + box2[3]

ATTRIBUTE_TO_ID = {
  '': 0, 'cycle.with_rider' : 1, 'cycle.without_rider' : 2,
  'pedestrian.moving': 3, 'pedestrian.standing': 4, 
  'pedestrian.sitting_lying_down': 5,
  'vehicle.moving': 6, 'vehicle.parked': 7, 
  'vehicle.stopped': 8}
'''
自定义目标物体状态 id
'''

def main():
  if not os.path.exists(OUT_PATH):
    os.mkdir(OUT_PATH)
  '''
  如果没有路径 OUT_PATH ,则创建
  '''
  for split in SPLITS:
    '''
    第一个 for 循环,代码从91-313行
    共需要循环 5 次,因为划分了 5 个数据集
    即 mini_train、mini_val、train、val、test
    '''
    # data_path = DATA_PATH + '{}/'.format(SPLITS[split])
    data_path = DATA_PATH
    nusc = NuScenes(
      version=SPLITS[split], dataroot=data_path, verbose=True)
    '''
     为划分的 split 数据集创建 NuScenes 对象
     NuScenes 类来自于 CenterFusion-master/src/tools/nuscenes-devkit/python-sdk/nuscenes/nuscenes.py
     其中 verbose=True ,表示加载时打印状态消息
     '''
    out_path = OUT_PATH + '{}.json'.format(split)
    '''
    输出为 .json 文件
    也就是:
    mini_trian.json
    mini_val.json
    ......
    '''
    categories_info = [{'name': CATS[i], 'id': i + 1} for i in range(len(CATS))]
    '''
    这是 coco 格式其中一个字段,记录目标物体的种类及 id ,最后的结果如下:
    categories_info = [
      {'name':'car','id':1},
      {'name':'truck','id':2},
      {'name':'bus','id':3},
      ......
    ]
    '''
    ret = {'images': [], 'annotations': [], 'categories': categories_info, 
           'videos': [], 'attributes': ATTRIBUTE_TO_ID, 'pointclouds': []}
    '''
    创建coco格式数据,从这里可以看出 coco 格式如下:
    {
      "images": [],
      "annotations": [],
      "categories": [categories_info],    #记录所有目标物体的类别
      "videos": [],                      #记录所有的场景
      "attributes": [ATTRIBUTE_TO_ID],
      "pointclouds": []
    }
    到现在为止,已经赋值了 categories 的值和 attributes 的值
    '''
    num_images = 0
    num_anns = 0
    num_videos = 0
    '''
    第一个:所有相机拍摄的全部图片数量计数
    第二个:一张图片中有效的 box 个数
    第三个:可以理解为场景的数量计数
    '''

    # A "sample" in nuScenes refers to a timestamp with 6 cameras and 1 LIDAR.
    for sample in nusc.sample:
      '''
      内嵌的第二层 for 循环,代码从 105-294 行,前面一层是划分成 5 个数据集
      了解过 nuscenes 数据集格式的应该知道吧,这里再来梳理下关系
      sample 其实就是时间戳,一个 scene 中有多个 sample
      这里的意思就是遍历 nuscenes 中所有的时间戳 sample
      '''
      scene_name = nusc.get('scene', sample['scene_token'])['name']
      '''
      根据 sample 的属性 scene_token 获取当前时间戳所属的 scene 的名称,比如 scene-0001
      get() 函数在 CenterFusion/src/tools/nuscenes-devkit/python-sdk/nuscenes/nuscenes.py 208 行
      '''
      if not (split in ['test']) and \
        not (scene_name in SCENE_SPLITS[split]):
        continue
      '''
      如果当前划分的数据集,也就是变量 split 的值不是 'test' 且获取到的场景名 scene_name 不在全局变量 SCENE_SPLITS[split] 中
      则直接跳过后面代码,开始下一次的循环
      '''
      if sample['prev'] == '':
        '''
        首先看判断条件 sample 字段中 prev 是它其中一个属性
        sample 的 prev 属性指向同一个场景它前面一个时间戳
        sample 的 next 属性指向同一个场景它后面一个时间戳
        如果 prev 为空,那么说明当前这个时间戳是它所属 scene 中的第一个时间戳
        '''
        print('scene_name', scene_name)
        num_videos += 1
        '''
        每检测到一个场景中的首个时间戳,它就 +1,所以可以理解为场景的数量
        '''
        ret['videos'].append({'id': num_videos, 'file_name': scene_name})
        '''
        往 coco 格式中的 videos 字段循环添加字典,每有一个场景,就有一个字典,这里把场景数量当成了场景 id
        最后第二层 for 循环结束后 videos 中的数据如下:
        videos = [
          {'id':1,'file_name':'scene-0001'},
          {'id':2,'file_name':'scene-0002'},
          ......]
        '''
        frame_ids = {k: 0 for k in sample['data']}
        '''
        sample 的 data 属性中每个传感器的值为 sample_data 的 token 外键,也就是指向一个传感器数据 sample_data
        这里将它初始化,它的结果为:
        frame_ids = {
          'RADAR_FRONT':0,
          'RADAR_FRONT_LEFT':0,
          'CAM_FRONT_RIGHT':0,
          'CAM_BACK_LEFT':0,
          ......          
        }
        用来记录一个 scene 中每个相机传感器拍摄的图片数量,初始化为 0
        '''
        track_ids = {}
        '''
        用来记录一个 scene 中持续追踪的 box
        记住,frame_ids 和 track_ids 是当前时间戳为首位时初始化的变量
        当前时间戳不是首位时,没有初始化这个两个变量
        '''
      # We decompose a sample into 6 images in our case.
      for sensor_name in sample['data']:
        '''
        内嵌的第三层 for 循环,代码从 117-294 行,前面一层是遍历所有的时间戳
        这里是遍历当前时间戳中的所有传感器(相机、激光雷达、毫米波雷达)
        '''
        if sensor_name in USED_SENSOR:
          '''
          如果当前传感器是相机传感器,则执行
          '''
          image_token = sample['data'][sensor_name]
          '''
          获取 sample_data 的 token 外键,前面也说过
          '''
          image_data = nusc.get('sample_data', image_token)
          '''
          根据 sample_data 的 token 外键获取当前时间戳下的当前传感器所对应的数据,即 sample_data
          '''
          num_images += 1
          '''
          所有相机拍摄的全部图片数量 +1
          '''

          # Complex coordinate transform. This will take time to understand.
          sd_record = nusc.get('sample_data', image_token)
          '''
          根据 sample_data 的 token 外键获取当前的 sample_data 字段
          和 image_data 的内容一样
          '''

          cs_record = nusc.get(
              'calibrated_sensor', sd_record['calibrated_sensor_token'])
          '''
          根据当前 sample_data 中的 calibrated_sensor_token 外键
          获取当前数据所对应时刻的传感器姿态数据 calibrated_sensor ,由于官网没有说清楚这个字段的意义,这里多啰嗦一下
          calibrated_sensor {
            'token':             //<str> -- Unique record identifier.
            'sensor_token':      //<str> -- Foreign key pointing to the sensor type.
            'translation':       //<float> [3] -- 坐标系原点以米为单位:x, y, z
            'rotation':          //<float> [4] -- 坐标系定位为四元数:w, x, y, z
            'camera_intrinsic':  //<float> [3, 3] -- 内在的相机校正,空出来的传感器不是摄像头
          }
          属性 translation 表示在汽车车身坐标系中传感器位置;
          属性 rotation 是一个四元素,表示在汽车车身坐标系中传感器的姿态数据(姿态数据:刚体在空间中的姿态)
          '''
          pose_record = nusc.get('ego_pose', sd_record['ego_pose_token'])
          '''
          根据当前 sample_data 中的 ego_pose_token 外键
          获取当前数据所对应时刻的车辆姿态数据 ego_pose
          ego_pose {
            'token':             //<str> -- Unique record identifier.
            'translation':       //<float> [3] -- 坐标系原点以米为单位:x, y, z(注意 z 总是 0)
            'rotation':          //<float> [4] -- 坐标系定位为四元数:w, x, y, z
            'timestamp':         //<int> -- Unix时间戳
          }
          属性 translation 表示在全局坐标系中车辆位置;
          属性 rotation 是一个四元素,表示在全局坐标系中车辆的姿态数据
          不要跟上面的混淆,两个坐标系不同
          '''

          global_from_car = transform_matrix(pose_record['translation'],
                                             Quaternion(pose_record['rotation']), inverse=False)
          '''
          根据车辆姿态数据(全局坐标系中)计算并返回一个变换矩阵,inverse=False 表示不计算逆旋转矩阵(这里矩阵的名称没写错,往下看就知道了)
          这里传递参数为 ego_pose 中的 translation 值和 rotation 值
          transform_matrix() 函数在 CenterFusion/src/tools/nuscenes-devkit/python-sdk/nuscenes/utils/geometry_utils.py 87 行
          '''
          car_from_sensor = transform_matrix(
              cs_record['translation'], Quaternion(cs_record['rotation']),
              inverse=False)
          '''
          这里就是将传感器的姿态数据(车身坐标系中),转化为变化矩阵,格式如上,公式也如上
          '''

          trans_matrix = np.dot(global_from_car, car_from_sensor)
          '''
          np.dot()是将两个变换矩阵进行相乘
          其中的参数一个是车辆相对于全局坐标系的变换矩阵、一个是传感器相对于车身坐标系的变换矩阵
          乘积的结果含义:传感器相对于全局坐标系的变换矩阵
          '''

          vel_global_from_car = transform_matrix(np.array([0, 0, 0]),
                                                 Quaternion(pose_record['rotation']), inverse=False)
          '''
          需要注意的是,第一个参数是数组([0,0,0]),第二个参数是车辆在全局坐标系中的姿态
          带入到 transform_matrix 中可以发现,vel_global_form_car 的值为上面公式(2)
          这样的一个 4×4 矩阵表示只旋转,不平移
          '''

          vel_car_from_sensor = transform_matrix(np.array([0, 0, 0]),
                                                 Quaternion(cs_record['rotation']), inverse=False)
          '''
          这里第二个参数是相对于车身坐标系中传感器的姿态
          '''

          velocity_trans_matrix = np.dot(vel_global_from_car, vel_car_from_sensor)
          '''
          两个 4×4 的变换矩阵相乘,结果含义:传感器相对于全局坐标系的旋转变换
          '''

          _, boxes, camera_intrinsic = nusc.get_sample_data(
              image_token, box_vis_level=BoxVisibility.ANY)
          '''
          根据外键 image_token 获取当前传感器的数据信息字段 sample_data,其中 box_vis_level 设置框的可见性,返回以下值:
          data_path:文件存放的绝对路径(这里没有获取这个值)
          boxes:box 列表
          cam_intrinsic:当前相机传感器的标定内参(3×3 的矩阵)
          get_sample_data() 函数在 CenterFusion/src/tools/nuscenes-devkit/python-sdk/nuscenes/nuscenes.py 249 行
          '''
          calib = np.eye(4, dtype=np.float32)
          calib[:3, :3] = camera_intrinsic
          calib = calib[:3]
          '''
          生成 float32 型的 4×4 的对角矩阵 calib
          然后将 calib 的前三行、前三列赋值 camera_intrinsic(相机内参)
          最后再将前三行赋值给 calib,得到一个 3×4 的矩阵,在后面 box 会用到
          '''
          frame_ids[sensor_name] += 1
          '''
          当前相机 sensor_name 拍摄的图片数量 +1
          '''
          all_radar_pcs = RadarPointCloud(np.zeros((18, 0)))
          '''
          初始化雷达点云图,np.zeros((18,0))=[] 是生成一个空数组
          RadarPointCloud 类在 CenterFusion/src/lib/utils/pointcloud.py 54 行
          '''

          for radar_channel in RADARS_FOR_CAMERA[sensor_name]:
            '''
            遍历当前相机传感器 sensor_name 所关联的雷达传感器
            '''
            radar_pcs, _ = RadarPointCloud.from_file_multisweep(nusc,
            sample, radar_channel, sensor_name, NUM_SWEEPS)
            '''
            根据 sample、雷达传感器名、相机传感器名获取相对应的聚合的点云图
            from_file_multisweep() 函数在 CenterFusion/src/lib/utils/pointcloud.py 70 行
            '''

            all_radar_pcs.points = np.hstack((all_radar_pcs.points, radar_pcs.points))
            '''
            循环水平拼接雷达点云图(就是将一个场景中分块的雷达点云图拼接成一个场景中完整的点云图)
            np.hstack() 按水平方向(列顺序)拼接 2 个或多个图像,图像的高度(数组的行)必须相同
            np.vstack() 按垂直方向(行顺序)拼接 2 个或多个图像,图像的宽度(数组的列)必须相同
            '''
          image_info = {'id': num_images,
                      'file_name': image_data['filename'],
                      'calib': calib.tolist(),
                      'video_id': num_videos,
                      'frame_id': frame_ids[sensor_name],
                      'sensor_id': SENSOR_ID[sensor_name],
                      'sample_token': sample['token'],
                      'trans_matrix': trans_matrix.tolist(),
                      'velocity_trans_matrix': velocity_trans_matrix.tolist(),
                      'width': sd_record['width'],
                      'height': sd_record['height'],
                      'pose_record_trans': pose_record['translation'],
                      'pose_record_rot': pose_record['rotation'],
                      'cs_record_trans': cs_record['translation'],
                      'cs_record_rot': cs_record['rotation'],
                      'radar_pc': all_radar_pcs.points.tolist(),
                      'camera_intrinsic': camera_intrinsic.tolist()}
          '''
          创建 COCO 格式的 image_info 的字段,将上面所产生的结果储存在了 image_info 字段中了,下面解释下每个属性的意义:
          'id':图片的id(基于全部的相机图片)
          'file_name':图片的文件名
          'calib':当前传感器的内参 3×4 矩阵
          'video_id':自定义的图片所属场景 scene 的 id (外键)
          'frame_id':将当前相机传感器所拍摄的图片数量作为自定义 id
          'sensor_id':自定义的传感器外键
          'sample_token':sample 外键
          'trans_matrix':传感器相对于全局坐标系姿态复合变化矩阵列表化
          'velocity_trans_matrix':传感器相对于全局坐标系旋转矩阵列表化
          'width':图片的宽度
          'height':图片的高度
          'pose_record_trans':车辆的原始 translation 数据
          'pose_record_rot':车辆的原始四元素 rotation 数据
          'cs_record_trans':传感器的原始 translation 数据
          'cs_record_rot':传感器的原始 rotation 数据
          'radar_pc':当前相机传感器所对应的完整雷达点云图列表化
          'camera_intrinsic':box 在当前相机传感器的坐标框架列表化
          '''

          ret['images'].append(image_info)
          '''
          将 images 字段添加进 COCO 格式中
          '''

          anns = []
          for box in boxes:
            '''
            循环读取当前相机传感器拍摄的一张图片中的 box 列表
            '''
            det_name = category_to_detection_name(box.name)
            '''
            获取 box 所属的目标类,比如移动的人、站着的人都属于人这一类
            category_to_detection_name() 函数在 CenterFusion/src/tools/nuscenes-devkit/python-sdk/nuscenes/eval/detection/utils.py 7 行
            '''
            if det_name is None:
              continue
            '''
            如果 box 没有所属的类,则直接检测下一个 box
            '''
            num_anns += 1
            '''
            有所属的类,是有效 box ,个数 +1
            '''
            v = np.dot(box.rotation_matrix, np.array([1, 0, 0]))
            '''
            由于 box 列表中的姿态数据 orientation 是一个四元素
            这里使用函数 rotation_matrix() 将四元素转换为旋转矩阵
            旋转矩阵再和 [1,0,0] 点乘,得到旋转矩阵的第一列
            rotation_matrix() 在 CenterFusion-master/src/tools/nuscenes-devkit/python-sdk/nuscenes/utils/data_classes.py 580 行
            '''
            yaw = -np.arctan2(v[2], v[0])
            '''
            arctan2() 函数是反正切函数第二种形式,可以参考博客:https://blog.csdn.net/qq_37083038/article/details/121264809
            这里将 Box 旋转矩阵第一列的第一个元素和第三个元素进行反正切计算
            得到的结果是 Box 绕相机坐标系的 Y 轴旋转的角度(这个角度也被叫做观测角)
            '''
            box.translate(np.array([0, box.wlh[2] / 2, 0]))
            '''
            通过查看 Box 类的定义,可知 wlh 是一个包含 box 长宽高的数组 array[长、宽、高]
            再查看 translate() 函数在 CenterFusion/src/tools/nuscenes-devkit/python-sdk/nuscenes/utils/data_classes.py 587 行
            可以知道这里是将 box 的中心点沿着 y 轴平移了 box 长度的一半
            '''
            category_id = CAT_IDS[det_name]
            '''
            根据 det_name 获取目标物体类别自定义的 id (全局变量中定义了)
            '''
            amodel_center = project_to_image(
              np.array([box.center[0], box.center[1] - box.wlh[2] / 2, box.center[2]],
                np.float32).reshape(1, 3), calib)[0].tolist()
            '''
            这句有点长,首先不管参数,它的代码为 project_to_image(参数1,参数2)[0].tolish
            参数1:np.array([box.center[0], box.center[1] - box.wlh[2] / 2, box.center[2]], np.float32).reshape(1, 3)
              reshape() 函数用于在不更改数据的情况下为数组赋予新形状
              这个参数的值其实就是 box 原始中心点(前面有平移过) 1×3 矩阵
            参数2:calib
              前面生成的一个 3×4 的矩阵(相机的内参)
            返回一个 1×2 的矩阵,并取矩阵的第一行,也就是 1×2 的矩阵,将其 tolist() 列表化
            其实这个矩阵就是 box 中心点在图像上显示的位置
            project_to_image() 函数在 CenterFusion-master/src/lib/utils/ddd_utils.py 36 行
            '''
            sample_ann = nusc.get(
                'sample_annotation', box.token)
            '''
            根据 box.token 外键,获取 sample_annotation 数据
            这里就不贴出 sample_annotation 字段的详细结构了
            '''

            instance_token = sample_ann['instance_token']
            '''
            获取 sample_annotation 中的 instance_token 外键
            '''

            if not (instance_token in track_ids):
                track_ids[instance_token] = len(track_ids) + 1
            '''
            需要检测的目标物体不在记录中,则记录需要检测的目标物体,并自定义 id
            '''

            attribute_tokens = sample_ann['attribute_tokens']
            '''
            获取 attribute_tokens 外键
            '''

            attributes = [nusc.get('attribute', att_token)['name'] \
                          for att_token in attribute_tokens]
            '''
            获取 attribute 字段中的 name 属性
            它的值是目标物体的状态,比如人移动、人站立等
            它的长度是 1
            '''

            att = '' if len(attributes) == 0 else attributes[0]
            '''
            如果 attributes 长度为 0,则 att = '',否则 att = attributes[0]
            '''

            if len(attributes) > 1:
                print(attributes)
                import pdb;
                pdb.set_trace()
            '''
            这里,我懵了,attributes 的长度是 1 ,但又为啥写这句
            在整个代码的执行过程中,都没有执行这个 if 语句
            '''

            track_id = track_ids[instance_token]
            '''
            根据 instance_token 外键获取记录中被检测目标物体自定义的 id
            '''

            vel = nusc.box_velocity(box.token).tolist()
            '''
            根据 box.token 外键获取 box 的速度
            其中 vel 的值为列表,里面由三个值,分别表示 X/Y/Z 轴方向上的速度
            box_velocity() 函数在 CenterFusion/src/tools/nuscenes-devkit/python-sdk/nuscenes/nuscenes.py 第 380 行
            '''

            vel_cam = np.dot(np.linalg.inv(velocity_trans_matrix),
                             np.array([vel[0], vel[1], vel[2], 0], np.float32)).tolist()
            '''
            np.linalg.inv() 函数:求 velocity_trans_matrix 传感器相对于全局坐标系的旋转矩阵的逆矩阵
            再与 box 的速度矩阵点乘并列表化
            得到 box 在摄像机坐标中的速度
            '''

            ann = {
                'id': num_anns,
                'image_id': num_images,
                'category_id': category_id,
                'dim': [box.wlh[2], box.wlh[0], box.wlh[1]],
                'location': [box.center[0], box.center[1], box.center[2]],
                'depth': box.center[2],
                'occluded': 0,
                'truncated': 0,
                'rotation_y': yaw,
                'amodel_center': amodel_center,
                'iscrowd': 0,
                'track_id': track_id,
                'attributes': ATTRIBUTE_TO_ID[att],
                'velocity': vel,
                'velocity_cam': vel_cam
            }
            '''
            coco 格式的 ann 字段,一张图片中一个 box 对应一个 ann 字段,属性含义如下:
            'id': 有效 box 的 id
            'image_id': 当前 box 所属的图片 id
            'category_id': box 所属类别的 id
            'dim': box 的长宽高
            'location': box 的中心点
            'depth': box 的深度
            'occluded': 暂时不知道
            'truncated': 暂时不知道
            'rotation_y': 绕相机坐标系 y 轴旋转(观测角)
            'amodel_center': box 在图像中的中心点位置
            'iscrowd': 道路是否拥挤,0 为不拥挤 1 为拥挤
            'track_id': 被检测目标物体 box 的 id
            'attributes': 目标物体状态的 id
            'velocity': 目标物体在全局坐标系的速度
            'velocity_cam': box 在相机坐标系中的速度
            '''

            bbox = KittiDB.project_kitti_box_to_image(
                copy.deepcopy(box), camera_intrinsic, imsize=(1600, 900))
            '''
            这个函数在 CenterFusion/src/tools/nuScenes_lib/utils_kitti.py 154 行
            copy.deepcopy() 函数:深拷贝,拷贝对象及其子对象
            camera_intrinsic:相加传感器内参(是一个投影矩阵)
            imsize:图片像素
            这个函数的作用是在图像上面作出 bbox 框,所以 bbox 的值为图像中的位置
            '''

            alpha = _rot_y2alpha(yaw, (bbox[0] + bbox[2]) / 2,
                                 camera_intrinsic[0, 2], camera_intrinsic[0, 0])
            '''
            调用函数,获取 box 在相机坐标系中的局部旋转角度 alpha
            '''

            ann['bbox'] = [bbox[0], bbox[1], bbox[2] - bbox[0], bbox[3] - bbox[1]]
            ann['area'] = (bbox[2] - bbox[0]) * (bbox[3] - bbox[1])
            ann['alpha'] = alpha
            anns.append(ann)
            '''
            给 ann 添加三个属性:bbox(box 数据)、area(box 覆盖的区域)、alpha(局部旋转角)
            最后为 anns 字段添加 ann
            '''

          visable_anns = []
          '''
          初始化 visable_anns 列表
          '''

          for i in range(len(anns)):
              vis = True
              for j in range(len(anns)):
                  if anns[i]['depth'] - min(anns[i]['dim']) / 2 > \
                          anns[j]['depth'] + max(anns[j]['dim']) / 2 and \
                          _bbox_inside(anns[i]['bbox'], anns[j]['bbox']):
                      vis = False
                      break
              if vis:
                  visable_anns.append(anns[i])
              else:
                  pass
          '''
          遍历所有的 box 对应的 ann 字段,并根据两个 box 的 depth 和 dim 属性判断有效 box
          '''

          for ann in visable_anns:
              ret['annotations'].append(ann)
          '''
          给 annotations 字段添加 ann 属性
          '''

        print('reordering images')
        images = ret['images']
        '''
        获取当前 COCO 格式的数据集中的 images 字段
        '''

        video_sensor_to_images = {}
        for image_info in images:
            tmp_seq_id = image_info['video_id'] * 20 + image_info['sensor_id']
            '''
            遍历数据集中的所有相机图片信息 image_info
            tmp_seq_id = 图片对应的场景id × 20 + 图片对应的传感器id
            '''

            if tmp_seq_id in video_sensor_to_images:
                video_sensor_to_images[tmp_seq_id].append(image_info)
            else:
                video_sensor_to_images[tmp_seq_id] = [image_info]
            '''
            对 image_info 进行重新排序
            '''

        ret['images'] = []
        for tmp_seq_id in sorted(video_sensor_to_images):
            ret['images'] = ret['images'] + video_sensor_to_images[tmp_seq_id]
        '''
        sorted() 函数:可以对所有可迭代的对象进行排序操作
        将排序后的 video_sensor_to_images 重新赋给 images 字段
        '''

        print('{} {} images {} boxes'.format(
            split, len(ret['images']), len(ret['annotations'])))
        print('out_path', out_path)
        json.dump(ret, open(out_path, 'w'))
        '''
        json.dump() 函数:将 Python 对象转换为适当的 json 对象
        '''

# Official train/ val split from 
# https://github.com/nutonomy/nuscenes-devkit/blob/master/python-sdk/nuscenes/utils/splits.py
'''
这后面的一大坨的部分,定义了每个数据集中划分了哪些场景
'''
SCENE_SPLITS = {
'train':
    ['scene-0001', 'scene-0002', 'scene-0004', 'scene-0005', 'scene-0006', 'scene-0007', 'scene-0008', 'scene-0009',
     'scene-0010', 'scene-0011', 'scene-0019', 'scene-0020', 'scene-0021', 'scene-0022', 'scene-0023', 'scene-0024',
     'scene-0025', 'scene-0026', 'scene-0027', 'scene-0028', 'scene-0029', 'scene-0030', 'scene-0031', 'scene-0032',
     'scene-0033', 'scene-0034', 'scene-0041', 'scene-0042', 'scene-0043', 'scene-0044', 'scene-0045', 'scene-0046',
     'scene-0047', 'scene-0048', 'scene-0049', 'scene-0050', 'scene-0051', 'scene-0052', 'scene-0053', 'scene-0054',
     'scene-0055', 'scene-0056', 'scene-0057', 'scene-0058', 'scene-0059', 'scene-0060', 'scene-0061', 'scene-0062',
     'scene-0063', 'scene-0064', 'scene-0065', 'scene-0066', 'scene-0067', 'scene-0068', 'scene-0069', 'scene-0070',
     'scene-0071', 'scene-0072', 'scene-0073', 'scene-0074', 'scene-0075', 'scene-0076', 'scene-0120', 'scene-0121',
     'scene-0122', 'scene-0123', 'scene-0124', 'scene-0125', 'scene-0126', 'scene-0127', 'scene-0128', 'scene-0129',
     'scene-0130', 'scene-0131', 'scene-0132', 'scene-0133', 'scene-0134', 'scene-0135', 'scene-0138', 'scene-0139',
     'scene-0149', 'scene-0150', 'scene-0151', 'scene-0152', 'scene-0154', 'scene-0155', 'scene-0157', 'scene-0158',
     'scene-0159', 'scene-0160', 'scene-0161', 'scene-0162', 'scene-0163', 'scene-0164', 'scene-0165', 'scene-0166',
     'scene-0167', 'scene-0168', 'scene-0170', 'scene-0171', 'scene-0172', 'scene-0173', 'scene-0174', 'scene-0175',
     'scene-0176', 'scene-0177', 'scene-0178', 'scene-0179', 'scene-0180', 'scene-0181', 'scene-0182', 'scene-0183',
     'scene-0184', 'scene-0185', 'scene-0187', 'scene-0188', 'scene-0190', 'scene-0191', 'scene-0192', 'scene-0193',
     'scene-0194', 'scene-0195', 'scene-0196', 'scene-0199', 'scene-0200', 'scene-0202', 'scene-0203', 'scene-0204',
     'scene-0206', 'scene-0207', 'scene-0208', 'scene-0209', 'scene-0210', 'scene-0211', 'scene-0212', 'scene-0213',
     'scene-0214', 'scene-0218', 'scene-0219', 'scene-0220', 'scene-0222', 'scene-0224', 'scene-0225', 'scene-0226',
     'scene-0227', 'scene-0228', 'scene-0229', 'scene-0230', 'scene-0231', 'scene-0232', 'scene-0233', 'scene-0234',
     'scene-0235', 'scene-0236', 'scene-0237', 'scene-0238', 'scene-0239', 'scene-0240', 'scene-0241', 'scene-0242',
     'scene-0243', 'scene-0244', 'scene-0245', 'scene-0246', 'scene-0247', 'scene-0248', 'scene-0249', 'scene-0250',
     'scene-0251', 'scene-0252', 'scene-0253', 'scene-0254', 'scene-0255', 'scene-0256', 'scene-0257', 'scene-0258',
     'scene-0259', 'scene-0260', 'scene-0261', 'scene-0262', 'scene-0263', 'scene-0264', 'scene-0283', 'scene-0284',
     'scene-0285', 'scene-0286', 'scene-0287', 'scene-0288', 'scene-0289', 'scene-0290', 'scene-0291', 'scene-0292',
     'scene-0293', 'scene-0294', 'scene-0295', 'scene-0296', 'scene-0297', 'scene-0298', 'scene-0299', 'scene-0300',
     'scene-0301', 'scene-0302', 'scene-0303', 'scene-0304', 'scene-0305', 'scene-0306', 'scene-0315', 'scene-0316',
     'scene-0317', 'scene-0318', 'scene-0321', 'scene-0323', 'scene-0324', 'scene-0328', 'scene-0347', 'scene-0348',
     'scene-0349', 'scene-0350', 'scene-0351', 'scene-0352', 'scene-0353', 'scene-0354', 'scene-0355', 'scene-0356',
     'scene-0357', 'scene-0358', 'scene-0359', 'scene-0360', 'scene-0361', 'scene-0362', 'scene-0363', 'scene-0364',
     'scene-0365', 'scene-0366', 'scene-0367', 'scene-0368', 'scene-0369', 'scene-0370', 'scene-0371', 'scene-0372',
     'scene-0373', 'scene-0374', 'scene-0375', 'scene-0376', 'scene-0377', 'scene-0378', 'scene-0379', 'scene-0380',
     'scene-0381', 'scene-0382', 'scene-0383', 'scene-0384', 'scene-0385', 'scene-0386', 'scene-0388', 'scene-0389',
     'scene-0390', 'scene-0391', 'scene-0392', 'scene-0393', 'scene-0394', 'scene-0395', 'scene-0396', 'scene-0397',
     'scene-0398', 'scene-0399', 'scene-0400', 'scene-0401', 'scene-0402', 'scene-0403', 'scene-0405', 'scene-0406',
     'scene-0407', 'scene-0408', 'scene-0410', 'scene-0411', 'scene-0412', 'scene-0413', 'scene-0414', 'scene-0415',
     'scene-0416', 'scene-0417', 'scene-0418', 'scene-0419', 'scene-0420', 'scene-0421', 'scene-0422', 'scene-0423',
     'scene-0424', 'scene-0425', 'scene-0426', 'scene-0427', 'scene-0428', 'scene-0429', 'scene-0430', 'scene-0431',
     'scene-0432', 'scene-0433', 'scene-0434', 'scene-0435', 'scene-0436', 'scene-0437', 'scene-0438', 'scene-0439',
     'scene-0440', 'scene-0441', 'scene-0442', 'scene-0443', 'scene-0444', 'scene-0445', 'scene-0446', 'scene-0447',
     'scene-0448', 'scene-0449', 'scene-0450', 'scene-0451', 'scene-0452', 'scene-0453', 'scene-0454', 'scene-0455',
     'scene-0456', 'scene-0457', 'scene-0458', 'scene-0459', 'scene-0461', 'scene-0462', 'scene-0463', 'scene-0464',
     'scene-0465', 'scene-0467', 'scene-0468', 'scene-0469', 'scene-0471', 'scene-0472', 'scene-0474', 'scene-0475',
     'scene-0476', 'scene-0477', 'scene-0478', 'scene-0479', 'scene-0480', 'scene-0499', 'scene-0500', 'scene-0501',
     'scene-0502', 'scene-0504', 'scene-0505', 'scene-0506', 'scene-0507', 'scene-0508', 'scene-0509', 'scene-0510',
     'scene-0511', 'scene-0512', 'scene-0513', 'scene-0514', 'scene-0515', 'scene-0517', 'scene-0518', 'scene-0525',
     'scene-0526', 'scene-0527', 'scene-0528', 'scene-0529', 'scene-0530', 'scene-0531', 'scene-0532', 'scene-0533',
     'scene-0534', 'scene-0535', 'scene-0536', 'scene-0537', 'scene-0538', 'scene-0539', 'scene-0541', 'scene-0542',
     'scene-0543', 'scene-0544', 'scene-0545', 'scene-0546', 'scene-0566', 'scene-0568', 'scene-0570', 'scene-0571',
     'scene-0572', 'scene-0573', 'scene-0574', 'scene-0575', 'scene-0576', 'scene-0577', 'scene-0578', 'scene-0580',
     'scene-0582', 'scene-0583', 'scene-0584', 'scene-0585', 'scene-0586', 'scene-0587', 'scene-0588', 'scene-0589',
     'scene-0590', 'scene-0591', 'scene-0592', 'scene-0593', 'scene-0594', 'scene-0595', 'scene-0596', 'scene-0597',
     'scene-0598', 'scene-0599', 'scene-0600', 'scene-0639', 'scene-0640', 'scene-0641', 'scene-0642', 'scene-0643',
     'scene-0644', 'scene-0645', 'scene-0646', 'scene-0647', 'scene-0648', 'scene-0649', 'scene-0650', 'scene-0651',
     'scene-0652', 'scene-0653', 'scene-0654', 'scene-0655', 'scene-0656', 'scene-0657', 'scene-0658', 'scene-0659',
     'scene-0660', 'scene-0661', 'scene-0662', 'scene-0663', 'scene-0664', 'scene-0665', 'scene-0666', 'scene-0667',
     'scene-0668', 'scene-0669', 'scene-0670', 'scene-0671', 'scene-0672', 'scene-0673', 'scene-0674', 'scene-0675',
     'scene-0676', 'scene-0677', 'scene-0678', 'scene-0679', 'scene-0681', 'scene-0683', 'scene-0684', 'scene-0685',
     'scene-0686', 'scene-0687', 'scene-0688', 'scene-0689', 'scene-0695', 'scene-0696', 'scene-0697', 'scene-0698',
     'scene-0700', 'scene-0701', 'scene-0703', 'scene-0704', 'scene-0705', 'scene-0706', 'scene-0707', 'scene-0708',
     'scene-0709', 'scene-0710', 'scene-0711', 'scene-0712', 'scene-0713', 'scene-0714', 'scene-0715', 'scene-0716',
     'scene-0717', 'scene-0718', 'scene-0719', 'scene-0726', 'scene-0727', 'scene-0728', 'scene-0730', 'scene-0731',
     'scene-0733', 'scene-0734', 'scene-0735', 'scene-0736', 'scene-0737', 'scene-0738', 'scene-0739', 'scene-0740',
     'scene-0741', 'scene-0744', 'scene-0746', 'scene-0747', 'scene-0749', 'scene-0750', 'scene-0751', 'scene-0752',
     'scene-0757', 'scene-0758', 'scene-0759', 'scene-0760', 'scene-0761', 'scene-0762', 'scene-0763', 'scene-0764',
     'scene-0765', 'scene-0767', 'scene-0768', 'scene-0769', 'scene-0786', 'scene-0787', 'scene-0789', 'scene-0790',
     'scene-0791', 'scene-0792', 'scene-0803', 'scene-0804', 'scene-0805', 'scene-0806', 'scene-0808', 'scene-0809',
     'scene-0810', 'scene-0811', 'scene-0812', 'scene-0813', 'scene-0815', 'scene-0816', 'scene-0817', 'scene-0819',
     'scene-0820', 'scene-0821', 'scene-0822', 'scene-0847', 'scene-0848', 'scene-0849', 'scene-0850', 'scene-0851',
     'scene-0852', 'scene-0853', 'scene-0854', 'scene-0855', 'scene-0856', 'scene-0858', 'scene-0860', 'scene-0861',
     'scene-0862', 'scene-0863', 'scene-0864', 'scene-0865', 'scene-0866', 'scene-0868', 'scene-0869', 'scene-0870',
     'scene-0871', 'scene-0872', 'scene-0873', 'scene-0875', 'scene-0876', 'scene-0877', 'scene-0878', 'scene-0880',
     'scene-0882', 'scene-0883', 'scene-0884', 'scene-0885', 'scene-0886', 'scene-0887', 'scene-0888', 'scene-0889',
     'scene-0890', 'scene-0891', 'scene-0892', 'scene-0893', 'scene-0894', 'scene-0895', 'scene-0896', 'scene-0897',
     'scene-0898', 'scene-0899', 'scene-0900', 'scene-0901', 'scene-0902', 'scene-0903', 'scene-0945', 'scene-0947',
     'scene-0949', 'scene-0952', 'scene-0953', 'scene-0955', 'scene-0956', 'scene-0957', 'scene-0958', 'scene-0959',
     'scene-0960', 'scene-0961', 'scene-0975', 'scene-0976', 'scene-0977', 'scene-0978', 'scene-0979', 'scene-0980',
     'scene-0981', 'scene-0982', 'scene-0983', 'scene-0984', 'scene-0988', 'scene-0989', 'scene-0990', 'scene-0991',
     'scene-0992', 'scene-0994', 'scene-0995', 'scene-0996', 'scene-0997', 'scene-0998', 'scene-0999', 'scene-1000',
     'scene-1001', 'scene-1002', 'scene-1003', 'scene-1004', 'scene-1005', 'scene-1006', 'scene-1007', 'scene-1008',
     'scene-1009', 'scene-1010', 'scene-1011', 'scene-1012', 'scene-1013', 'scene-1014', 'scene-1015', 'scene-1016',
     'scene-1017', 'scene-1018', 'scene-1019', 'scene-1020', 'scene-1021', 'scene-1022', 'scene-1023', 'scene-1024',
     'scene-1025', 'scene-1044', 'scene-1045', 'scene-1046', 'scene-1047', 'scene-1048', 'scene-1049', 'scene-1050',
     'scene-1051', 'scene-1052', 'scene-1053', 'scene-1054', 'scene-1055', 'scene-1056', 'scene-1057', 'scene-1058',
     'scene-1074', 'scene-1075', 'scene-1076', 'scene-1077', 'scene-1078', 'scene-1079', 'scene-1080', 'scene-1081',
     'scene-1082', 'scene-1083', 'scene-1084', 'scene-1085', 'scene-1086', 'scene-1087', 'scene-1088', 'scene-1089',
     'scene-1090', 'scene-1091', 'scene-1092', 'scene-1093', 'scene-1094', 'scene-1095', 'scene-1096', 'scene-1097',
     'scene-1098', 'scene-1099', 'scene-1100', 'scene-1101', 'scene-1102', 'scene-1104', 'scene-1105', 'scene-1106',
     'scene-1107', 'scene-1108', 'scene-1109', 'scene-1110'],
'val':
    ['scene-0003', 'scene-0012', 'scene-0013', 'scene-0014', 'scene-0015', 'scene-0016', 'scene-0017', 'scene-0018',
     'scene-0035', 'scene-0036', 'scene-0038', 'scene-0039', 'scene-0092', 'scene-0093', 'scene-0094', 'scene-0095',
     'scene-0096', 'scene-0097', 'scene-0098', 'scene-0099', 'scene-0100', 'scene-0101', 'scene-0102', 'scene-0103',
     'scene-0104', 'scene-0105', 'scene-0106', 'scene-0107', 'scene-0108', 'scene-0109', 'scene-0110', 'scene-0221',
     'scene-0268', 'scene-0269', 'scene-0270', 'scene-0271', 'scene-0272', 'scene-0273', 'scene-0274', 'scene-0275',
     'scene-0276', 'scene-0277', 'scene-0278', 'scene-0329', 'scene-0330', 'scene-0331', 'scene-0332', 'scene-0344',
     'scene-0345', 'scene-0346', 'scene-0519', 'scene-0520', 'scene-0521', 'scene-0522', 'scene-0523', 'scene-0524',
     'scene-0552', 'scene-0553', 'scene-0554', 'scene-0555', 'scene-0556', 'scene-0557', 'scene-0558', 'scene-0559',
     'scene-0560', 'scene-0561', 'scene-0562', 'scene-0563', 'scene-0564', 'scene-0565', 'scene-0625', 'scene-0626',
     'scene-0627', 'scene-0629', 'scene-0630', 'scene-0632', 'scene-0633', 'scene-0634', 'scene-0635', 'scene-0636',
     'scene-0637', 'scene-0638', 'scene-0770', 'scene-0771', 'scene-0775', 'scene-0777', 'scene-0778', 'scene-0780',
     'scene-0781', 'scene-0782', 'scene-0783', 'scene-0784', 'scene-0794', 'scene-0795', 'scene-0796', 'scene-0797',
     'scene-0798', 'scene-0799', 'scene-0800', 'scene-0802', 'scene-0904', 'scene-0905', 'scene-0906', 'scene-0907',
     'scene-0908', 'scene-0909', 'scene-0910', 'scene-0911', 'scene-0912', 'scene-0913', 'scene-0914', 'scene-0915',
     'scene-0916', 'scene-0917', 'scene-0919', 'scene-0920', 'scene-0921', 'scene-0922', 'scene-0923', 'scene-0924',
     'scene-0925', 'scene-0926', 'scene-0927', 'scene-0928', 'scene-0929', 'scene-0930', 'scene-0931', 'scene-0962',
     'scene-0963', 'scene-0966', 'scene-0967', 'scene-0968', 'scene-0969', 'scene-0971', 'scene-0972', 'scene-1059',
     'scene-1060', 'scene-1061', 'scene-1062', 'scene-1063', 'scene-1064', 'scene-1065', 'scene-1066', 'scene-1067',
     'scene-1068', 'scene-1069', 'scene-1070', 'scene-1071', 'scene-1072', 'scene-1073'],
'mini_train':
    ['scene-0061', 'scene-0553', 'scene-0655', 'scene-0757', 'scene-0796', 'scene-1077', 'scene-1094', 'scene-1100'],
'mini_val':
    ['scene-0103', 'scene-0916'],
}
if __name__ == '__main__':
  main()
  • 7
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HIT_Vanni

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值