OpenPCDet系列 | 8.2 nuScenes数据集的eval流程

news2024/11/17 11:57:46

0. eval转换的目标

模型的训练和测试过程输出结果是不一样的,对于训练过程是为了构建损失函数来进行训练,而对于测试过程是为了对object进行预测生成预测内容。下面以VoxelNeX检测器的类代码可见,training和testing将会输出两个内容。

class VoxelNeXt(Detector3DTemplate):
    def __init__(self, model_cfg, num_class, dataset):
        super().__init__(model_cfg=model_cfg, num_class=num_class, dataset=dataset)
        self.module_list = self.build_networks()

    def forward(self, batch_dict):

        for cur_module in self.module_list:
            batch_dict = cur_module(batch_dict)

        if self.training:
            loss, tb_dict, disp_dict = self.get_training_loss()
            ret_dict = {
                'loss': loss
            }
            return ret_dict, tb_dict, disp_dict
        else:
            pred_dicts, recall_dicts = self.post_processing(batch_dict)
            return pred_dicts, recall_dicts

对于测试过中,这里的recall_dict是各阈值召回率的指标,而pred_dicts是一个字典存储相关的预测信息。
在这里插入图片描述

后续做内容就是把这些每一帧的预测数据进行转换,每个pred_boxes都从此转换为nuSences的数据结构,也就是下面的一个dict。这里的一个list集是对应着上面的shape为(222,9)的pred_boxes,在mini版本中验证集有81帧点云,所以最后的结果也是构建成81个字典,每个字典再有自己的每个预测object的列表。
在这里插入图片描述

知道了由很多个pred_dicts构建成nusc_annos列表的整个过程,就是eval过程,后续就会清晰很多,因为都是围绕着这个点来进行。现在整个eval_one_epoch函数主要分成两个步骤:dataset.generate_prediction_dicts和dataset.evaluation


1. dataset.generate_prediction_dicts

1.1 generate_prediction_dicts函数

通过dataset.generate_prediction_dicts来生成预测字典,核心代码如下所示:

model.eval()

if cfg.LOCAL_RANK == 0:
    progress_bar = tqdm.tqdm(total=len(dataloader), leave=True, desc='eval', dynamic_ncols=True)
for i, batch_dict in enumerate(dataloader):
    load_data_to_gpu(batch_dict)

    with torch.no_grad():
        pred_dicts, ret_dict = model(batch_dict)

    disp_dict = {}
    ......
    statistics_info(cfg, ret_dict, metric, disp_dict)
    
    annos = dataset.generate_prediction_dicts(
        batch_dict, pred_dicts, class_names,
        output_path=final_output_dir if args.save_to_file else None
    )
    det_annos += annos

对于VoxelNeXt模型来说,这里的batch_dict对于不同的模型应该是一致的,输入一致,根据对应的配置文件以及获取到权重文件应该可以实现多模型集成的效果。这里我设置的batch_size为4,所以对应的pred_dicts列表有4个成员:
在这里插入图片描述

ret_dict是一个字典,包含当前点云帧的的预测点在gt中的召回率
在这里插入图片描述

随后利用batch_dict、pred_dict来生成当前batch的预测结果。batch_dict的结构如下所示:
在这里插入图片描述

1.2 generate_single_sample_dict函数

对每个点云帧场景的预测值进行处理,对应的label转换为对应名字,处理后如下所示。box_dict是一个点云帧的预测结果
在这里插入图片描述

这里的label和nuScenes设置的label是不一样的,这里的label预测是根据在yaml文件中的CLASS_NAMES来进行一一对应顺序。然后进行相应的name转换,获取到具体的类别。也就是说,最后的类别检测对应是自己设定的类别列表,而不是nuScenes自带的类别列表。同时,这里的label没有0类,标号从1开始。

