HD介绍及移植调试参考
1. 概述¶
HD (human detect),人形检测。
通过神经网络算法来识别一副图像中有没有人及每个人的具体位置坐标。算法库是libfaster_HD_ARM.a
,不同于传统的把目标对象物化然后在画面中进行逐一比对来查找是否有人;
deep learning 的 one-stage detector,不是 ROI-based,其不需要region proposal阶段。直接产生物体的类别概率和位置坐标值,经过单次检测即可直接得到最终的检测结果,因此有着更快的检测速度。
计算时间与传进去的画面大小相关(分辨率大小),如果传进去的大小是固定的,不管里面有多少人,计算时间都是固定的;检测过程会在潜在目标人形范围画大小不同的框,最后根据框的重合度挑选一个最像人形的。
数据的识别过程如图:
图1-1 数据处理流程图
2. 算法库导入¶
HD的算法库是libfaster_HD_ARM.a
,公版mixer编译时,需要把Makefile的USE_HC_HD = 1(默认是2);
公版库的路径是:
project\release\ipc\i6b0\common\uclibc\4.9.4\ex_libs\static
头文件ssnn.h
路径是:
project\release\include
客户端请按照对应方式导入libfaster_HD_ARM.a
库。
目前最新的算法库里面包含了两套模型库:
-
model 0
用于白天场景。
-
model 1
用于夜视场景。
3. 公版测试Mixer 跑HD指令¶
测试model 0即白天模型:
/customer/mixer -n 3 -R 1920_1080_704_576_1920_1080 -V 1_2_0 -L 3_0_1 -v 1_1_4 -s 30 -f 30_30_1 -c 0_0_0_2_5_0_0_0_352_288 –o
测试model 1即夜视场景:
/customer/mixer -n 3 -R 1920_1080_704_576_1920_1080 -V 1_2_0 -L 3_0_1 -v 1_1_4 -s 30 -f 30_30_1 -c 0_0_0_4_5_0_0_0_352_288 –o
-c说明 :
-
第四个参数等于2,表示要跑HD model 0;
-
第四个参数等于 4, 表示要跑HD model 1;
-
第五个参数等于5,表示检测帧率最大为5,实际帧率需要根据算法运行时间决定。
352*288是DIVP src的分辨率。可以不设,默认值是352*288,用于SAD模块计算运动区域范围。
-o :
画OSD,当有目标时,在目标范围画框以标识目标。
4. Demo 流程说明¶
4.1. 初始化通道¶
-
DIVP通道
分辨率是352*288或者指定其他分辨率,可以接到VPE的port3(此时独占整个DVIP),或者通过frame mode 从port2接出。此通道数据将用于做运动区域检测。
-
VPE port 2通道
需要设置usrdepth >= 1, 从VPE的port2拿data去做人形检测,可以和子码流共用一路数据流。
4.2. HD 初始化mid_hchd_Initial¶
4.2.1. Module_SAD() 初始化¶
-
init IVE
MI_IVE_Create(ive_handle)
用于CSC转换,把YUV 转为RGB 。
-
AllocateImage 申请内存存放data
获取VPE port2 的参数
int width_port2=0,height_port2=0; MI_VPE_PortMode_t stVpeMode; memset(&stVpeMode,0,sizeof(MI_VPE_PortMode_t)); MI_VPE_GetPortMode(0, 2, &stVpeMode); width_port2 = stVpeMode.u16Width; height_port2 = stVpeMode.u16Height;
申请内存用于存放image,大小为port2的size
ModuleTest_AllocateImage(&RGB_image1, E_MI_IVE_IMAGE_TYPE_U8C3_PACKAGE, width_port2, height_port2, ALIGN_UP(height_port2, 32))
4.2.2. HD神经网络算法cfg 配置并且初始化算法¶
cfg.target_height = ALIGN_UP(g_ieHeight, 32); //align 32 目标图像的高,不一定等于g_ieHeight,可缩放 cfg.target_width = g_ieWidth; //align 32 cfg.max_detection = HCHD_DETECT_MAX-1; //最大检测个数,需要注意的是多少并不影响算法速度。 cfg.nms_thresh = 0.5; //建议不要动,会影响两个人站在一起的识别准确度。(识别为一个人还是2个) cfg.prob_thresh = HCHD_PROBABILITY; //人形灵敏度,太高容易漏判,太低容易误判 cfg.num_threads = 1; Init_Network(&ntwk_handle, &cfg) //初始化网络
根据-c 参数param的值,切换网络模型
network_type = param; if(network_type == SSNN_Network_Type_HDS) { Change_Model(ntwk_handle, 1);//HD3S,夜视场景model } else if(network_type == SSNN_Network_Type_HD) { Change_Model(ntwk_handle, 0);//HD3 白天场景model }
PS:cfg在Init_Network后依然可以重新修改,新的修改将在人形检测函数调用后生效。
4.2.3. SAD 移动侦测配置¶
sad_ctrl.eOutCtrl = E_MI_IVE_SAD_OUT_CTRL_16BIT_BOTH; sad_ctrl.eMode = E_MI_IVE_SAD_MODE_MB_8X8; sad_ctrl.u16Thr = SAD_BLOCK_SIZE * SAD_BLOCK_SIZE * MD_PIXEL_DIFF; sad_ctrl.u8MinVal = 0; sad_ctrl.u8MaxVal = 255;
以上设置一般无需修改,MD_PIXEL_DIFF 指两帧img 差多少个pixel算画面有运动。
4.2.4. HD线程mid_hchd_Task 创建¶
pthread_create(&g_pthread_hchd, NULL, mid_hchd_Task, (void *)param);
4.3. mid_hchd_Task 解析¶
4.3.1. HC mode和HD mode介绍¶
HC是只检测画面中有多少个人,不返回坐标;
HD是检测并返回画面多个人并且返回每一个人的坐标。
4.3.2. 帧率控制¶
getInterval = 1000000 / g_ieFrameInterval;
通过两次时间戳差值,保证在没有检测到运动画面时,检测帧率不大于g_ieFrameInterval。
4.3.3. 取DIVP port的数据¶
if(nHC_keepYuv_count == 1){ memset(&stBufInfo,0,sizeof(MI_SYS_BufInfo_t)); if (MI_SUCCESS != MI_SYS_ChnOutputPortGetBuf(&stDivpChnOutputPort , &stBufInfo, &hHandle_buffer1)) { //printf("MI_SYS_ChnOutputPortGetBuf 1 is fail \n"); usleep(20*1000); continue; } Y_image1.eType = E_MI_IVE_IMAGE_TYPE_U8C1; Y_image1.apu8VirAddr[0] = (MI_U8*)stBufInfo.stFrameData.pVirAddr[0]; Y_image1.aphyPhyAddr[0] = (MI_PHY)stBufInfo.stFrameData.phyAddr[0]; Y_image1.u16Height = ALIGN_UP(g_ieHeight, 32); Y_image1.u16Width = g_ieWidth; Y_image1.azu16Stride[0] = g_ieWidth; nHC_keepYuv_count = 0; } else if(nHC_keepYuv_count == 0) { memset(&stBufInfo,0,sizeof(MI_SYS_BufInfo_t)); if (MI_SUCCESS != MI_SYS_ChnOutputPortGetBuf(&stDivpChnOutputPort , &stBufInfo, &hHandle_buffer0)) { //printf("MI_SYS_ChnOutputPortGetBuf 2 is fail \n"); usleep(20*1000); continue; } Y_image0.eType = E_MI_IVE_IMAGE_TYPE_U8C1; Y_image0.apu8VirAddr[0] = (MI_U8*)stBufInfo.stFrameData.pVirAddr[0]; Y_image0.aphyPhyAddr[0] = (MI_PHY)stBufInfo.stFrameData.phyAddr[0]; Y_image0.u16Height = ALIGN_UP(g_ieHeight, 32); Y_image0.u16Width = g_ieWidth; Y_image0.azu16Stride[0] = g_ieWidth; nHC_keepYuv_count = 1; }
间隔几帧抓两次。将会用于计算SAD值,这个有区别于之前连续抓两次的做法,使得对运动图像的检测能力更强,如下图:
图4-1 图像检测做法对比
原始做法:抓连续两张图像计算SAD值。
新的做法:跳帧取两张图像计算SAD值。
4.3.4. 移动侦测判断MD_preprocess¶
MI_IVE_Sad(handle->ive_handle,handle->Y_image0 , handle->Y_image1, handle->SadResult, handle->ThdResult,handle->sad_ctrl, 0)
调用IVE的接口,计算前后两帧画面的pixel 的差值。并且返回存在差值的区域坐标。
需要说明的是,SAD的计算是有硬件模块支持。
sad_height = sad_y_max - sad_y_min + 1; sad_width = sad_x_max - sad_x_min + 1;
通过X、Y坐标得到运动区域范围。
4.3.5. 运动区域内存data起始位置计算,并且将data转为RGB格式¶
新的版本中,改为取VPE port2的数据做人形检测,不再使用MD通道的数据了;
先获得VPE PORT2 的参数,并且获取一帧data。
stVpeChnOutputPort.eModId = E_MI_MODULE_ID_VPE; stVpeChnOutputPort.u32ChnId = 0; stVpeChnOutputPort.u32DevId = 0; stVpeChnOutputPort.u32PortId = 2; printf("MD: %d %d %d %d --->> ",sad_x_min,sad_y_min,sad_x_max,sad_y_max); MI_VPE_GetPortMode(0, 2, &stVpeMode); width_port2 = stVpeMode.u16Width; height_port2 = stVpeMode.u16Height; MI_SYS_ChnOutputPortGetBuf(&stVpeChnOutputPort , &stBufInfo, &hHandle_buffer2) YUV_image.eType = E_MI_IVE_IMAGE_TYPE_YUV420SP; YUV_image.u16Height = ALIGN_UP(/*g_ieHeight*/height_port2, 32); YUV_image.u16Width = /*g_ieWidth*/width_port2; YUV_image.azu16Stride[0] = /*g_ieWidth*/width_port2; YUV_image.azu16Stride[1] = /*g_ieWidth*/width_port2; YUV_image.apu8VirAddr[0] = (MI_U8*)stBufInfo.stFrameData.pVirAddr[0]; YUV_image.apu8VirAddr[1] = (MI_U8*)stBufInfo.stFrameData.pVirAddr[1]; YUV_image.aphyPhyAddr[0] = (MI_PHY)stBufInfo.stFrameData.phyAddr[0]; YUV_image.aphyPhyAddr[1] = (MI_PHY)stBufInfo.stFrameData.phyAddr[1];
根据PORT的resolution和MD通道计算到的运动范围,重新做映射。
sad_x_min = sad_x_min * width_port2 / g_ieWidth; sad_y_min = sad_y_min * height_port2 / g_ieHeight; sad_x_max = sad_x_max * width_port2 / g_ieWidth; sad_y_max = sad_y_max * height_port2 / g_ieHeight; YUV_image.apu8VirAddr[0] += offset_y0 + offset_x; YUV_image.apu8VirAddr[1] += ((offset_y1/2)+ offset_x); YUV_image.u16Width = sad_width; YUV_image.u16Height = sad_height;
如果使用的是白天的模型,则需要把YUV数据转为RGB ,再送给人形库做检测;
如果使用的是红外的模块,则不需要转换。
if (SSNN_Network_Type_HDS != network_type) { RGB_image1.u16Width = sad_width; RGB_image1.u16Height = sad_height; RGB_image1.azu16Stride[0] = sad_width; csc_ctrl.eMode = E_MI_IVE_CSC_MODE_PIC_BT601_YUV2RGB; ret = MI_IVE_Csc(ive_handle,&YUV_image,&RGB_image1,&csc_ctrl,1); if(0 != ret) { printf("[MVE] MI_IVE_CSC error!!! ret[0x%x]\n",ret); } }
4.3.6. 执行HD算法¶
cfg.target_height = ALIGN_UP(sad_height, 32); cfg.target_width = ALIGN_UP(sad_width, 32); if (SSNN_Network_Type_HDS != network_type) { if (Forward_Network(ntwk_handle, RGB_image1.apu8VirAddr[0], sad_height, sad_width, 3)) { printf("[HD] Forward error!!!\n"); } } else { if (Forward_Network_Stride(ntwk_handle, YUV_image.apu8VirAddr[0], sad_height, sad_width,1, YUV_image.azu16Stride[0])) { printf("[HD] Forward error!!!\n"); } if(MI_SUCCESS != MI_SYS_ChnOutputPortPutBuf(hHandle_buffer2)){ printf("%s %d MI_SYS_ChnOutputPortPutBuf 2 is error!\n",__func__,__LINE__); break; } }
Forward_Network是HD算法接口,这个函数会相当耗时。
如果夜间模式,则调用Forward_Network_Stride 接口。
4.3.7. 根据HD返回的人形坐标画OSD¶
human_cnt = Get_Detection(ntwk_handle, sad_height, sad_width); drawRect[i].u16X = (ntwk_handle->boxes[i].x_min + sad_x_min); drawRect[i].u16Y = (ntwk_handle->boxes[i].y_min + sad_y_min); drawRect[i].u16Width = (ntwk_handle->boxes[i].x_max - ntwk_handle->boxes[i].x_min) - 1; drawRect[i].u16Height = (ntwk_handle->boxes[i].y_max - ntwk_handle->boxes[i].y_min) - 1;
获取计算结果,画OSD
HCHDtoRECT(chn,drawRect,human_cnt);
4.3.8. 释放DIVP的内存¶
MI_SYS_ChnOutputPortPutBuf(hHandle_buffer1) MI_SYS_ChnOutputPortPutBuf(hHandle_buffer0)
5. 神经网络API说明¶
-
初始化一个句柄
int Init_Network(NetworkHandle **phandle, network_config *cfg);
-
执行算法
void Release_Network(NetworkHandle **phandle); int Forward_Network(NetworkHandle *handle, unsigned char *data, int height, int width, int color);
-
获取HD/FD 的检测结果,坐标保存在handle里面
int Get_Detection(NetworkHandle *handle, int img_height, int img_width); // Only for detector
-
获取HC的结果
int Get_Prob(NetworkHandle *handle);
-
切换算法模型
int Change_Model(NetworkHandle *handle, int network_index);
-
获取网络模型,参考E_NETWORK_TYPE
int Get_Num_Classes(NetworkHandle *handle); int Get_Network_Type(NetworkHandle *handle);
-
获取图像数据对齐格式。
int Get_Size_Alignment(NetworkHandle *handle);
6. 神经网络结构体说明¶
-
E_NETWORK_TYPE
enum E_NETWORK_TYPE { CLASSIFIER, //For HD FD DETECTOR //For HC };
-
NetworkHandle
typedef struct { network_config *cfg; //算法config void *net; BBox *boxes; // Only for detector float *probs; // Only for classifier int num_detection; }NetworkHandle;
算法句柄,通过Init_Network 接口来create
-
BBox
typedef struct { short int x_min, y_min; // top-left corner short int x_max, y_max; // bottom-right corner short int class_idx; float prob; }BBox;
用于描述一个目标的具体坐标
-
network_config
typedef struct { // == Model-dependent settings == // int target_height; //图像大小 int target_width; // == General settings == // float prob_thresh; //设置与目标的匹配度 float nms_thresh; // int num_threads; int max_detection; //最大探测人数 }network_config;
7. FAQ¶
-
误判与漏判平衡
cfg.prob_thresh = HCHD_PROBABILITY;
可以修改匹配度,越大表示需要符合的特征越多,越接近人形模型,此时误判减少,漏判可能增加;
HCHD_PROBABILITY越小则相反,此时漏判会减少,误判则可能增加。
-
框滞后
因为CPU性能的原因,HD算法只能跑到5帧左右,如果人移动太快,画出来的框还在前几帧的位置 ,就会看到框比实际人滞后的情况,可以导入OSD tracking。
相关的
patch:osd_tracking.rar
随demo同时release。原理:在HD检测频率不变的情况下,增加MD的检测频率,并且计算前后两次画面的内容偏移值,然后再上 一次HD人形坐标的基础上加上偏移值从新更新OSD 框的位置。
注意:这个修改会使CPU提高5~8%左右,并且增加约1M的内存使用。
-
运行一段时间后OOM,或者sys 占CPU突然飙升
HD算法在计算时会需要很多内存,假设cfg.target_width =128、cfg.target_height=128;
使用的峰值内存大小会到5M,如果此时系统内存不足并且cache也没有可释放内存,则出现OOM;
如果频繁释放cache内存,则可能导致sys 占用的CPU飙升,从而影响帧率。
-
CPU 使用率
目前公版cfg.target_height = 288, cfg.target_width =352,HD线程CPU最大使用在60% 左右。
开启MD后根据运动范围大小,CPU使用率动态波动。
-
一个人画了两个大小不一样的画框
算法在识别到一个人型对象时,会在这个对象身上画大大小小不同的框,然后根据特征留下一个最像人形的坐标,但如果算法觉得有两个都很匹配,则都留下,这样就看到一个人被画了两个框的情形;
可以通过
cfg.nms_thresh
参数调节,但是参数调节过低可能导致两个站一起很近的人,被判断为一个人,从而只有一个框。 -
手或者脚可以识别
算法RD在训练算法库的模型时,有把不完整的人体如手、脚的放进去学习,故此算法可以识别到人的手或者脚,其他如半个脑袋的图像也会识别出来,但是因为不完整,可能存在漏判。