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) 坐标位置的亮度差。

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

    借助该假设,像素点 (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_t num_layers{5};
uint32_t capacity{5000U};
int32_t src_width = 1280;
int32_t src_height = 720;

// Alloc memory used to hold key points, include previous points, current points
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{arr_mem1.phyAddr,
               arr_mem1.virAddr,
               arr_mem1.memSize,
               capacity,
               top_k};
hbUCPSysMem status_mem;
bUCPMalloc(&status_mem, capacity, 0)
hbVPArray status{status_mem.phyAddr,
                 status_mem.virAddr,
                 status_mem.memSize,
                 capacity,
                 top_k};

// Alloc memory used to hold gaussian pyramid images, include previous frame and current frame
std::vector<hbUCPSysMem> prev_frame_mem(num_layers);
std::vector<hbVPImage> prev_frame(num_layers);
std::vector<hbUCPSysMem> cur_frame_mem(num_layers);
std::vector<hbVPImage> cur_frame(num_layers);
for (int32_t i = 0; i < num_layers; i++) {
  if (i != 0) {
    src_width = (src_width + 1) / 2;
    src_height = (src_height + 1) / 2;
  }
  hbUCPMallocCached(&prev_frame_mem[i], src_width * src_height, 0);
  prev_frame[i] = {HB_VP_IMAGE_FORMAT_Y,
                   HB_VP_IMAGE_TYPE_U8C1,
                   src_width,
                   src_height,
                   src_width,
                   prev_frame_mem[i].virAddr,
                   prev_frame_mem[i].phyAddr,
                   nullptr, 
                   0,
                   0};

  hbUCPMallocCached(&cur_frame_mem[i], src_width * src_height, 0);
  cur_frame[i] = {HB_VP_IMAGE_FORMAT_Y,
                  HB_VP_IMAGE_TYPE_U8C1,
                  src_width,
                  src_height,
                  src_width,
                  cur_frame_mem[i].virAddr,
                  cur_frame_mem[i].phyAddr,
                  nullptr, 
                  0,
                  0};
}

// optical flow parameter
hbVPLKOFParam lkof_param;
HB_VP_INITIALIZE_OPTICAL_FLOW_PARAM(&lkof_param);

// 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;

// i frame process
{
  // two blocks of memory read and write alternately
  std::vector<hbVPImage> &prev_frame = i % 2 == 0 ? prev_frame : cur_frame;
  std::vector<hbVPImage> &curr_frame = i % 2 == 0 ? cur_frame : prev_frame;
  std::vector<hbUCPSysMem> &curr_mem = i % 2 == 0 ? cur_frame_mem : prev_frame_mem;

  hbVPArray &prev_points = i % 2 == 0 ? arr0 : arr1;
  hbVPArray &curr_points = i % 2 == 0 ? arr1 : arr0;

    // create task
  hbVPOpticalFlowPyrLK(&task_handle, &curr_points, &status, nullptr, &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);
for (int32_t i = 0; i < num_layers; i++) {
  hbUCPFree(&prev_frame_mem[i]);
  hbUCPFree(&cur_frame_mem[i]);
}