CLASS_NAMES: ['car','truck', 'construction_vehicle', 'bus', 'trailer',
              'barrier', 'motorcycle', 'bicycle', 'pedestrian', 'traffic_cone']

随后再把当前的binfile名字以及当前场景对应的token的信息再保存在字典中,再将结果添加到列表,结果如下:
在这里插入图片描述

核心代码:

def generate_prediction_dicts(self, batch_dict, pred_dicts, class_names, output_path=None):
    """
    Args:
        batch_dict:
            frame_id:
        pred_dicts: list of pred_dicts
            pred_boxes: (N, 7 or 9), Tensor
            pred_scores: (N), Tensor
            pred_labels: (N), Tensor
        class_names:
        output_path:
    """
    
    def get_template_prediction(num_samples):
        box_dim = 9 if self.dataset_cfg.get('TRAIN_WITH_SPEED', False) else 7
        ret_dict = {
            'name': np.zeros(num_samples), 'score': np.zeros(num_samples),
            'boxes_lidar': np.zeros([num_samples, box_dim]), 'pred_labels': np.zeros(num_samples)
        }
        return ret_dict

    def generate_single_sample_dict(box_dict):
        pred_scores = box_dict['pred_scores'].cpu().numpy()
        pred_boxes = box_dict['pred_boxes'].cpu().numpy()       # (N, 9)
        pred_labels = box_dict['pred_labels'].cpu().numpy()
        pred_dict = get_template_prediction(pred_scores.shape[0])   # fill with zero array
        if pred_scores.shape[0] == 0:
            return pred_dict
        
        # 预测标签是从1开始标号的,所以转换到类别名称需要label-1来获取
        pred_dict['name'] = np.array(class_names)[pred_labels - 1]    # label transformer to name
        pred_dict['score'] = pred_scores
        pred_dict['boxes_lidar'] = pred_boxes
        pred_dict['pred_labels'] = pred_labels

        return pred_dict

    annos = []
    for index, box_dict in enumerate(pred_dicts):   # process frame
        single_pred_dict = generate_single_sample_dict(box_dict)            # add other info
        single_pred_dict['frame_id'] = batch_dict['frame_id'][index]        # add lidar bin file name info
        if 'metadata' in batch_dict:
            single_pred_dict['metadata'] = batch_dict['metadata'][index]
        annos.append(single_pred_dict)

    return annos

随后就是对整个数据集的全部组建,直到结束。
在这里插入图片描述


2. dataset.evaluation

通过dataset.evaluation对预测字典进行评估

result_str, result_dict = dataset.evaluation(
    det_annos, 
    class_names,
    eval_metric=cfg.MODEL.POST_PROCESSING.EVAL_METRIC,
    output_path=final_output_dir
)

2.1 transform_det_annos_to_nusc_annos函数

1)利用boxes_lidar_to_nusenes函数对annos的每个预测的gt信息进行重新构造字典结构:
在这里插入图片描述

代码如下:

def boxes_lidar_to_nusenes(det_info):
    boxes3d = det_info['boxes_lidar']   # (N, 9)
    scores = det_info['score']          # (N, )
    labels = det_info['pred_labels']    # (N, )

    box_list = []
    for k in range(boxes3d.shape[0]):
        quat = Quaternion(axis=[0, 0, 1], radians=boxes3d[k, 6])    # orientation
        velocity = (*boxes3d[k, 7:9], 0.0) if boxes3d.shape[1] == 9 else (0.0, 0.0, 0.0)    # *解包使用
        box = Box(
            boxes3d[k, :3],
            boxes3d[k, [4, 3, 5]],  # wlh
            quat, label=labels[k], score=scores[k], velocity=velocity,
        )
        box_list.append(box)
    return box_list

ps:这里利用了nuScenes定义的一个Box类以及Quaternion四元数库,这是因为nuScenes定义的相关函数是需要Quaternion的传参。这里Box的定义在nuscenes.utils.data_classes文件中。
在这里插入图片描述

