Hi35xx芯片自带硬件的移动侦测功能,要实现需要找到对应接口及配置相关通道属性。
找到“..._SDK_V1.0.4.0\mpp\sample\common\sample_comm_vda.c",查看移动侦测的实现过程。
int vda_md(VI_CHN ViChn_Md)
{
int s32Ret;
////本例,ViChn_Md参数是设置绑定的输入通道为物理输入,通道号为24
VDA_CHN VdaChn_Md = 0;////设置对应的vda通道为0.
SIZE_S stSize;
//vi通道实际是1080p的,此处输入是720p。实际移动侦测是等比缩放检测,不影响结果。
stSize.u32Width = 720;/////设置vda通道的输入视频宽高:
stSize.u32Height = 576;
s32Ret = SAMPLE_COMM_VDA_MdStart(VdaChn_Md, ViChn_Md, &stSize);
if (SUCCESS != s32Ret)
{
("VDA Md Start failed!\n");
return -1;
}
return 0;
}
int SAMPLE_COMM_VDA_MdStart(VDA_CHN VdaChn, VI_CHN ViChn, SIZE_S *pstSize)
{
HI_S32 s32Ret = HI_SUCCESS;
VDA_CHN_ATTR_S stVdaChnAttr; //设置VDA通道的属性
MPP_CHN_S stSrcChn, stDestChn;
if (VDA_MAX_WIDTH < pstSize->u32Width || VDA_MAX_HEIGHT < pstSize->u32Height)
{
("Picture size invaild!\n");
return -1;
}
/* step 1 create vda channel */
stVdaChnAttr.enWorkMode = VDA_WORK_MODE_MD;//设置工作模式为md
stVdaChnAttr.u32Width = pstSize->u32Width; ////通道宽度,传递的参数值是720
stVdaChnAttr.u32Height = pstSize->u32Height; ////通道高度,传递的参数值是576
////设置移动侦测属性结构体
stVdaChnAttr.unAttr.stMdAttr.enVdaAlg = VDA_ALG_REF; //VDA算法采样帧差法
stVdaChnAttr.unAttr.stMdAttr.enMbSize = VDA_MB_16PIXEL;//宏块大小,两种16*16和8*8单位像素。
stVdaChnAttr.unAttr.stMdAttr.enMbSadBits = VDA_MB_SAD_8BIT;//SAD输出精度
stVdaChnAttr.unAttr.stMdAttr.enRefMode = VDA_REF_MODE_DYNAMIC;
stVdaChnAttr.unAttr.stMdAttr.u32MdBufNum = 8;
stVdaChnAttr.unAttr.stMdAttr.u32VdaIntvl = 4; //侦测间隔以帧为单位
stVdaChnAttr.unAttr.stMdAttr.u32BgUpSrcWgt = 128;
stVdaChnAttr.unAttr.stMdAttr.u32SadTh = 2000; //SAD报警阈值,最大4080
stVdaChnAttr.unAttr.stMdAttr.u32ObjNumMax = 128; //运动区域最大输出个数
s32Ret = HI_MPI_VDA_CreateChn(VdaChn, &stVdaChnAttr);//创建视频侦测通道,这里主要设置好vda通道属性。
if(s32Ret != HI_SUCCESS)
{
("err!\n");
return s32Ret;
}
/* step 2: vda chn bind vi chn */
stSrcChn.enModId = HI_ID_VIU;////设置输入通道的类型,本次采用物理输入hdmi或vga
stSrcChn.s32ChnId = ViChn; ///VI物理输入通道号
stSrcChn.s32DevId = 0;////VI 和 VDEC 作为数据源,是以通道为发送者,向其他模块发送数据,用户将设备号置为 0,SDK 不检查输入的设备号。详细查看绑定接口说明。
("stSrcChn.s32ChnId=%d\n", stSrcChn.s32ChnId);
stDestChn.enModId = HI_ID_VDA;
stDestChn.s32ChnId = VdaChn;
stDestChn.s32DevId = 0;
s32Ret = HI_MPI_SYS_Bind(&stSrcChn, &stDestChn);//将vda通道和要进行md的通道进行绑定
if(s32Ret != HI_SUCCESS)
{
("err!\n");
return s32Ret;
}
/* step 3: vda chn start recv picture */
s32Ret = HI_MPI_VDA_StartRecvPic(VdaChn);////开始接受图像
if(s32Ret != HI_SUCCESS)
{
("err!\n");
return s32Ret;
}
/* step 4: create thread to get result */
gs_stMdParam.bThreadStart = HI_TRUE;
gs_stMdParam.VdaChn = VdaChn;
("gs_stMdParam.bThreadStart=%d\n", gs_stMdParam.bThreadStart);
("gs_stMdParam.VdaChn=%d\n", gs_stMdParam.VdaChn);
pthread_create(&gs_VdaPid[SAMPLE_VDA_MD_CHN], 0, SAMPLE_COMM_VDA_MdGetResult, (void *)&gs_stMdParam);////获取结果并对结果处理。
return HI_SUCCESS;
}
void *SAMPLE_COMM_VDA_MdGetResult(void *pdata)
{
HI_S32 s32Ret;
VDA_CHN VdaChn;
VDA_DATA_S stVdaData;
VDA_MD_PARAM_S *pgs_stMdParam;
HI_S32 maxfd = 0;
FILE *fp = stdout;
HI_S32 VdaFd;
fd_set read_fds;
struct timeval TimeoutVal;
pgs_stMdParam = (VDA_MD_PARAM_S *)pdata;
VdaChn = pgs_stMdParam->VdaChn;
/* decide the stream file name, and open file to save stream */
/* Set Venc Fd. */
VdaFd = HI_MPI_VDA_GetFd(VdaChn);
if (VdaFd < 0)
{
printf("HI_MPI_VDA_GetFd failed with %#x!\n", VdaFd);
return NULL;
}
if (maxfd <= VdaFd)
{
maxfd = VdaFd;
}
while (HI_TRUE == pgs_stMdParam->bThreadStart)
{
FD_ZERO(&read_fds);
FD_SET(VdaFd, &read_fds);
TimeoutVal.tv_sec = 2;
TimeoutVal.tv_usec = 0;
s32Ret = select(maxfd + 1, &read_fds, NULL, NULL, &TimeoutVal);
if (s32Ret < 0)
{
printf("select failed!\n");
break;
}
else if (s32Ret == 0)
{
printf("get venc stream time out, exit thread\n");
//break;
continue;
}
else
{
if (FD_ISSET(VdaFd, &read_fds))
{
/*******************************************************
step 2.3 : call mpi to get one-frame stream
*******************************************************/
s32Ret = HI_MPI_VDA_GetData(VdaChn, &stVdaData, HI_TRUE);////获取视频侦测分析结果
if(s32Ret != HI_SUCCESS)
{
printf("HI_MPI_VDA_GetData failed with %#x!\n", s32Ret);
return NULL;
}
/*******************************************************
*step 2.4 : save frame to file
*******************************************************/
//printf("\033[0;0H");/*move cursor*/
SAMPLE_COMM_VDA_MdPrtObj(fp, &stVdaData);////对结果进行显示处理
/*******************************************************
*step 2.5 : release stream
*******************************************************/
s32Ret = HI_MPI_VDA_ReleaseData(VdaChn,&stVdaData);////释放视频侦测分析结果。
if(s32Ret != HI_SUCCESS)
{
printf("HI_MPI_VDA_ReleaseData failed with %#x!\n", s32Ret);
return NULL;
}
}
}
usleep(1000);
}
return 0;
}
int SAMPLE_COMM_VDA_MdPrtObj(FILE *fp, VDA_DATA_S *pstVdaData)
{
VDA_OBJ_S *pstVdaObj;
int i;
sint32 x0, y0, width, hight;
////bObjValid为MD结果的有效性,是否有效是根据u32SadTh设置的值决定的。运动区域内宏块以u32SadTh值为界
if (HI_TRUE != pstVdaData->unData.stMdData.bObjValid)
{
printf("bMbObjValid = FALSE.\n");
return HI_SUCCESS;
}
////有效运动区域大于2个时,才进行打印
if(pstVdaData->unData.stMdData.stObjData.u32ObjNum > 2)
{
printf("ObjNum=%d, IndexOfMaxObj=%d, SizeOfMaxObj=%d, SizeOfTotalObj=%d\n", \
pstVdaData->unData.stMdData.stObjData.u32ObjNum, \
pstVdaData->unData.stMdData.stObjData.u32IndexOfMaxObj, \
pstVdaData->unData.stMdData.stObjData.u32SizeOfMaxObj,\
pstVdaData->unData.stMdData.stObjData.u32SizeOfTotalObj);
for (i=0; i
{
////检测结果放在pstAddr 为首地址的内存中
pstVdaObj = pstVdaData->unData.stMdData.stObjData.pstAddr + i;
printf("==>left=%d, top=%d, right=%d, bottom=%d\n", i, \
pstVdaObj->u16Left, pstVdaObj->u16Top, \
pstVdaObj->u16Right, pstVdaObj->u16Bottom);
x0 = pstVdaObj->u16Left;
y0 = pstVdaObj->u16Top;
width = pstVdaObj->u16Right - pstVdaObj->u16Left;
hight = pstVdaObj->u16Bottom - pstVdaObj->u16Top;
////运动区域是(x0,y0)为左上角坐标,width为宽,hight为高的矩形框里,这些坐标是对应输入分辨率stSize.u32Width = 720;stSize.u32Height = 576;图像的坐标,图像左上角坐标为(0,0)
}
}
return HI_SUCCESS;
}
void SAMPLE_COMM_VDA_MdStop(VDA_CHN VdaChn, VI_CHN ViChn)
{
HI_S32 s32Ret = HI_SUCCESS;
MPP_CHN_S stSrcChn, stDestChn;
printf("gs_stMdParam.bThreadStart=%d\n", gs_stMdParam.bThreadStart);
/* join thread */
if (TRUE == gs_stMdParam.bThreadStart)
{
gs_stMdParam.bThreadStart = HI_FALSE;
printf("gs_stMdParam.bThreadStart=%d\n", gs_stMdParam.bThreadStart);
pthread_join(gs_VdaPid[SAMPLE_VDA_MD_CHN], 0);
}
/* vda stop recv picture */
s32Ret = HI_MPI_VDA_StopRecvPic(VdaChn);
if(s32Ret != HI_SUCCESS)
{
printf("err(0x%x)!!!!\n",s32Ret);
}
/* unbind vda chn & vi chn */
stSrcChn.enModId = HI_ID_VIU;
stSrcChn.s32ChnId = ViChn;
stSrcChn.s32DevId = 0;
stDestChn.enModId = HI_ID_VDA;
stDestChn.s32ChnId = VdaChn;
stDestChn.s32DevId = 0;
s32Ret = HI_MPI_SYS_UnBind(&stSrcChn, &stDestChn);
if(s32Ret != HI_SUCCESS)
{
printf("err(0x%x)!!!!\n", s32Ret);
}
/* destroy vda chn */
s32Ret = HI_MPI_VDA_DestroyChn(VdaChn);
if(s32Ret != HI_SUCCESS)
{
printf("err(0x%x)!!!!\n", s32Ret);
}
return;
}
①.注意查看VI 的有效物理通道绑定的 VI 设备号,对应程序中的逻辑通道。通过cat /proc/umap/vi
②.MD原理,将输入图像,根据最小单位宏块(16*16 或 8*8像素)分成相应个数的大小,然后检测各宏块的sad值(根据背景图像和当前视频差异判断出两帧图像相应宏块之间的亮度差的绝对值之和)。同时也可以输出变化区域,变化区域的坐标、每个宏块的sad值。
例如:
以vi输入通道2为例,换算成物理输入通道为ToHisViChn(High , _s32Chn)=2*8=16,输入图像大小按CIF为352*288,定义宏块大小为 16x16=stVdaChnAttr.unAttr.stMdAttr.enMbSize = VDA_MB_16PIXEL,则图像可以分成22列18行。然后检测获得结果在定义为VDA_DATA_S的结构体中。
③.因为vda设置了最多通道数。在SAMPLE_COMM_VDA_MdStart(VDA_CHN VdaChn, VI_CHN ViChn, SIZE_S *pstSize)函数中,根据模块号 stSrcChn.enModId不同,绑定目标数据的属性中的通道号就不同。比如当模块号为HI_ID_VDEC解码时,那么stDestChn.s32ChnId则和HI_ID_VDEC解码的通道ID=stSrcChn.s32ChnId一致;而当模块号为HI_ID_VIU输入通道时,那么stDestChn.s32ChnId则和输入的物理通道号除8一致。
④.在HI_MPI_SYS_Bind函数中VI 和 VDEC 作为数据源时,是以通道s32ChnId为发送者,向其他模块发送数据,用户将设备号s32DevId置为0,SDK 不检查输入的设备号。
⑤.如果检测输出的结果很多,不停打印,说明检测太灵敏。需要设置 stVdaChnAttr.unAttr.stMdAttr.u32SadTh值大些,最大4080.可以理解成亮度的变化值,本例中调试设置值成2000.
⑥.其他详细说明可查看“HiMPP V3.0 媒体处理软件开发参考.pdf”文档中“7.视频侦测分析”。
⑦.若用户自己实现时,开始md只需要调用“vdamd.c”文件里函数vda_md(VI_CHN ViChn_Md),注意ViChn_Md和stSrcChn.enModId及stSrcChn.s32ChnId需要对应,查看③。关闭md时,调用“vdamd.c”文件里函数SAMPLE_COMM_VDA_MdStop,注意解绑时的 stSrcChn.enModId 和 stSrcChn.s32ChnId = ViChn。
⑧.如果“.c”文件中注释为乱码,那么可以用记事本或Notepad++打开文件查看。