OpticalFlowPyrLK算子用于计算连续多帧图像特定像素点的变化,常用于目标特征点跟踪、提升BOX稳定性等场景。
| 输入连续帧图像 | 参数 | 输出图像 |
|---|---|---|
![]() | - | ![]() |
稀疏光流算法即Lucas-Kanade算法,是一种计算机视觉中常用的运动估计方法,用于估计图像序列中像素的运动方向和速度。Lucas-Kanade算法基于两个假设:
假设 t 时刻,位于 (x,y) 像素位置的物体,在 t+Δt 时刻位于 (x+u,y+v) 位置,基于亮度不变假设有:
I(x,y,t)=I(x+u,y+v,t+Δt)
将等式右边进行一阶泰勒展开得:
I(x+u,y+v,t+Δt)=I(x,y,t)+Ix′u+Iy′v+It′Δt
由上面两个公式可以得到:
Ix′u+Iy′v+It′Δt=0
整理后可表示成:
[Ix′,Iy′][uv]=−ΔIt
其中,Ix′ , Iy′ 分别为 (x,y) 像素点处图像亮度在 x 方向和 y 方向的偏导数,即图像x和y方向的梯度。ΔIt 即为两图之间的 (x,y) 坐标位置的亮度差。
借助该假设,像素点 (x,y) 领域内的所有像素都有下面的公式:
[Ix1′,Iy1′Ix2′,Iy2′][uv]=[−ΔIt1−ΔIt2]
上式即为 Ax=b 的形式,可求得光流向量的最小二乘解:
x=(ATA)-1ATb
其中要求 ATA 可逆,为了满足这个要求,Lucas-Kanade方法选取角点作为特征点。除了基于亮度不变假设和邻域光流相似假设,Lucas-Kanade算法还借助了图像金字塔的方式解决图像偏移较大的情况,在高层低分辨率图像上,大的偏移将变为小的偏移,从而求解出光流。
因此,OpticalFlowPyrLK算子需要前后两帧的金字塔图层,和前一帧的特征点作为输入,其中特征点通常选用角点。 连续帧计算会放大误差,此时还需要引入当前帧的先验特征点,用来修正误差。
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]);
}