2)利用lidar_nusc_box_to_global函数对重新构造的gt box信息进行坐标系的转换,这里主要是利用当前sample的sample_data的传感器标定信息calibrated_sensor以及ego_pose信息进行坐标系的转换。数据集转换处理后的box列表结构不变:

def lidar_nusc_box_to_global(nusc, boxes, sample_token):
    s_record = nusc.get('sample', sample_token)
    sample_data_token = s_record['data']['LIDAR_TOP']
    
    # 坐标系的转化主要是calibrated_sensor和ego_pose相关信息的提取
    sd_record = nusc.get('sample_data', sample_data_token)
    cs_record = nusc.get('calibrated_sensor', sd_record['calibrated_sensor_token'])
    sensor_record = nusc.get('sensor', cs_record['sensor_token'])
    pose_record = nusc.get('ego_pose', sd_record['ego_pose_token'])

    data_path = nusc.get_sample_data_path(sample_data_token)    # lidar bin file path
    box_list = []
    for box in boxes:
        # Move box to ego vehicle coord system 
        # 从激光雷达传感器坐标系转换到车身坐标系下
        box.rotate(Quaternion(cs_record['rotation']))
        box.translate(np.array(cs_record['translation']))
        
        # Move box to global coord system
        # 从车身坐标系转换到全局坐标系下
        box.rotate(Quaternion(pose_record['rotation']))
        box.translate(np.array(pose_record['translation']))
        box_list.append(box)
    return box_list

ps:这里用到了一个四元数的库Quaternion,表示rotation的四元数可以利用Python包pyquaternion转换成(pitch,yaw,roll)的形式,而且可以计算对应的旋转矩阵和逆矩阵,非常好用。

此时,将预测的box移动到到全局坐标系中。随后重新对每个box进行遍历依次处理。具体来说,模型预测的参数是基于激光雷达坐标系中的,那么现在需要利用ego参数将其转换到车身坐标系中,然后再从陈胜坐标系转换到全局坐标系下。
在这里插入图片描述

ps:为什么需要转到全局坐标系下,因为原本nuScenes的标注格式就是全局坐标系下的坐标。
在这里插入图片描述

3)处理完box_list后重新对其进行遍历,提取速度和类别来判断当前属性,重新对每个box构造一个nusc_anno的字典,并保存在annos列表变量中。annos的列表长度与box_list的列表长度是一致的。

nusc_anno = {
    'sample_token': det['metadata']['token'],
    'translation': box.center.tolist(),
    'size': box.wlh.tolist(),
    'rotation': box.orientation.elements.tolist(),
    'velocity': box.velocity[:2].tolist(),
    'detection_name': name,
    'detection_score': box.score,
    'attribute_name': attr
}
annos.append(nusc_anno)

此时的annos其实属于某一个sample中的预测信息信息。根据det变量中的boxes_lidar来进行构建。
在这里插入图片描述

4)最后将每个sample的预测lidar box信息,都构造成以上的一个annos列表。sample的token作为字典的key,处理后的预测annos作为字典的value,从而将所有的sample进行处理。
在这里插入图片描述

这里每个token列表的每个element都是nusc_anno结构的一个字典。

总结一下:
transform_det_annos_to_nusc_annos函数的作用是将原来的预测sample格式。
在这里插入图片描述

将gt进行坐标系的转换后分别构造成如下所示:
在这里插入图片描述

2.2 NuScenesEval

利用nuScenes提供的评价函数来获取预测的评估结果:https://github.com/nutonomy/nuscenes-devkit/blob/master/python-sdk/nuscenes/eval/detection/evaluate.py

