OpticalFlowPyrLK

The OpticalFlowPyrLK operator is used to calculate the changes of specific pixel points in consecutive multi-frame images, which is commonly used in scenarios such as target feature point tracking and improving BOX stability.

Operator Effect

Input continuous frame imageParameterOutput Image
image-image

Principle

The sparse optical flow algorithm, known as the Lucas-Kanade algorithm, is a motion estimation method commonly used in computer vision to estimate the direction and speed of motion of pixels in an image sequence. The Lucas-Kanade algorithm is based on two assumptions:

  1. Brightness constancy assumption: the brightness of pixels of the same object remains constant over a short period of time.

Assuming that the object located at (x,y)(x,y) pixel position at time tt is located at (x+u,y+v)(x+u,y+v) position at time t+Δtt+Δt, based on the assumption of brightness invariance we have.

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

A first-order Taylor expansion of the right-hand side of the equation yields:

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

It can be obtained from the above two equations:

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

The collation can be expressed as:

[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}

Among them, IxI'_x and IyI'_y are the partial derivatives of the image luminance in the xx direction and the yy direction at the (x,y)(x,y) pixel point, respectively, the gradient of the image in the $x$ and $y$ directions.

  1. Neighborhood optical flow similarity assumes that the direction and size of pixel movement in a small image region is essentially the same.

With the help of this assumption, all pixels in the field of pixel point (x,y)(x,y) have the following equation.

[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}

The above equation, which is of the form Ax=bAx=b, leads to a least squares solution for the optical flow vector:

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

One of the requirements is that ATAA^TA is invertible, and in order to fulfill this requirement, the Lucas-Kanade method selects corner points as feature points. In addition to being based on the luminance invariance assumption and the neighborhood optical flow similarity assumption, the Lucas-Kanade algorithm solves for the case of large image offsets with the help of image pyramids, where large offsets become small offsets on high-level low-resolution images, thus solving for the optical flow.

Therefore, the OpticalFlowPyrLK operator requires the pyramid layers of the previous and previous frames, and the feature points of the previous frame as input, where the feature points are usually chosen to be corner points. Successive frame calculations amplify the error, when it is also necessary to introduce a priori feature points of the current frame, which are used to correct the error.

API Interface

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

For detailed interface information, please refer to hbVPOpticalFlowPyrLK.

Usage

// 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]); }