地平线bev参考算法板端输入数据准备教程

1.bev参考算法模型信息

板端或者X86端运行hrt_model_exec model_info命令获取模型输入输出信息,参考命令如下:

hrt_model_exec model_info --model_file model.hbm

使用以上命令获取的bev模型输入信息分别如下所示:

模型

input name

input shape

layout

数据格式

量化scale

IPM

input[0]

(6,3,512,960)

NCHW

NV12

-–

IPM

input[1]

(6,2,128,128)

NCHW

int16

0.0078125

LSS

input[0]

(6,3,256,704)

NCHW

NV12

-–

LSS

input[1]

(10,2,128,128)

NCHW

int16

0.0078125

LSS

input[2]

(10,2,128,128)

NCHW

int16

0.03125

GKT

input[0]

(6,3,512,960)

NCHW

NV12

-–

GKT

input[1]-input[9]

(6,2,64,64)

NCHW

int16

0.00390625

2. 图像输入生成

根据模型输入信息,需要将6张图像转成NV12格式后拼接在一起,然后导出为.bin文件作为板端模型的input[0]输入,图像格式转为NV12代码如下:

import argparse
import os
import cv2
import numpy as np
import torch
from PIL import Image
from torchvision.transforms.functional import pil_to_tensor
from torchvision.transforms.functional_tensor import resize

def process_img(img_path, resize_size, crop_size):
    orig_img = cv2.imread(img_path)
    orig_img = Image.fromarray(orig_img)
    orig_img = pil_to_tensor(orig_img)
    resize_hw = (
        int(resize_size[0]),
        int(resize_size[1]),
    )

    orig_shape = (orig_img.shape[1], orig_img.shape[2])
    resized_img = resize(orig_img, resize_hw).unsqueeze(0)
    top = int(resize_hw[0] - crop_size[0])
    left = int((resize_hw[1] - crop_size[1]) / 2)
    resized_img = resized_img[:, :, top:, left:]

    return resized_img, orig_shape