class DetectionEval:
    """
    This is the official nuScenes detection evaluation code.
    Results are written to the provided output_dir.

    nuScenes uses the following detection metrics:
    - Mean Average Precision (mAP): Uses center-distance as matching criterion; averaged over distance thresholds.
    - True Positive (TP) metrics: Average of translation, velocity, scale, orientation and attribute errors.
    - nuScenes Detection Score (NDS): The weighted sum of the above.

    Here is an overview of the functions in this method:
    - init: Loads GT annotations and predictions stored in JSON format and filters the boxes.
    - run: Performs evaluation and dumps the metric data to disk.
    - render: Renders various plots and dumps to disk.

    We assume that:
    - Every sample_token is given in the results, although there may be not predictions for that sample.

    Please see https://www.nuscenes.org/object-detection for more details.
    """
    def __init__(self,
                 nusc: NuScenes,
                 config: DetectionConfig,
                 result_path: str,
                 eval_set: str,
                 output_dir: str = None,
                 verbose: bool = True):
        """
        Initialize a DetectionEval object.
        :param nusc: A NuScenes object.
        :param config: A DetectionConfig object.
        :param result_path: Path of the nuScenes JSON result file.
        :param eval_set: The dataset split to evaluate on, e.g. train, val or test.
                         {'v1.0-mini': 'mini_val', 'v1.0-trainval': 'val', 'v1.0-test': 'test'}
        :param output_dir: Folder to save plots and results to.
        :param verbose: Whether to print to stdout.
        """       

    def evaluate(self) -> Tuple[DetectionMetrics, DetectionMetricDataList]:
        """
        Performs the actual evaluation.
        :return: A tuple of high-level and the raw metric data.
        """


    def render(self, metrics: DetectionMetrics, md_list: DetectionMetricDataList) -> None:
        """
        Renders various PR and TP curves.
        :param metrics: DetectionMetrics instance.
        :param md_list: DetectionMetricDataList instance.
        """
       

    def main(self,
             plot_examples: int = 0,
             render_curves: bool = True) -> Dict[str, Any]:
        """
        Main function that loads the evaluation code, visualizes samples, runs the evaluation and renders stat plots.
        :param plot_examples: How many example visualizations to write to disk.
        :param render_curves: Whether to render PR and TP curves to disk.
        :return: A dict that stores the high-level metrics and meta data.
        """

        ......

class NuScenesEval(DetectionEval):
    """
    Dummy class for backward-compatibility. Same as DetectionEval.
    """

其中,这里初始化result_path存储的上述处理完的数据所保存的序列文件。其内容还是如下所示:
在这里插入图片描述

在执行main函数时,会想进行评估然后再进行可视化,当然可以通过相关的参数设置为False取消显示。

2.3 nuscenes_utils.format_nuscene_results

加载出metrics_summary.json文件,并进行相关信息的整理和输出,这里的评价指标最主要的是每个class的error和map,以及总map和nds。
在这里插入图片描述

在执行NuScenesEval时已经开始执行:

nusc_eval = NuScenesEval(
    nusc,
    config=eval_config,
    result_path=res_path,
    eval_set=eval_set_map[self.dataset_cfg.VERSION],
    output_dir=str(output_path),
    verbose=True,
)

这里的eval_config文件在nuScenes-devkit中已经内置了,路径在:
python-sdk/nuscenes/eval/detection/configs/detection_cvpr_2019.json,内容如下:
在这里插入图片描述

在main中执行了保存相关文件的操作
在这里插入图片描述

json文件既保存在output_path路径下的metrics_summary.json文件中,所以这里有两种方式对评价指标进行读取,其中一种是利用返回的结果,另外一种是读取json文件来读取评价信息。

# 方法1:
metrics_summary = nusc_eval.main(plot_examples=0, render_curves=False)

# 方法2:
with open(output_path / 'metrics_summary.json', 'r') as f:
    metrics = json.load(f)

# 相关指标的整理
result_str, result_dict = nuscenes_utils.format_nuscene_results(metrics, self.class_names, version=eval_version)

最后的结果保存如下:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/737571.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++数据结构笔记(7)——队列的顺序结构实现

