mmdet3d+waymo test/evaluation流程

Posted ZLTJohn

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mmdet3d+waymo test/evaluation流程相关的知识,希望对你有一定的参考价值。

evaluation是最直观反映模型性能和问题的步骤,也是检验运行环境是否有错的重要一换,下面依托detr3d与custom waymo dataset类,介绍一下mmdet3d+waymo在eval的流程。
调用eval有两种流程,一种是training过程中eval,另一种是调用test.py,test.py支持validation set和test set的处理。无论哪种,其核心都是调用dataset类下的evaluate方法。下面主要讲调用test.py的流程。

evaluation

调用bash

bash tools/dist_test.sh \\
projects/configs/detr3d/detr3d_res101_gridmask_waymo.py \\
work_dirs/detr3d_res101_gridmask_waymo/epoch_10.pth \\
8 --eval=waymo

其中 --eval代表evaluate的时候用哪种style的metric,比如waymo,在mmdet3d里支持kitti和waymo两种style。

tools/dist_test.sh

CONFIG=$1
CHECKPOINT=$2
GPUS=$3
PORT=$PORT:-29500

PYTHONPATH="$(dirname $0)/..":$PYTHONPATH \\
python -m torch.distributed.launch --nproc_per_node=$GPUS --master_port=$PORT \\
    $(dirname "$0")/test.py $CONFIG $CHECKPOINT --launcher pytorch $@:4

就是用torch的distributed.launch方法来启动我们的test.py。这里有个巨坑是之后可能会调用tensorflow,环境配置不好的话就会出现cuda error,解决方法后面说。

tools/test.py

和tools/train.py类似。首先处理命令行参数,读config文件,初始化dataset,dataloader,载入model。
值得注意的是dataset = build_dataset(cfg.data.test),也就是config文件的‘data’里,‘test’的部分;data里还有’train’和’eval’,这个都是在training流程里使用的。
初始化后,就是把image放进model里,然后进行evaluate了,核心代码如下:

outputs = single_gpu_test(model, data_loader, args.show, args.show_dir)
#list of dict where dict['pts_bbox']=dict(bboxes scores labels)
if args.format_only:
    dataset.format_results(outputs, **kwargs)
if args.eval:
	print(dataset.evaluate(outputs, **eval_kwargs))

如果跑的是test set,没有gt box,就只会format result,否则调用dataset的evaluate函数。下面先讲讲single gpu test,再讲evaluate

single_gpu_test

这个函数位于mmdet3d/apis/test.py中,核心就是

model.eval()
results = []
for i, data in enumerate(data_loader):
   with torch.no_grad():
      result = model(return_loss=False, rescale=True, **data)
   if args.show: 
   	   ...
   results.extend(result)

result的格式为[dict(),dict()...],其中result[0]['pts_bbox']存了每个frame的所有bbox相关信息,也是一个dict,格式如下:

 predictions_dict = 
                'bboxes': boxes3d,
                'scores': scores,
                'labels': labels
            

不太重要的细节:

detr3d的detector在调用forward处理batched input tensor之后,会调用bbox coder把output feature整理成bboxes,并把一个batch的bboxes按frame分开,形成一个list。在分开的同时,gpu上的data也放到了cpu上。

dataset.evaluate(output, …)

waymo_dataset类里,不仅定义了如何读取数据,也定义了如何根据数据进行evaluation;后者在代码里占了很大部分。
evaluate有kitti和waymo标准,下面只讲waymo的,核心代码如下:

result_files, tmp_dir = self.format_results(
                results,
                pklfile_prefix,
                submission_prefix,
                data_format='waymo')
 ret_bytes = subprocess.check_output(        
                'mmdetection3d/mmdet3d/core/evaluation/waymo_utils/' +
                f'compute_detection_metrics_main pklfile_prefix.bin ' +
                f'waymo_root/gt.bin',
                shell=True)
ret_texts = ret_bytes.decode('utf-8')
print(ret_texts)

由于我们之前处理出的waymo数据都是kitti格式的,需要调用format_results把格式转回waymo。比如最关键的bbox坐标,都是以front_cam为坐标系的,我们要把他转回以lidar坐标。结果会存到一个.bin文件里。

转回去之后,调用github: waymo-open-dataset提供的compute_detection_metrics_main,比较我们的.bin文件,和validation set里面本来就有的laser_gt.bin,从而得到metric。值得一提的是.bin文件是protobuf文件,其类型为waymo_open_dataset.protos.metrics_pb2.Objects(),定义可以在相关的.proto文件里查看。也可以用python很方便地查看他的内容。

dataset.format_results(results)

输入是model得到的bbox,函数首先将list of dict的格式转换成kitti format的result,然后再调用KITTI2Waymo converter,把kitii format转换成waymo format。核心代码如下:

result_files = self.bbox2result_kitti(outputs,
								      self.CLASSES,
									  pklfile_prefix,
									  submission_prefix)