if __name__ == "__main__":
    #resize后的图像大小,lss参考算法为(396, 704)
    resize_size = (540, 960)
    #input_size用于裁剪 resized image
    #模型输入的图像大小,lss参考算法为(256, 704)
    input_size = (512, 960)
    orig_imgs = []
    #输入的6V图像路径
    inputs = './single_frame/imgs'
    #这里可以定义输入图像的顺序,需要与训练时输入的顺序一致
    input_list=["01.jpg","02.jpg","03.jpg","04.jpg","05.jpg","06.jpg"]
    for i, img in enumerate(input_list):
        img = os.path.join(inputs, img)
        img, orig_shape = process_img(img, resize_size, input_size)
        orig_imgs.append({"name": i, "img": img})
    input_imgs = []
    for image in orig_imgs:
        image=image["img"].permute(0,2,3,1).squeeze(0)
        #bgr to nv12
        image=image.numpy().astype(np.uint8)
        height, width = image.shape[0], image.shape[1]    
        yuv420p = cv2.cvtColor(image, cv2.COLOR_BGR2YUV_I420)
        yuv420p = yuv420p.reshape((height * width * 3 // 2, )) 
        y = yuv420p[:height * width] 
        uv_planar = yuv420p[height * width:].reshape((2, height * width // 4)) 
        uv_packed = uv_planar.transpose((1, 0)).reshape((height * width // 2, )) 
        nv12 = np.zeros_like(yuv420p) 
        nv12[:height * width] = y 
        nv12[height * width:] = uv_packed
        input_imgs.append(nv12)
    input_imgs=np.array(input_imgs)
    input_imgs=input_imgs.reshape(6 * 3 * input_size[0] * input_size[1] // 2)
    #导出为bin
    input_imgs.tofile("inputnv12.bin")

ipm和gkt参考算法的resize_size = (540, 960)和input_size = (512, 960),lss参考算法的resize_size=(396, 704),input_size = (256, 704)。

3. 采样点points输入生成

points输入的生成需要以下4个步骤:

  1. 相机内外参数获取;
  2. 生成homography矩阵;
  3. 根据homography矩阵导出浮点points;
  4. 对浮点points做int16量化。

3.1 相机内外参数获取

OE开发包的ddk/samples/ai_toolchain/horizon_model_train_sample/scripts/tools目录下提供了nuscenes数据集相机内外参获取脚本gen_camera_param_nusc.py,运行方式如下:

#进入到OE开发包的scripts路径
cd $workspace$/horizon_model_train_sample/scripts
python3 ./tools/gen_camera_param_nusc.py --data-path $nuscenes_path$ --save-path $save_path$ --save-by-city True --version $nuscene_version$

上述脚本中的参数意义为:

  • --data-path :配置为bev参考算法运行时构建的meta文件夹路径

  • --save-path:生成相机内外参的保存路径;

  • --save-by-city:nuscenes数据集中不同城市的相机内外参可能是不同的,为了便于区分,所以用city 名称命名生成的相机内外参数;

  • --version:使用的nuscenes数据据版本,可选为v1.0-mini和v1.0-trainval,默认为v1.0-mini;-
    脚本运行结束后,会在–save-path下生成boston和singapore两个文件夹,文件夹中包含相机内参/外参矩阵,目录结构如下所示:

    |-- boston
    | |-- camera_intrinsic.npy #相机内参
    | |-- sensor2ego_rotation.npy #相机外参
    | -- sensor2ego_translation.npy # |-- singapore | |-- camera_intrinsic.npy | |-- sensor2ego_rotation.npy – sensor2ego_translation.npy

这里生成了boston和singapore两个文件夹,是因为nuscenes数据集在boston和singapore location下采集的数据的相机参数不同。

3.2 生成homography矩阵

OE开发包的ddk/samples/ai_toolchain/horizon_model_train_sample/scripts/tools目录下提供了homography矩阵生成脚本homography_generator.py,运行方式如下:

#导出singapore的homography矩阵
python3 ./tools/homography_generator.py --sensor2ego-translation ./singapore/sensor2ego_translation.npy --sensor2ego-rotation ./singapore/sensor2ego_rotation.npy  --camera-intrinsic ./singapore/camera_intrinsic.npy --save-path ./singapore

#导出boston的homography矩阵
python3 ./tools/homography_generator.py --sensor2ego-translation ./boston/sensor2ego_translation.npy --sensor2ego-rotation ./boston/sensor2ego_rotation.npy  --camera-intrinsic ./boston/camera_intrinsic.npy --save-path ./boston

上述脚本的参数意义为:

  • --sensor2ego-translation:即上节生成的sensor2ego_translation.npy
  • --sensor2ego-rotation:即上节生成的sensor2ego_rotation.npy
  • --camera-intrinsic: 内参矩阵的路径,即上节生成的camera_intrinsic.npy
  • --save-path:保存路径

3.3 导出浮点Points

获取homography矩阵后,使用tools文件夹下的reference_points_generator.py脚本导出浮点类型的points,参考命令如下:

#导出singapore location下lss的points
python3 ./tools/reference_points_generator.py --config ./configs/bev/bev_mt_lss_efficientnetb0_nuscenes.py --homography ./singapore/homography.npy --save_path ./lss/points/singapore
#导出boston location下lss参考算法的points
python3 ./tools/reference_points_generator.py --config ./configs/bev/bev_mt_lss_efficientnetb0_nuscenes.py --homography ./boston/homography.npy --save_path ./lss/points/boston

可以通过修改config文件路径导出ipm、gkt参考算法的Points.

上述脚本的参数意义为:

  • --config:config文件的路径 ,脚本中以bev_mt_lss参考算法为例
  • --homography:上节中导出homo矩阵的路径
  • --save_path:保存路径

输出文件说明:

  • ipm有1个points输入,所以会在save_path下生成一个reference_points0.npy;
  • lss有2个points输入,所以会在save_path下生成一个reference_points0.npy和reference_points1.npy;
    • gkt有 9个points,所以会在save_path下生成一个reference_points0.npy到reference_points8.npy等9个npy文件;

3.4 对浮点Points做量化

导出浮点Points后 ,需要对其做int16量化。这里提供了给浮点Points进行int16量化的process_reference_points.py,源代码为:

import numpy as np 
import torch
import argparse
import os

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--model",
        "-m",
        type=str,
        required=True,
        help="Selectable as ipm,lss,gkt",
    )
    parser.add_argument(
        "--points_path",
        "-p",
        type=str,
        required=True,
        help="path of points(float32)"
    )
    parser.add_argument(
        "--save_path",
        type=str,
        default=".",
        help="path for saving point(int16)",
    )
    known_args, unknown_args = parser.parse_known_args()
    return known_args, unknown_args
def process_reference_points(model, points_path, save_path):
    for i, name in enumerate(os.listdir(points_path)):
        if model=="ipm":
            scale = [0.0078125]
        elif model == "gkt":
            scale = [
            0.00390625, 0.00390625, 0.00390625, 0.00390625, 0.00390625,
            0.00390625, 0.00390625, 0.00390625, 0.00390625]
        if model == "lss":
            scale = [0.0078125, 0.03125]
        path= os.path.join(points_path, name)
        print(path)  
        points = np.load(path)
        idx=int(path[-5])
        points = points.transpose(0, 3, 1, 2) / scale[idx]
        print(points.shape)
        points = np.floor(points + 0.5)
        points = np.clip(points, -32768, 32767)
        points = points.astype(np.int16)
        save_name= os.path.join(save_path, name)[2:-4]
        print("save_name:",save_name)
        points.tofile(save_name+".bin")
        print("----save points OK----")                          
if __name__ == "__main__":
    args, args_env = parse_args()
    if args_env:
        setup_args_env(args_env)
    process_reference_points(args.model, args.points_path, args.save_path)

参考命令如下:

#对lss参考算法在singapore location下的points做量化
python3 process_reference_points.py --model "lss" --points_path ./lss/points/singapore --save_path ./lss/points/processed_singapore
 #对lss参考算法在boston location下的points做量化
python3 process_reference_points.py --model "lss" --points_path ./lss/points/boston --save_path ./lss/points/processed_boston

命令中的参数意义是:

  • --model:参考算法类型,可选为lss,ipm,gkt
  • --points_path:浮点points路径,注意每个参考算法在singapore和boston location下的浮点points是不同的
  • --save_path:保存路径

输出文件说明:

  • ipm有1个points输入,所以会在save_path下生成一个reference_points1.bin;
  • lss有2个points输入,所以会在save_path下生成一个reference_points0.bin和reference_points1.bin;
  • gkt有 9个points,所以会在save_path下生成一个reference_points0.bin到reference_points8.bin等9个bin文件。

至此,bev参考算法板端输入数据的准备完成。

#这里可以定义输入图像的顺序,需要与训练时输入的顺序一致 input_list=[“01.jpg”,“02.jpg”,“03.jpg”,“04.jpg”,“05.jpg”,“06.jpg”]

请问训练时使用的nuscenes 输入顺序为什么?

是 back → back_left ->front_left → front → front_right ->back_right吗?

请问一下,对浮点进行int16量化的目的是什么啊,以IPM为例

scale = [0.0078125]

idx=int(path[-5])

points = points.transpose(0, 3, 1, 2) / scale[idx]

为什么是int16量化,而不是int8量化;并且是为什么采用乘以128(1/0.0078125)的方式实现的啊

你好,我看points量化时有一步是points = np.floor(points + 0.5),这一步是为了将浮点数取整到最近的整型数吗?

这是为了和硬件中half round up的四舍五入的方式对齐,即在-x.5的时候,取值为-x

你好,points采用int16量化的原因是基于数值分布,使用int16量化可以实现更高的量化精度;而scale=0.0078125是使用config文件中的get_grid_quant_scale函数计算得出的,get_grid_quant_scale函数代码如下:-

这样做的原因是points中会存在一些数值较大的值,如果使用工具基于这些值自动统计scale可能会导致比较大的量化误差,所以这里使用get_grid_quant_scale函数来限制scale的值。

经过实验,得出输入顺序为: 【“01.jpg”: frontleft,“02.jpg”: back,“03.jpg”:backleft,“04.jpg”:front,“05.jpg”:frontright,“06.jpg”:backright】

明白了,谢谢。

了解了,感谢!

4卡4090训练,batch_per_gpu=4,40个epoch,训练BEV_IPM模型累计需要40days,这个正常么? 单独在coco数据集训练了fcos的检测模型,训练速度是正常的,环境应该没有问题

你好,这么久的训练时间是异常的,麻烦发个问题帖我们这边看一下