1.队列&#xff0c;和现实生活中的规则类似&#xff0c;先进先出 2.队尾只允许元素进入&#xff0c;队头只允许元素退出 3.用数组来实现队列的顺序存储&#xff0c;无论哪一段都可以作为队头或者队尾 SeqQueue.h头文件 #ifndef SEQQUEUE_H #define SEQQUEUE_H #include<…

仿大众点评项目 —— Day02【优惠券秒杀、分布式锁】

❤ 作者主页&#xff1a;欢迎来到我的技术博客&#x1f60e; ❀ 个人介绍&#xff1a;大家好&#xff0c;本人热衷于Java后端开发&#xff0c;欢迎来交流学习哦&#xff01;(&#xffe3;▽&#xffe3;)~* &#x1f34a; 如果文章对您有帮助&#xff0c;记得关注、点赞、收藏、…

Java字符串类

string类的理解(以JDK8为例说明) 1.1的声明 public final class String implements java.io.Serializable&#xff0c; Comparable<String>&#xff0c; CharSequence final:String是不可被继承的 Serializable:可序列化的接口。凡是实现此接口的类的对象就可以通过…

建筑施工脚手架安全技术统一标准

为统一建筑施工脚手架设计、施工、使用及管理&#xff0c;做到技术先进、安全适用、经济合理&#xff0c;制定本标准。 本标准适用于房屋建筑工程和市政工程施工用脚手架的设计、施工、使用及管理。 建筑施工脚手架的设计、施工、使用及管理&#xff0c;除应符合本标准外&…

第一百零二天学习记录:数据结构与算法基础:初识数据结构与算法

管理系统模型&#xff08;仓库管理系统&#xff09;—顺序表 操作对象之间的关系&#xff1a;线性关系 数据结构&#xff1a;线性数据结构、线性表 &#xff08;例如&#xff1a;学生成绩管理系统、人事管理系统、仓库管理系统、通讯录等。&#xff09; 操作对象&#xff1a;若…

OWASP 定义的大模型应用最常见的10个关键安全问题

7月15日之前入驻华为云&#xff0c;可参与Check抽奖活动&#xff0c;抽奖活动在文末 1. 《OWASP 大模型应用最常见的10个关键安全问题》项目简介&#xff08;OWASP TOP10 LLMs Project&#xff09; *OWASP Top 10 for Large Language Model Applications OWASP 大模型应用程序…

vue3使用腾讯地图(‘关键词搜索、逆地址解析‘)

1.登录腾讯地图位置服务进入控制台 申请腾讯地图开发者进入控制台申请自己的key 腾讯位置服务 - 立足生态&#xff0c;连接未来 2.进入vue项目的public文件下的index.html 引入腾讯资源包&#xff0c;并把申请的key填入 <script src"https://map.qq.com/api/js?v2…

文心一言 VS 讯飞星火 VS chatgpt (57)-- 算法导论6.4 1题

文心一言 VS 讯飞星火 VS chatgpt &#xff08;57&#xff09;-- 算法导论6.4 1题 一、参照图 6-4 的方法&#xff0c;说明 HEAPSORT 在数组 A(5&#xff0c;13&#xff0c;2&#xff0c;25&#xff0c;7&#xff0c;17&#xff0c;20&#xff0c;8&#xff0c;4)上的操作过程…

怎么修复损坏的视频文件?视频文件修复办法分享!

随着科技的不断发展&#xff0c;我们的生活中已经离不开各种类型的视频文件。因为各式各样的原因&#xff0c;有时候我们的视频文件可能会损坏。 而损坏的视频文件通常是无法正常播放&#xff0c;这无疑会给我们的生活和工作造成极大的困扰。那么&#xff0c;怎么修复损坏的视…

【Linux学习】记录下Linux的常用基本指令~

