OpticalFlowPyrLK

OpticalFlowPyrLK算子用于计算连续多帧图像特定像素点的变化,常用于目标特征点跟踪、提升BOX稳定性等场景。

算子效果

输入连续帧图像参数输出图像
image-image

原理

稀疏光流算法即Lucas-Kanade算法,是一种计算机视觉中常用的运动估计方法,用于估计图像序列中像素的运动方向和速度。Lucas-Kanade算法基于两个假设:

  1. 亮度恒定假设:在短时间内,同一个物体的像素亮度保持不变。

假设 tt 时刻,位于 (x,y)(x,y) 像素位置的物体,在 t+Δtt+Δt 时刻位于 (x+u,y+v)(x+u,y+v) 位置,基于亮度不变假设有:

I(x,y,t)=I(x+u,y+v,t+Δt)I(x,y,t)=I(x+u,y+v,t+\Delta t)

将等式右边进行一阶泰勒展开得:

I(x+u,y+v,t+Δt)=I(x,y,t)+Ixu+Iyv+ItΔtI(x+u,y+v,t+\Delta t)=I(x,y,t) + I'_xu + I'_yv+I'_t\Delta t

由上面两个公式可以得到:

Ixu+Iyv+ItΔt=0I'_xu + I'_yv+I'_t\Delta t=0

整理后可表示成:

[Ix,Iy][uv]=ΔIt\begin{aligned}\begin{bmatrix} I'_x , I'_y \end{bmatrix}\begin{bmatrix} u \\v \end{bmatrix}=-\Delta I_t \end{aligned}

其中,IxI'_xIyI'_y 分别为 (x,y)(x,y) 像素点处图像亮度在 xx 方向和 yy 方向的偏导数,即图像xxyy方向的梯度。ΔItΔI_t 即为两图之间的 (x,y)(x,y) 坐标位置的亮度差。

  1. 邻域光流相似假设:一个小的图像区域里像素移动方向和大小是基本一致的。

借助该假设,像素点 (x,y)(x,y) 领域内的所有像素都有下面的公式:

[Ix1,Iy1Ix2,Iy2][uv]=[ΔIt1ΔIt2]\begin{aligned}\begin{bmatrix} I'_{\text{x1}} , I'_{\text{y1}} \\I'_{\text{x2}} , I'_{\text{y2}} \end{bmatrix}\begin{bmatrix} u \\v \end{bmatrix}= \begin{bmatrix} -\Delta I_{\text{t1}} \\-\Delta I_{\text{t2}} \end{bmatrix}\end{aligned}

上式即为 Ax=bAx=b 的形式,可求得光流向量的最小二乘解:

x=(ATA)-1ATbx=(A^TA)^{\text{-1}}A^Tb

其中要求 ATAA^TA 可逆,为了满足这个要求,Lucas-Kanade方法选取角点作为特征点。除了基于亮度不变假设和邻域光流相似假设,Lucas-Kanade算法还借助了图像金字塔的方式解决图像偏移较大的情况,在高层低分辨率图像上,大的偏移将变为小的偏移,从而求解出光流。

因此,OpticalFlowPyrLK算子需要前后两帧的金字塔图层,和前一帧的特征点作为输入,其中特征点通常选用角点。 连续帧计算会放大误差,此时还需要引入当前帧的先验特征点,用来修正误差。

API接口

int32_t hbVPOpticalFlowPyrLK(hbUCPTaskHandle_t *taskHandle, hbVPArray *currPoints, hbVPArray *currPointsStatus, hbVPArray *currPointsConf, hbVPArray const *prevPoints, hbVPImage const *currPym, hbVPImage const *prevPym, hbVPLKOFParam const *lkofParam);

详细接口信息请查看 hbVPOpticalFlowPyrLK

使用方法

