随着 AI 技术的不断发展,从 Sensor 采集数据,经过处理之后给 AI 模型做图像分析已经变成一个很典型的应用。因此,在嵌入式应用行业,无论是汽车,安防,手机等应用中,对 Sensor 采集的数据做图像处理变得非常重要。针对以上需求,Cambricon CE3226 内部集成了多个复杂的硬件组件,并实现了对应的中间层软件库,能够满足客户对视频处理的各种要求。
本系列博文会对 CNMPS(Cambricon Media Process System,寒武纪媒体处理系统)各个组件功能,工作原理作详细介绍,并对中间层软件作系统分析,方便客户编写自己的应用程序。
本文是该系列博文的第二篇,介绍视频输入系统的组成,工作原理以及软件架构。
一、基本概念
VI(Video Input,视频输入)模块,支持通过 MIPI Rx(包括 MIPI 接口和 LVDS 接口)、BT.656、BT.1120、
BT.601 和 DC 等接口采集视频数据。VI 可以将采集数据通过总线直接发送给其他模块,或者先将数据存入指定的内存区域,对数据进行处理后,再发送给其他模块。
- 视频输入设备
视频输入设备封装视频采集接口,接收接口视频原始数据后,输出到视频输入物理 PIPE(视频数据处理单元)行处理。
- 视频输入物理 PIPE
视频输入物理 PIPE 绑定视频输入设备,接收设备发送数据,调用 ISP、算法等模块,进行图像分析处理。
- 视频输入虚拟 PIPE
视频输入虚拟 PIPE 不绑定视频输入设备,接收其他模块或用户发送过来的数据,调用 ISP、算法等模块,进行图像分析处理。(注释:虚拟pipe就是说,数据不是sensor通过dev送进来的,而是用户通过软件,send frame进来的。比如用接口cnviSendPipeRaw 发送数据给虚拟pipe。)
- 视频物理通道
视频物理通道接收视频输入 PIPE 图像数据后,可以调用其他硬件模块,实现裁剪、缩放、遮挡等功能,再输出图像数据到 DDR。
- 视频扩展通道
视频扩展通道绑定视频物理通道,调用其他模块实现裁剪、缩放、遮挡、鱼眼等功能,输出多路图像数据到 DDR。
- PIPE 的工作模式
参考 VI 和 VPPS 工作模式 章节的详细描述。
- 镜头畸变校正
LDC(Lens Distortion Correction,镜头畸变校正),一些透镜由于制造精度以及组装工艺的偏差会引入畸变,需要针对畸变进行图像校正。
- 防抖
DIS(Digital Image Stabilization,防抖)模块使用防抖算法计算当前图像与前一帧图像的偏移,得到在各个轴方向上的抖动偏移矩阵,然后对当前图像进行校正,达到防抖目的。
二、功能描述
VI 在软件层次上分为输入设备、输入 PIPE(包含物理 PIPE 和虚拟 PIPE)、物理通道(PHY_CHN)和
扩展通道(EXT_CHN)4 个部分,如图(1)所示:
图(1)
CE3226V100 VI 输出通道功能如下图(2):
图(2)
- 视频输入设备
VI 设备之间相互独立,最多支持 8 个设备。
- 视频输入 PIPE
VI 的 PIPE 包含 ISP、TDNR 等相关图像处理功能,输出 YUV 图像格式给通道。物理 PIPE 和虚拟 PIPE
可以同时运行,但是总个数不能超过 8 个。
- 视频物理、扩展通道
– CE3226V100 VI:1 个 PIPE 支持输出 1 个物理通道,输出 8 个扩展通道。
– CE3226V100 VI: 通 道 支 持 的 典 型 分 辨 率 和 帧 率 为 3840x2160@60fps 、 3840x2160@30fps 、
1080p@60fps 和 1080p@30fps 等。
- 绑定关系
– CE3226V100 设备与 MIPI/LVDS/DVP 绑定关系是固定的,不能动态修改绑定关系。
– CE3226V100 设备和时序输入接口的约束关系如下接口的绑定关系参见图(3) CE3226V100 设备与
MIPI/LVDS/BT.1120/BT.656/BT601/DC 接口的绑定关系 。
图(3)
– CE3226V100 LVDS 与 DVP 设备管脚复用关系参见图(4) CE3226V100 LVDS 与 DVP 设备管脚复用关系 。
图(4)
– PIPE 可以绑定任意设备,绑定关系为静态,不能动态修改绑定关系。
– 线性模式和 WDR 模式都是一个物理 PIPE 绑定一个设备
- 从模式
VI 的从模式模块,主要用于多 SENSOR 同步拼接。从模式与设备对应关系是固定的,不支持修改。
用户需要根据 SENSOR 管脚的连线和下表确定使用哪个从模式模块,然后选择对应的物理设备号创建
设备,否则会没数据,具体步骤如下:
– 确认硬件原理图上 SENSOR 的管脚连接到了 SENSOR_HSx/SENSOR_VSx/SENSOR_VSOUTx(可选)。
– 根据图(3)确定该 SENSOR 连接到从模式模块编号 x。
– 使用编号为 x 的设备。
图(5)
三、API & 关键代码分析
1.驱动交互 API
VI 模块的 API 函数在 /mps/out/include/cn_vi.h 头文件中,具体实现封装在/mps/out/lib/libcn_vi.so
动态库文件中,这一层 API 直接和 /mps/out/ko 路径中对应的驱动模块交互。
cn_vi.h代码片段如图(6)所示,涉及的数据类型在 /mps/out/include/cn_common_vi.h 中定义。
图(6)
- 针对 VI输入设备,实现了以下主要 API:
cnS32_t cnviSetDevAttr(viDev_t ViDev, const cnviDevAttr_t *pstDevAttr) //VI设备属性设置
cnS32_t cnviSetDevCrop(viDev_t ViDev, const cnviCropInfo_t *pstCropInfo)//VI设备剪裁属性
cnS32_t cnviEnableDev(viDev_t ViDev) //VI 设备使能
cnS32_t cnviSetDevBindPipe(viDev_t ViDev, const cnviDevBindPipe_t *pstDevBindPipe)//绑定PIPE
- 针对 VI PIPE,主要实现了:
cnS32_t cnviCreatePipe(viPipe_t ViPipe, const cnviPipeAttr_t *pstPipeAttr)//pipe 创建
cnS32_t cnviSetPipeAttr(viPipe_t ViPipe, const cnviPipeAttr_t *pstPipeAttr)//pipe 设置属性
cnS32_t cnviStartPipe(viPipe_t ViPipe) //根据 VI PIPE 号,启用 PIPE
cnS32_t cnviSetPipeDumpAttr(viPipe_t ViPipe, const cnviDumpAttr_t *pstDumpAttr)//设置DUMP属性
cnS32_t cnviSendPipeYUV(viPipe_t ViPipe, const cnVideoFrameInfo_t *pstVideoFrame, cnS32_t s32MilliSec)//通过 pipe 发送 YUV数据
cnS32_t cnviSendPipeRaw(viPipe_t ViPipe, const cnVideoFrameInfo_t *pstVideoFrame[],cnU32_t u32FrameNum, cnS32_t s32MilliSec) //通过 pipe 发送 RAW数据
cnS32_t cnviSetPipeFrameSource(viPipe_t ViPipe, const cnviEnPipeFrameSource_t enSource)//设置数据源
cnS32_t cnviSetPipeTdnrAttr(viPipe_t ViPipe, const cnviPipeTdnrAttr_t *pstNrAttr)//设置 TDNR 属性
- 针对 VI CHANNEL,实现了以下主要 API:
cnS32_t cnviSetChnAttr(viPipe_t ViPipe,viChn_t ViChn,const cnviChnAttr_t *pstChnAttr)//设置CHN属性
cnS32_t cnviEnableChn(viPipe_t ViPipe, viChn_t ViChn)//使能 CHN
cnS32_t cnviSetChnRotation(viPipe_t viPipe, viChn_t viChn, const cnEnRotation_t enRotation)//旋转
cnS32_t cnviSetChnLDCAttr(viPipe_t viPipe, viChn_t viChn, const cngdcLdcAttr_t *pstLDCAttr)//畸变
cnS32_t cnviSetChnSpreadAttr(viPipe_t viPipe, viChn_t viChn, const cngdcSpreadAttr_t *pstSpreadAttr)
cnS32_t cnviSetChnDISConfig(viPipe_t viPipe, viChn_t viChn,
const cnviDisConfig_t *pstDisConfig)//防抖属性参数
cnS32_t cnviSetChnDISAttr(viPipe_t viPipe, viChn_t viChn,
const cnviDisAttr_t *pstDisAttr)//设置防抖控制属性
cnS32_t cnviSetExtChnAttr(viPipe_t ViPipe, viChn_t ViChn, const cnviExtChnAttr_t
*pstExtChnAttr)//设置扩展通道属性
cnS32_t cnviGetChnFrame(viPipe_t ViPipe, viChn_t ViChn, cnVideoFrameInfo_t *pstFrameInfo, cnS32_t s32MilliSec)//获取通道帧数据信息
2.用户 API
为了方便客户应用开发,MPS 在以上 API 基础上,继续封装了一层 API,供开发人员直接对 VI 模块进行 设置、初始化、启动等操作。相关函数通过 /mps/sample/common/cnsample_comm.h 导出。具体函数实现在/mps/sample/common/cnsample_comm_vi.cpp 文件中实现。
2.1
cnsample_comm_vi.cpp 文件中首先按照不同的 SensorType 分别创建并填充了几组 cnviDevAttr_t、cnviPipeAttr_t、cnviChnAttr_t 结构体。三种结构体的定义如图(7)所示。分别抽象了 VI DEV、VI PIPE、
VI Channel 的属性。
图(7)
以 Sony327 的 SONY_IMX327_MIPI_2M_30FPS_12BIT 配置为例,三个结构具体成员内容见图(8)
图(8)
2.2
接下来实现了 cnsampleCommViGetFrameRateBySensor()、cnsampleCommViGetSizeBySensor()、cnsampleCommViGetWdrModeBySensor(),根据 sensor 的设置方式确定对应的帧率、pic_size、WDR模式的枚举值。
2.3
设置并启动一个 VI 通过图(9)所示的函数实现,该函数供 APP 创建 VI 时直接调用。
图(9)
cnsampleCommViStartVi()主要实现了 设置参数,创建 VI 设备,启动 Mipi,创建 ISP,以及设置 sensor 的从模式,用于 APS 全景拼接。
cnsampleCommViSetParam(pstViConfig) 为空函数,直接返回 CN_SUCESS;
cnsampleCommViCreateVi(pstViConfig) 是 VI 模块中最重要的函数,主要函数调用层次如下(描红为直接与驱动程序交互的 API):
|--cnsampleCommViCreateVi(pstViConfig)
|--cnsampleCommViCreateSingleVi(pstViInfo)
|--cnsampleCommViStartDev(pstViInfo)
|--cnsampleCommViGetDevAttrBySns(cnEnSampleSnsType_t enSnsType, cnviDevAttr_t* pstViDevAttr)
|--cnviSetDevAttr(ViDev, &stViDevAttr)
|--cnviGetDevCrop(ViDev, &stCropInfo)
|--cnviSetDevCrop(ViDev, &stCropInfo)
|--cnviEnableDev(ViDev)
|--cnsampleCommViBindPipeDev(pstViInfo)
|--cnviSetDevBindPipe(pstViInfo->stDevInfo.ViDev, &stDevBindPipe)
|--cnsampleCommViStartViPipe(pstViInfo)
|--cnsampleCommViGetPipeAttrBySns(pstViInfo->stSnsInfo.enSnsType, &stPipeAttr)
|--cnsampleCommViStartSingleViPipe(ViPipe, &stPipeAttr)
|--cnviCreatePipe(ViPipe, pstPipeAttr)
|--cnviStartPipe(ViPipe)
|--cnsampleCommViStartViChn(pstViInfo)
|--cnsampleCommViGetChnAttrBySns(pstViInfo->stSnsInfo.enSnsType, &stChnAttr)
|--cnviSetChnAttr(ViPipe, ViChn, &stChnAttr)
|--cnviEnableChn(ViPipe, ViChn)
以下介绍上述代码关键的实现过程,具体细节有兴趣的读者自行分析。
输入参数结构体 pstViConfig 的原型见图(10)
图(10)
包含了 VI 模块的设备、Pipe、Chanel 的设备编号信息以及Sensor 的数量、型号等信息,如图(11)这些参数的填充是 APP 程序首要任务。
图(11)
pstViConfig 结构体填充完成后,首先cnsampleCommViCreateVi(cnsampleViConfig_t* pstViConfig) 函数根据输入参数中的 s32WorkingViNum 数量,循环调用 cnsampleCommViCreateSingleVi(pstViInfo),创建对应数量的 VI 实体。流程如下
- 调用 cnsampleCommViStartDev(pstViInfo),开启 VI 输入设备。该函数根据传入的 SensorType,获取对应的 Dev 属性,并确定 Dev 设备编号,宽动态范围参数,调用 cnviSetDevAttr(ViDev, &stViDevAttr) 开启 Dev。
- 调用 cnsampleCommViBindPipeDev(pstViInfo) 绑定 Dev 和 Pipe。
- 调用 cnsampleCommViStartViPipe(pstViInfo),根据 SensorType 获取对应的 pipe 属性,并确定 pipe 编号、是否启动降噪。最后在 cnsampleCommViStartSingleViPipe(ViPipe, &stPipeAttr)函数中创建并启动 Pipe。
- 调用 cnsampleCommViStartViChn(pstViInfo),根据 SensorType 获取对应的 channel 属性,并确定动态域、视频格式、像素格式、是否压缩,调用 cnviSetChnAttr(ViPipe, ViChn, &stChnAttr) 设置 Channel,
最后根据 VI&VPPS 的在线离线模式,执行 cnviEnableChn(ViPipe, ViChn) 启动 Channel。
cnsampleCommViStartMipi(pstViConfig) 函数用于设置 MIPI 的时钟,时序等参数。
cnsampleCommViCreateIsp(pstViConfig) 函数用于启动 ISP,一般设置为 BYPASS,ISP 的内容较为复杂,不在本文中赘述。
cnsampleCommViStartSlaveMode(pstViConfig) 用于 APS 全景拼接,参考后续章节。
2.4
cnsampleCommViGetSensorInfo(cnsampleViConfig_t* pstViConfig) 函数用于获取 Sensor 的信息,并填充 pstViConfig 结构体,该函数也是直接供 APP 程序直接调用的重要函数。具体分析见 《Cambricon CE3226 媒体处理系统 (五):应用编程》。
参考资料:
寒武纪媒体处理系统开发者手册-CN-v0.8.0.pdf
评论