VPI - 视觉编程接口

3.2 版本

稠密光流

概述

稠密光流算法估计前后帧之间每个 4x4 像素块中的运动矢量。它的用途包括运动检测和物体跟踪。

下面的输出表示 HSV 颜色空间中的每个矢量,其中色调与运动方向相关,值与速度成正比。

输入输出运动矢量

实现

该算法分析两张图像(前一帧和当前帧)的内容,并将运动估计写入输出图像。

如下所示,该算法将输入图像分割成 4x4 像素块。然后,对于每个块,它估计从前一帧到当前帧的内容平移,并将估计值作为运动矢量写入输出图像中的对应像素。

稠密光流估计

2D 运动矢量表示为 X,Y 坐标对,每个坐标采用 S10.5 有符号定点格式,如下所示

S10.5 有符号定点格式

S10.5 格式和浮点格式之间的转换如下所示

\begin{align*} S_{10.5} &= \lfloor F \times 32 \rfloor \\ F &= \lfloor S_{10.5} / 32 \rfloor \end{align*}

C API 函数

有关实现该算法的限制、约束和后端的列表,请查阅以下函数的参考文档

函数描述
vpiCreateOpticalFlowDense vpiSubmitOpticalFlowDense 创建负载。
vpiSubmitOpticalFlowDense 在两帧上运行稠密光流,输出运动矢量。
vpiOpticalFlowDenseSetSGMParams 设置半全局匹配参数,供给定负载的稠密光流操作使用。
vpiOpticalFlowDenseGetSGMParams 检索在稠密光流负载中设置的半全局匹配参数。

用法

语言
  1. 导入 VPI 模块
    import vpi
  2. 获取第一帧。
    prevImage = inVideo.read()[1]
  3. 获取下一帧。
    while inVideo.read(curImage)[0]
  4. 使用 OFA 后端执行算法,将前一帧和当前帧传递给它。
    with vpi.Backend.OFA
    motion = vpi.optflow_dense(prevImage, curImage)
  5. 通过将当前帧分配给前一帧,为下一次迭代做准备。
    prevImage = curImage
  1. 初始化阶段
    1. 包含定义所需函数和类型的头文件
      声明实现稠密光流的函数。
    2. 创建将在其中提交算法以供执行的流
      VPIStream stream;
      vpiStreamCreate(0, &stream);
      struct VPIStreamImpl * VPIStream
      流的句柄。
      定义: Types.h:250
      VPIStatus vpiStreamCreate(uint64_t flags, VPIStream *stream)
      创建流实例。
    3. 定义具有块线性内存布局的运动矢量图像。运动矢量采用 [x, y] 形式,表示估计的平移,两个坐标均采用 S10.5 格式。输出维度计算时考虑到一个 4x4 输入像素块对应一个输出矢量
      VPIImage mvImage;
      int32_t mvWidth = (width + 3) / 4;
      int32_t mvHeight = (height + 3) / 4;
      vpiImageCreate(mvWidth, mvHeight, VPI_IMAGE_FORMAT_2S16_BL, 0, &mvImage);
      #define VPI_IMAGE_FORMAT_2S16_BL
      单平面,带有两个交错的块线性 16 位有符号整数通道。
      struct VPIImageImpl * VPIImage
      图像的句柄。
      定义: Types.h:256
      VPIStatus vpiImageCreate(int32_t width, int32_t height, VPIImageFormat fmt, uint64_t flags, VPIImage *img)
      使用指定的标志创建空的图像实例。
    4. 创建负载以包含临时缓冲区。负载配置为由 OFA 后端处理
      VPIPayload optflow;
      int32_t gridSize = 4;
      VPI_CHECK_STATUS(
      vpiCreateOpticalFlowDense(VPI_BACKEND_OFA, width, height, imgFmtBL, &gridSize, 1, quality, &optflow));
      VPIStatus vpiCreateOpticalFlowDense(uint64_t backends, int32_t width, int32_t height, VPIImageFormat inputFmt, const int32_t *gridSize, int32_t numLevels, VPIOpticalFlowQuality quality, VPIPayload *payload)
      为 vpiSubmitOpticalFlowDense 创建负载。
      struct VPIPayloadImpl * VPIPayload
      算法负载的句柄。
      定义: Types.h:268
      @ VPI_BACKEND_OFA
      OFA 后端。
      定义: Types.h:97
    5. 获取第一帧
      VPIImage prevImage = /* 前一帧 */;
  2. 处理阶段
    1. 从第二帧开始处理循环
      for (int idframe = 1; idframe < frame_count; ++idframe)
      {
    2. 获取当前帧
      VPIImage curImage = /* 当前帧 */;
    3. 提交算法。该算法必须将前一帧和当前帧都馈送到 NVIDIA 编码器引擎,并为每个 4x4 像素块生成运动矢量
      vpiSubmitOpticalFlowDense(stream, VPI_BACKEND_OFA, optflow, prevImage, curImage, mvImage);
      VPIStatus vpiSubmitOpticalFlowDense(VPIStream stream, uint64_t backend, VPIPayload payload, VPIImage prevImg, VPIImage curImg, VPIImage mvImg)
      在两帧上运行稠密光流,输出运动矢量。
    4. (可选)如果不再有任务要提交到流,请等待直到流完成处理。同步完成后,您可以使用在此迭代中计算的输出运动矢量
      vpiStreamSync(stream);
      VPIStatus vpiStreamSync(VPIStream stream)
      阻塞调用线程,直到此流队列中所有提交的命令都完成(队列为空)。。。
    5. 交换前一帧和当前帧,以便当前帧成为下一次迭代的前一帧
      VPIImage tmpImg = prevImage;
      prevImage = curImage;
      curImage = tmpImg;
      }
  3. 清理阶段
    1. 释放流、负载以及输入和输出数组所持有的资源
      vpiImageDestroy(prevImage);
      vpiImageDestroy(curImage);
      vpiImageDestroy(mvImage);
      void vpiImageDestroy(VPIImage img)
      销毁图像实例。
      void vpiPayloadDestroy(VPIPayload payload)
      释放负载对象和所有关联的资源。
      void vpiStreamDestroy(VPIStream stream)
      销毁流实例并释放所有硬件资源。

请查阅 稠密光流 示例以获取完整示例。

有关更多信息,请参阅 VPI - 视觉编程接口 的 “C API 参考” 部分中的 稠密光流

性能

有关如何使用下表中的性能信息,请参阅 算法性能表
在比较测量结果之前,请查阅 比较算法运行时间
有关性能基准测试方式的更多信息,请参阅 性能基准

 -