// Include the header #include "hobot/hb_ucp.h" #include "hobot/vp/hb_vp.h" #include "hobot/vp/hb_vp_opticalflow_pyrlk.h" // init Image, allocate memory for multilayer gaussian pyramid layer array int32_t top_k{15}; int32_t frame_num{5}; int32_t_t numLayers{5}; uint32_t capacity{5000U}; int32_t src_width = 1280; int32_t src_height = 720; hbUCPSysMem arr_mem0; hbUCPMallocCached(arr_mem0, capacity * sizeof(hbVPKeyPoint), 0); hbVPArray arr0{arr_mem0.phyAddr, arr_mem0.virAddr, arr_mem0.memSize, arr_mem0.capacity, top_k}; hbUCPSysMem arr_mem1; hbUCPMallocCached(arr_mem1, capacity * sizeof(hbVPKeyPoint), 0); hbVPArray arr1; hbVPArray arr1{arr_mem1.phyAddr, arr_mem1.virAddr, arr_mem1.memSize, capacity, top_k}; std::vector<hbUCPSysMem> frame_mem0(numLayers); std::vector<hbVPImage> frame0(numLayers); std::vector<hbUCPSysMem> frame_mem1(numLayers); std::vector<hbVPImage> frame1(numLayers); for (int32_t i = 0; i < numLayers; i++) { if (i != 0) { src_width = (src_width + 1) / 2; src_height = (src_height + 1) / 2; } hbUCPMallocCached(&frame_mem0[i], src_width * src_height, 0); frame0[i] = {HB_VP_IMAGE_FORMAT_Y, HB_VP_IMAGE_TYPE_U8C1, src_width, src_height, src_width, frame_mem0[i].virAddr, frame_mem0[i].phyAddr, nullptr, 0, 0}; hbUCPMallocCached(&frame_mem1[i], src_width * src_height, 0); frame0[i] = {HB_VP_IMAGE_FORMAT_Y, HB_VP_IMAGE_TYPE_U8C1, src_width, src_height, src_width, frame_mem1[i].virAddr, frame_mem1[i].phyAddr, nullptr, 0, 0}; } hbUCPSysMem status_mem; bUCPMalloc(&status_mem, capacity, 0) hbVPArray status{status_mem.phyAddr, status_mem.virAddr, status_mem.memSize, capacity, top_k}; hbUCPSysMem conf_mem; hbUCPMalloc(&conf_mem, capacity * sizeof(float), 0); hbVPArray conf{conf_mem.phyAddr, conf_mem.virAddr, conf_mem.memSize, capacity, top_k} // init array, allocate memory for key points array hbUCPSysMem prev_pts_mem; hbUCPMalloc(&curr_pts_mem, prev_arr.memSize, 0); hbVPArray prev_pts_arr{curr_pts_mem.phyAddr, curr_pts_mem.virAddr, curr_pts_mem.memSize, top_k}; hbVPArray status{}; hbUCPMalloc(&conf_mem_, prev_arr_.size * sizeof(float), 0); hbVPArray conf{conf_mem_.phyAddr, conf_mem_.virAdd, conf_mem_.memSize, prev_arr_.size}; // optical flow parameter hbVPLKOFParam lkof_param; HB_VP_INITIALIZE_OPTICAL_FLOW_PARAM(&lkof_param); lkof_param.confEnable = true; // init task handle and schedule param hbUCPTaskHandle_t task_handle{nullptr}; hbUCPSchedParam sched_param; sched_param.backend = HB_UCP_DSP_CORE_0; sched_param.priority = 0; for (int32_t i = 0; i < frame_num; i++) { std::vector<hbVPImage> &prev_frame = i % 2 == 0 ? frame0 : frame1; std::vector<hbVPImage> &curr_frame = i % 2 == 0 ? frame1 : frame0; std::vector<hbUCPSysMem> &curr_mem = i % 2 == 0 ? frame_mem1 : frame_mem0; hbVPArray &prev_points = i % 2 == 0 ? arr0 : arr1; hbVPArray &curr_points = i % 2 == 0 ? arr1 : arr0; // create task hbVPOpticalFlowPyrLK(&task_handle, &curr_points, &status, &conf, &prev_points, curr_frame.data(), prev_frame.data(), &lkof_param); // submit task hbUCPSubmitTask(task_handle, &sched_param); // wait for task done hbUCPWaitTaskDone(task_handle, 0); // release task handle hbUCPReleaseTask(task_handle); } // release memory hbUCPFree(&arr_mem0); hbUCPFree(&arr_mem1); hbUCPFree(&status_mem); hbUCPFree(&conf_mem); for (int32_t i = 0; i < numLayers; i++) { hbUCPFree(&frame_mem0[i]); hbUCPFree(&frame_mem1[i]); }