1、Linux是一个操作系统&#xff0c;和windows是“并列”关系。Linux已经成为"世界第一大操作系统"。 2、Linux这种使用命令的方式比图形化界面的好处&#xff1f; &#xff08;1&#xff09;节省系统资源&#xff1a;运行图形化界面需要让系统付出一些额外开销&am…

stm32(时钟和中断事件知识点)

一、复位和时钟控制&#xff08;RCC&#xff09; 复位 系统复位 当发生以下任一事件时&#xff0c;产生一个系统复位&#xff1a; 1. NRST引脚上的低电平(外部复位) 2. 窗口看门狗计数终止(WWDG复位) 3. 独立看门狗计数终止(IWDG复位) 4. 软件复位(SW复位) 5. 低功耗管…

软件为什么需要进行应急演练脚本?

软件为什么需要进行应急演练脚本&#xff1f;在当今互联网时代&#xff0c;安全问题愈加突出&#xff0c;不断有新的网络攻击方式不断涌现。针对软件系统的安全漏洞和攻击活动不断增加&#xff0c;软件应急演练变得尤为重要。 首先&#xff0c;应急演练可以帮助软件团队建立应急…

C++11可变参数模板,lambda表达式,包装器

目录 可变参数模板 lambda表达式 问题的引入 lambda表达式语法 捕捉列表的使用 函数对象和lambda表达式 function包装器 可变参数模板 C11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板&#xff0c;相比C98/&#xff0c;类模版和函数模版中只能…

基于redis实现延时队列(一)

背景 最近项目中需要对一条数据&#xff0c;在半小时候更改其状态&#xff0c;类似于提交商城订单半小时后未支付的订单需要更改为超时状态&#xff0c;当然这个解决方案有很多&#xff0c;最好的解决方案是用MQ的死信队列&#xff1b;但由于项目中没有引入MQ&#xff0c;故本…

PMP-质量管理的重要性

本篇文章主要是方便从事于项目管理的“初学者”们了解质量管理的重要性&#xff01;&#xff01;&#xff01; 一、什么是质量管理 项目质量管理包括把组织的质量政策应用于规划、管理、控制项目和产品质量要求&#xff0c;以满足相关方目标的各个过程。此外&#xff0c;项目质…

Latex公式炫酷技巧

最近看到一个炫酷的latex公式用法&#xff0c;特意在此记录一下 效果如下 latex代码如下 \begin{equation}\mathcal{L}_{mot}^{\textcolor{magenta}{\bullet}} \frac{1}{\sum_{i1}^{N}{s_i^l}}\sum_{i1}^{N}\Big\Vert{s}^{l}_i(\mathbf{\hat{f}}_i-\mathbf{f}^{fg}_i)\Big…

网络安全系统教程+学习路线

一、什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面…

【MySQL系列】表的学习及基本操作

「前言」文章内容大致是数据库表的基本操作 「归属专栏」MySQL 「主页链接」个人主页 「笔者」枫叶先生(fy) 「枫叶先生有点文青病」「句子分享」 人生当苦无妨&#xff0c;良人当归即好。 ——烽火戏诸侯《雪中悍刀行》 目录 一、创建表二、修改表三、 删除表 一、创建表 创建…

组合模式的例子

// 组合模式的接口 public interface AccessDecisionVoter {// 投票结果的常量int ACCESS_GRANTED 1;int ACCESS_ABSTAIN 0;int ACCESS_DENIED -1;// 投票方法&#xff0c;根据用户和请求判断是否授权int vote(User user, Request request); }// 组合模式的叶子节点&#xf…

Android中system/bin/Input命令 -- Android12

IMS:Android中Input命令--Android12 1、Android12 Input命令更新1.1 shell脚本1.2 InputShellCommand#onCommand 命令解析 2、Input相关命令参数2.1 text2.2 keyevent2.3 tap2.4 swipe2.5 draganddrop2.6 press2.7 roll2.8 motionevent2.9 keycombination2.10 默认handleDefaul…