converter = KITTI2Waymo(result_files['pts_bbox'],
                        waymo_tfrecords_dir,
                        waymo_results_save_dir,
                        waymo_results_final_path, prefix)
converter.convert()

bbox2result_kitti返回的result_files 为:List[dict]: A list of dict have the kitti 3d format ,主要负责一些简单的格式转换。

KITTI2Waymo主要负责把dict格式转换成metric_pb2.object格式,其中内容也有变:坐标系的变换,添加timestamp,context_name使得能够匹配到gt.bin对应的object

mmdet3d/core/evaluation/waymo_utils/KITTI2Waymo

这一块是比较坑爹的,因为他多线程读tfrecord文件来获得timestamp和cam_to_velodyne变换矩阵,读取的过程tensorflow又莫名其妙用了gpu,导致我每次运行都报错,下面讲讲具体流程。

self. __ init __

    def __init__(self,
                 kitti_result_files,
                 waymo_tfrecords_dir,
                 waymo_results_save_dir,
                 waymo_results_final_path,
                 prefix,
                 workers=64):

其他重要内容:
self.waymo_tfrecord_pathnames获取validation set底下所有.tfrecord的pathname
self.name2idx是一个dict,存储ABBBCCC -> result index的映射。ABBBCCC就是kitti format的一帧的命名方式,其中A=1,代表valildation set,BBB对应tfrecord的index,CCC是tfrecord里第几帧;result index就是我们的result_files的index。

self.convert

mmcv.track_parallel_progress(self.convert_one, 
							 range(len(self.waymo_tfrecord_pathnames)), 
							 self.workers)
# combine all seperate .bin files into one .bin
pathnames = sorted(glob(join(self.waymo_results_save_dir, '*.bin')))
combined = self.combine(pathnames)
with open(self.waymo_results_final_path, 'wb') as f:
    f.write(combined.SerializeToString())

这里用了mmcv的方法,并行调用convert_one,遍历每个tfrecord提取每个frame的参数。每个frame会形成一个.bin文件,最后把分散的.bin文件合成一个.bin,就可以和gt.bin比较了。

self.conver_one

如下可见,对于每个线程,我们都调用tensorflow来读取.tfrecord文件,这一读就坏了,具体的原因未知,之后开一篇总结一下碰到的坑,解决办法就是把这些信息用dict存到其他格式的文件里,不读取.tfrecord就不会错了。
如下图可见,如果tfrecord里的某个frame和kitti result能对上,那么就用这个frame的转换矩阵、timestamp等信息来把对应的kitti_result转换成waymo format result,并存在一个bin里。调用的是parse_object函数,相关转换方法可以参考下面的链接二。

def convert_one(self, file_idx):
        """Convert action for single file.

        Args:
            file_idx (int): Index of the file to be converted.
        """
  		file_pathname = self.waymo_tfrecord_pathnames[file_idx]
        file_data = tf.data.TFRecordDataset(file_pathname, compression_type='')
        ## return still got error here
        for frame_num, frame_data in enumerate(file_data):
            frame = open_dataset.Frame()
            frame.ParseFromString(bytearray(frame_data.numpy()))

            filename = f'self.prefixfile_idx:03dframe_num:03d'

            for camera in frame.context.camera_calibrations:
                # FRONT = 1, see dataset.proto for details
                if camera.name == 1:
                    T_front_cam_to_vehicle = np.array(
                        camera.extrinsic.transform).reshape(4, 4)

            T_k2w = T_front_cam_to_vehicle @ self.T_ref_to_front_cam    # inverse(Tr_velo_to_cam)
            context_name = frame.context.name               # file name strip
            frame_timestamp_micros = frame.timestamp_micros     # timestamp
            if filename in self.name2idx:
                kitti_result = \\
                    self.kitti_result_files[self.name2idx[filename]]
                objects = self.parse_objects(kitti_result, T_k2w, context_name,
                                             frame_timestamp_micros)
            else:
                print(filename, 'not found.')
                objects = metrics_pb2.Objects()

            with open(
                    join(self.waymo_results_save_dir, f'filename.bin'),
                    'wb') as f:
                f.write(objects.SerializeToString())

参考链接

  1. waymo 数据集-MMDetection3D
  2. 带你玩转 3D 检测和分割 (二):核心组件分析之坐标系和 Box

以上是关于mmdet3d+waymo test/evaluation流程的主要内容,如果未能解决你的问题,请参考以下文章

mmdet3d+waymo test/evaluation流程

Waymo dataset+mmdet3d的坐标系问题

Waymo dataset+mmdet3d的坐标系问题

Waymo dataset+mmdet3d的坐标系问题

mmdet3d纯视觉baseline之数据准备:处理waymo dataset v1.3.1

mmdet3d纯视觉baseline之数据准备:处理waymo dataset v1.3.1