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 image | Parameter | Output 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:
- 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) pixel position at time t is located at (x+u,y+v) position at time t+Δt, based on the assumption of brightness invariance we have.
I(x,y,t)=I(x+u,y+v,t+Δ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)+Ix′u+Iy′v+It′Δt
It can be obtained from the above two equations:
Ix′u+Iy′v+It′Δt=0
The collation can be expressed as:
[Ix′,Iy′][uv]=−ΔIt
Among them, Ix′ and Iy′ are the partial derivatives of the image luminance in the x direction and the y direction at the (x,y) pixel point, respectively, the gradient of the image in the $x$ and $y$ directions.
- 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) have the following equation.
[Ix1′,Iy1′Ix2′,Iy2′][uv]=[−ΔIt1−ΔIt2]
The above equation, which is of the form Ax=b, leads to a least squares solution for the optical flow vector:
x=(ATA)-1ATb
One of the requirements is that ATA 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]);
}