地平线征程5芯片(Jounery 5,以下简称J5)已经被越来越多的合作伙伴和开发者所使用,用于面向智能驾驶场景应用的开发。智驾应用是一个非常复杂的开发场景,不仅仅涉及到神经网络的训练和部署, 也覆盖到传感器的接入、图像处理和工程调优的大量工作,开发复杂度很高,有一定的上手门槛。-
千里之行始于足下,从一个最简化的全流程示例完成从Camera接入到目标检测输出的应用开发,将是进行复杂应用场景开发的第一步。为此,在地平线芯片算法工具链的工具包中提供了一个全流程的应用示例(ai_forward_view_sample),以源码的形式呈现了一个Hello World级别的视觉目标检测场景应用的Demo。下面和大家一起对该示例进行解读。-
为了更顺畅地理解此全流程示例的实现原理,希望大家对J5芯片、J5媒体系统接口、J5视频通路配置、算法工具链等背景知识已经有所了解。-
1. 基础原理
1.1 硬件通路
在解读代码之前,有必要了解一下J5芯片的视频通路硬件设计,了解J5芯片的硬件特性可以更好的理解如何高效的进行应用开发。
J5视频通路架构图
主要名词解释
名词
释义
VPS *
Video Process System 图像处理系统
VIN *
Video IN 图像输入
VPM*
Video Process Manager 图像处理管理
DVP
Digital Video Port 数字视频接口
CIM
Camera Interface Manager 图像数据通道管理
CIM-DMA
Camera Interface Manager(DMA output)图像数据通道管理(DMA输出)
ISP
Image Signal Processor 图像处理器
PYM
PYraMid 图像金字塔处理单元
GDC
Geometry Distortion Correction 几何畸变矫正
BPU
Brain Process Unit 地平线神经网络加速处理单元
*VPS/VIN/VPM 是虚拟概念,其他是实体硬件单元
J5支持4路MIPI RX 和 2路MIPI TX,Camara通过MIPI 接口接入,通过配置实现On-line 模式或Off-line模式的数据处理流程(RX0/RX1支持配置成On-line模式,RX2/RX3支持配置成Off-line模式)。支持2路ISP和3路PYM,其中PYM2只支持off-line模式。
On-line模式:-
相机RAW data直接从MIPI接口经过CIM接入到ISP模块,经过ISP处理后,直接进入金字塔模块进行图片的resize/crop操作,输出写入DDR中。在金字塔之前的链路上数据不需要经过DDR,避免了内存的写入读取开销,用于极低延时要求的场景。-
Off-line模式:-
相机RAW data 经过CIM-DMA模块写入DDR,ISP/PYM/BPU等模块都从DDR上读取数据进行处理后再写入DDR。
从Camera接入,到神经网络处理的最基本的数据流如下所示,每个硬件模块的工作模式通过JSON配置文件进行配置,重点会涉及到三个重要的配置文件:mipi config、cim config、vpm config,后文将有更多的解读。-
数据流图
Camera通过MIPI TX 接入后,可配置经过On-line 或 Off-line的模式,将RAW data直接送入到ISP 或是DDR。在这一阶段通过mipi config 和 cim config 两个配置文件来配置实现。
RAW data经过ISP的处理后可以配置直接送入金字塔(PYM)进行crop/resize操作,将图像处理成算法模型所需的尺寸大小。在这一阶段通过vpm config 配置文件进行配置完成。
视频通路支持多种灵活的pipeline配置, 详细的配置说明请参考《MU-3020-12-J5-视频通路配置指南》
图像数据经过金字塔(PYM)处理后输出的是YUV420格式(NV12),该数据格式可以被BPU直接处理。BPU支持在模型中将该数据格式高效的转换成算法模型所需的输入格式(例如RGB/BGR等),并且由于在整个通路上是共享DDR,数据不需搬运即可被BPU所处理,实现零拷贝,最大化的降低了整个处理流程的延时。
1.2 软件Pipeline
在软件层面如何初始化各硬件模块,并实现从Camera->ISP->PYM的数据流处理,除了上面所述的几个重要的配置文件外,还需要依赖J5平台所提供的Auto媒体系统接口(VIO APIs)进行配置的加载和运行,实现数据流pipeline的的运行和图像数据的获取。下面对流程中核心的API进行简要说明。
详细的VIO APIs接口说明请参考《MU-3020-5-J5-图像媒体模块调试指南》
视频通路调用流程图
1.3 模型推理
从金字塔(PYM) 输出的YUV420数据送给BPU作为视觉感知算法模型的输入,BPU上进行模型推理使用libDNN推理库提供的APIs,下图对推理的流程及使用的API做简要的介绍。
详细的接口使用说明可以参考《地平线征程5 算法工具链:5.2 BPU SDK API手册》
模型推理调用流程图
2. 示例功能
在J5 算法工具链开发包(OpenExplorer)的“ddk/samples/ai_forward_view_sample”
目录下提供了一个从Camera接入到检测算法推理,再到将结构化结果在图像上渲染的全流程示例,是打通视频通路和模型推理的有效参考实现。建议大家在深入示例代码前先编译、运行该示例,体验示例的功能,有一个感性的认识。
全流程示例主要呈现了如下的功能:-
1. Camera接入-
目前Camera支持max9296+ar0233 和max96712+ar0233。不同的摄像头模组通过配置文件进行配置。
2. ISP/PYM配置使用-
通过配置文件实现将Camera数据处理成算法模型推理所需要的输入数据,包括crop/resize功能。
3. 网络图像回灌-
实现通过网络发送/接收图像数据作为数据源,完成检测算法推理和可视化渲染功能。
4. 检测模型BPU推理-
集成了一个目标检测模型(fcos_efficientnetb0),在BPU上执行图像推理,实现对输入图像中的目标进行检测,输出目标bounding box。
5. 检测结果可视化渲染-
将目标检测结果渲染在输入图像上,通过后台uws服务提供web方式的可视化渲染能力。
本文重点对视频通路、模型推理的部分进行解读,网络回灌和可视化渲染只是为了提供更完整、丰富的示例功能,不做详细分解。
3. 实现框架
整个示例涉及到Camera的接入、图像数据处理、算法模型推理、检测结果后处理、图像编码、结果可视化渲染多个功能,为了代码实现更加清晰和解耦,设计了多个不同模块分别完成不同阶段的功能,并最大化减少相互间的依赖。
模块框架图
整个示例设计了5个模块来实现不同的任务,做到功能解耦。
模块
功能说明
VIOModule
视频输入模块。对Camera、网络回灌不同的输入模式进行了实现封装,根据配置文件创建不同的子模块实例(CameraInputModule, NetworkInputModule)
InferenceModule
模型推理模块。加载目标检测模型在BPU上实现对视频通路的图像数据进行推理执行
PostProcessModel
后处理模块。实现目标检测模型的后处理逻辑,主要是NMS算法的后处理实现
CodecModule
编解码模块。实现将从金字塔输出的YUV420的图像数据转换编码成JPEG格式数据,用于可视化渲染
WebDisplayModule
Web渲染模块。实现检测目标Box与图像进行合成渲染,通过UWS服务提供可以Web访问的可视化能力
不同的Module里显式的调用下游Module的接口进行数据流的处理,在上面的框架图中已标记出相应的接口。下图对整个示例的运行流程进行详细拆解。
模块调用时序图
4. 模块分析
对整个流程中的三个重点模块进行详细解读,这三个模块构成了在J5开发板上完成一个完整通路的必要流程。
4.1 VIOModule
VIOModule 封装应用数据源的实现,包含了从Camera输入和网络回灌输入两种模式,通过配置文件(j5_vio_config.json
)进行输入模式指定,运行时根据模式类别创建相应InputModule实例,加载不同的配置。
{
"config_index": 2, //配置输入模式
"board_name": "j5dev",
"config_0": { //MAX9296 + AR0233
"cam_en": 1,
"data_source": "mipi_camera",
"data_source_num": 1,
"channel_id": [0],
"max_vio_buffer": 3,
"source_cfg_file": "configs/vio/vin/camera/j5_camera_source.json",
"vpm_cfg_file": "configs/vio/vpm/sen_cim_isp0_pym0_1080p_ar0233_max9296/vpm_config.json"
},
"config_1": { //回灌模式
"cam_en": 0,
"data_source": "network_feedback",
"data_source_num": 1,
"channel_id": [0],
"max_vio_buffer": 3,
"source_cfg_file": "configs/vio/vin/network/j5_network_source.json",
"vpm_cfg_file": "configs/vio/vpm/ddr_pym0_1080p/vpm_config.json"
},
"config_2": { //MAX96712 + AR0233 Camera
"cam_en": 1,
"data_source": "mipi_camera",
"data_source_num": 1,
"channel_id": [0],
"max_vio_buffer": 3,
"source_cfg_file": "configs/vio/vin/camera/j5_camera_source.json",
"vpm_cfg_file": "configs/vio/vpm/sen_cim_isp0_pym0_1080p_ar0233_max96712/vpm_config.json"
}
}
“config_index" 指定示例应用启用何种配置,不同的相机模组可增加不同的配置项,当前示例配置中描述了三种配置选项,config_0 用于max9296 camera模组的接入配置;config_1 用于网络回灌的接入配置;config_2 用于max96712 camera模组的接入配置。
“data_source” 用于指定当前config的数据源类型,分为mipi和network两种。摄像头接入配置为:“mipi_camera”; 网络回灌配置为: “network_feedback”。VIOModule在运行时,根据data_source 的配置创建 CameraInputModule 或 NetworkInputModule的实例。本文将只对CameraInputModule进行解读,如果有更多同学对网络回灌有兴趣,后期再对NetworkInputModule进行解读。
“source_cfg_file” 指定数据源的配置文件路径。对于camera输入,该配置文件中将在CameraInputModule或NetworkInputModule中解析。对于Camera输入,将包含对mipi config 和 cim config的配置文件指定。对于Network输入,主要配置端口号和回灌图像的size信息。
“vpm_cfg_file” 指定视频通路pipeline的配置文件路径。该配置文件中将对ISP/PYM/GDC器件进行配置,在CameraInputModule的初始化中作为hb_vio_init() 的参数,用于VIO通路的初始化。示例中未使用到GDC。
4.2 CameraInputModule
该Module主要实现摄像头的接入配置、VIO Pipeline的启动、图像数据的获取,并调用下游Modules进行图像数据的处理。 源码实现路径:src/modules/input/camera_input_module.cc
-
-
4.2.1 配置分析
视频通路主要由VIN/VPM模块覆盖,硬件通路的配置主要是VIN 和 VPM相关配置文件,先对配置进行分析。
VIN负责Camera接入部分功能,其中包括Sensor/Serdes、MIPI、CIM/CIM DMA及同步LPWM等,对用户提供统一的API,各中Camera的差异性则通过JSON方式配置,用以适配各类接入场景。
VIN部分的配置共有3个JSON 配置文件,配置结构如下:
VIN Config 结构
Mipi config: 该配置文件在示例代码的 configs/vio/vin/camera/hb_j5dev.json
中指定,如下所示:
"config_path":"/system/etc/cam/hb_mipi_ar0233_raw12_%dfps_%dP.json"
**Cim config:**该配置文件在示例代码的 configs/vio/vin/camera/hb_j5dev.json
中指定,如下所示:
"data_path":"/app/bin/vps/vpm/cfg/sen_cim_isp0_pym0_1080p_ar0233/cim_config.json"
两个子配置文件都可以在J5 板上环境相应目录找到,详细的配置项不做展开。
配置参数详解可以参考系统软件手册《MU-3020-12-J5-视频通路配置指南》
VPM 数据通路主要包含ISP、PYM,和其他模块(GDC、Stitch等)的配置,配置文件结构如下:
VPM Config 结构
**VPM config:**该配置文件在示例代码的 configs/vip/vpm/vpm_config.json
, 对ISP和PYM进行了配置。
"isp": {
"hw_id":0,
"ctx_id": 0,
"sched_mode":0,
"hdr_mode": 1,
"width": 1920, //INPUT width
"height": 1080, //INPUT height
"frame_rate": 30, //帧率
......
"isp_stream_output_format": 1, //输出直连(on-line)到后级(PYM)
......
},
isp_stream_output_format 配置了ON-LINE 直连到PYM
"pym": {
"pym_config": {
"pym_hw_id":0, //PYM 硬件模块ID, 此处指定pym0
"pym_mode": 1, //单路Online(前级-PYM直连)模式
....
},
"pym_ctrl": {
"source_en": 1, //Enable 原图输出
"src_in_width": 1920,
"src_in_height": 1080,
"src_in_stride_y": 1920,
.....
"ds_roi_en": 1, //Enable Downscale层,按bit使能
"ds_roi_uv_bypass": 0,
"ds_roi": [
{
"ds_roi_layer": 0,
"ds_roi_sel": 1,
"ds_roi_start_top": 0,
"ds_roi_start_left": 0,
"ds_roi_region_width": 960, //原图ROI [0,0,960,540]
"ds_roi_region_height": 540,
"ds_roi_stride_y": 512,
"ds_roi_stride_uv": 512,
"ds_roi_out_width": 512, // Downscale to [512,512]
"ds_roi_out_height": 512
},
......
}
},
"gdc": {
.......
}
}
示例中配置了Downscale的第一层,从原图中的[0,0, 960,540]的ROI区域缩放到[512, 512]的图像大小,该大小是后续推理模型的输入尺寸。
-
4.2.2 运行模式
到此时可以深入代码进行解读CameraInputModule的运行机制。首先看类图理解继承关系和接口。
输入Module类图
InputModule在被Start后将创建一个工作线程(produce_thread_),该线程持续的获取输入数据(CameraInputModule从金字塔读取Frame data,NetworkInputModule从网络接收Image data),并将数据传递给下游Module(如InferenceModule,CodecModule)
void InputModule::Run() {
while (this->HasNext() && !stop_) {
std::shared_ptr<VioMessage> vio_msg = vio_msg_pool_->GetSharedPtr(1);
if (!vio_msg) {
continue;
}
int32_t frame_id = NextFrameId(); //Generate next frame id
if (this->Next(vio_msg->pym_image_) != 0) { // Read data in Sub-Class
std::this_thread::sleep_for(std::chrono::milliseconds(5));
continue;
}
if (frame_id % sample_freq_ == 0 && (vio_msg->pym_image_->pym_context_ ||
vio_msg->pym_image_->src_context_)) {
....
InferenceModule::GetInstance()->Feed(vio_msg); // To inference
CodecModule::GetInstance()->Feed(vio_msg); // To encode
}
}
}
4.3 InferenceModule
模型推理模块主要实现目标检测模型的推理,如何在J5上部署模型,可以参考OpenExplorer中的参考示例(ddk/samples/ai_toolchain/horizon_runtime_sample/code/00_quick_start
),这里对使用推理库(libDNN) APIs进行模型部署的流程不做太多介绍,重点说明在通过视频通路金字塔作为输出时的数据处理。
-
4.3.1 检测模型信息
示例中集成了一个编译好的目标检测模型(configs/model/fcos_efficientnetb0_mscoco.hbm
),该模型是efficientnetb0为backbone的FCOS模型,提供对车辆、行人等目标的检测能力。该模型在交付包中提供了实现源码(ddk/samples/ai_toolchain/horion_model_train_sample/release_modles/fcos_efficientnetb0_mscoco
)。
模型的基本信息:
model name: fcos_effb0_test_model-
input[0]:-
name: arg0[img]-
input source: HB_DNN_INPUT_FROM_PYRAMID-
valid shape: (1,3,512,512,)-
aligned shape: (1,3,512,512,)-
aligned byte size: 393216
从模型信息看到,input source是HB_DNN_INPUT_FROM_PYRAMID, 输入源是金字塔,输入类型是NV12。Valid shape 表示模型输入的有效shape是 1x3x512x512, 所以上面介绍的金字塔配置部分,配置了从Camera输入原图Downscale到512x512的尺寸,直接可作为模型的推理输入数据。
-
4.3.2 异步处理机制
在InferenceModule中实现了数据异步处理的机制,创建了一个queue,用于缓存从上游Feed的Frame Data,在Module Start时创建的工作线程(produce_thread),从queue读取数据进行模型推理,实现上下游的执行解耦。该机制在其他Module(例如PostProcessModule)中都有使用,将不再赘述。
异步处理机制
-
4.3.3 Zero-Copy
从视频通路金字塔输出的数据需要传递给BPU作为算法模型推理的输入,是否需要内存复制?回答是否,因为金字塔与BPU可以共享DDR,金字塔的输出内存地址可以直接给BPU进行处理,实现Zero-Copy,实现极致的性能。-
从金字塔输出的图像格式是NV12_SEPARATE,模型输入Tensor 内存数据需要将Y和UV分量数据分开赋值,因为共享内存,只需要将相应的物理地址和虚拟地址赋值到Input Tensor memory中即可,无需memory copy。具体的实现逻辑可以阅读DoInference()中的实现代码。-
在上面的分析中看到,在金字塔的配置中配置的是DownScale的第0层,所以获取输出数据的来源是roi_ds_[0]
void DoProcess() {
//Assign physical address of Y
input_tensor->sysMem[0].phyAddr =
pyramid_message->pym_image_->roi_ds_[0].y_paddr + y_offset;
//Assign virtual address of Y
input_tensor->sysMem[0].virAddr =
reinterpret_cast<uint8_t *>(pyramid_message->pym_image_->roi_ds_[0].y_vaddr) + y_offset;
input_tensor->sysMem[0].memSize = ALIGNED_16(aligned_width * valid_height);
//Assign physical address of UV
input_tensor->sysMem[1].phyAddr =
pyramid_message->pym_image_->roi_ds_[0].c_paddr + c_offset;
//Assign virtual address of UV
input_tensor->sysMem[1].virAddr =
reinterpret_cast<uint8_t *>(pyramid_message->pym_image_->roi_ds_[0].c_vaddr) + c_offset;
input_tensor->sysMem[1].memSize = ALIGNED_16(aligned_width * valid_height / 2);
......
}
其中的y_offset/c_offset 是为了支持对图像进行crop,本示例中无需crop,所以运行时这两个变量为0,无需考虑。-
推理的输出output_tensor 进行简单封装后传递给下游PostProcessModule进行后处理。
PostProcessModule::GetInstance()->Feed(postprocess_message);
4.4 PostprocessModule
后处理模块的运行机制与InferenceModule类似,功能上实现了NMS(non maximum suppression,非极大值抑制算法),此处不做更多解读,网络上有很多材料可以学习。-
通过后处理的配置文件(configs/model/postprocess_config.json)可以微调部分参数,例如score_threshold, iou_threshold等,用于改善误检或漏检的效果。
该模块将BBox数据传递给WebDisplayModule,用于与CodecModule编码输出后的JPEG进行合并渲染,并提供可视化功能。
WebDisplayModule::GetInstance()->FeedFrame(out_message);
5.渲染效果
到此处,主要的模块解读完成,最后根据示例代码中的README,根据接入的摄像头型号的不同简单的调整一下config,运行一下示例,既可在浏览器中查看检测输出的可视化效果。如下图,是通过示例中集成的图片回灌得到的渲染效果,后处理配置中的score_threshold微调到0.61。
渲染效果