VPI - Vision Programming Interface

3.2 版本

稠密光流

概述

此应用程序从输入视频源获取帧,在前一帧和当前帧上运行算法,然后计算每个 4x4 像素块的运动矢量。输出运动矢量将映射到 HSV 颜色空间,其中色调与运动角度相关,值与运动速度相关,结果将保存到视频文件。

指令

命令行参数为

<后端> <输入视频> <质量> <网格大小> <金字塔层数>

其中

  • backend: 定义将执行处理的后端。仅支持 OFA 后端。ofa 仅在 Jetson AGX Orin 上受支持。
  • input video: 输入视频文件名,它接受 .mp4、.avi 以及可能取决于 OpenCV 支持的其他格式。
  • quality: 指定算法将使用的质量。可用选项有:low(最快)、medium(性能和质量平衡)和 high(最慢)。
  • gridsize: 图像上规则网格的大小,每个单元格将产生一个运动矢量。使用 1 表示稠密网格。
  • numlevels: 使用的金字塔层数。

以下是 Jetson AGX Orin 的一个示例。

  • C++
    ./vpi_sample_13_optflow_dense ofa ../assets/pedestrians.mp4 high 1 5
  • Python
    python3 main.py ofa ../assets/pedestrians.mp4 high 2

该应用程序将处理 pedestrians.mp4 并创建 denseoptflow_mv_ofa.mp4

结果

输入视频运动矢量视频

源代码

为了方便起见,以下代码也安装在 samples 目录中。

语言
27 import sys
28 import vpi
29 import numpy as np
30 from os import path
31 from argparse import ArgumentParser
32 from contextlib import contextmanager
33 import cv2
34 
35 
36 # ----------------------------
37 # Some utility functions
38 
39 def process_motion_vectors(mv)
40  with mv.rlock_cpu() as data
41  # convert S10.5 format to float
42  flow = np.float32(data)/(1<<5)
43 
44  # Create an image where the motion vector angle is
45  # mapped to a color hue, and intensity is proportional
46  # to vector's magnitude
47  magnitude, angle = cv2.cartToPolar(flow[:,:,0], flow[:,:,1], angleInDegrees=True)
48 
49  clip = 5.0
50  cv2.threshold(magnitude, clip, clip, cv2.THRESH_TRUNC, magnitude)
51 
52  # build the hsv image
53  hsv = np.ndarray([flow.shape[0], flow.shape[1], 3], np.float32)
54  hsv[:,:,0] = angle
55  hsv[:,:,1] = np.ones((angle.shape[0], angle.shape[1]), np.float32)
56  hsv[:,:,2] = magnitude / clip
57 
58  # Convert HSV to BGR8
59  bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
60  return np.uint8(bgr*255)
61 
62 # ----------------------------
63 # Parse command line arguments
64 
65 parser = ArgumentParser()
66 parser.add_argument('backend', choices=['ofa'],
67  help='Backend to be used for processing')
68 
69 parser.add_argument('input',
70  help='Input video to be processed')
71 
72 parser.add_argument('quality', choices=['low', 'medium', 'high'],
73  help='Quality setting')
74 
75 parser.add_argument('gridSize', type=int, choices=[1,2,4,8],
76  help='Grid size')
77 
78 parser.add_argument('numLevels', type=int, choices=[1,2,3,4,5],
79  help='Number of pyramid levels')
80 
81 args = parser.parse_args();
82 
83 assert args.backend == 'ofa'
84 if args.backend == 'ofa'
85  backend = vpi.Backend.OFA
86 
87 if args.quality == "low"
88  quality = vpi.OptFlowQuality.LOW
89 elif args.quality == "medium"
90  quality = vpi.OptFlowQuality.MEDIUM
91 else
92  assert args.quality == "high"
93  quality = vpi.OptFlowQuality.HIGH
94 
95 # -----------------------------
96 # Open input and output videos
97 
98 inVideo = cv2.VideoCapture(args.input)
99 
100 fourcc = cv2.VideoWriter_fourcc(*'MPEG')
101 inSize = (int(inVideo.get(cv2.CAP_PROP_FRAME_WIDTH)), int(inVideo.get(cv2.CAP_PROP_FRAME_HEIGHT)))
102 fps = inVideo.get(cv2.CAP_PROP_FPS)
103 
104 # Calculate the output dimensions based on the input's and the chosen grid size
105 outSize = ((inSize[0] + args.gridSize-1)//args.gridSize, (inSize[1]+args.gridSize-1)//args.gridSize)
106 
107 outVideo = cv2.VideoWriter('denseoptflow_mv_python'+str(sys.version_info[0])+'_'+args.backend+'.mp4',
108  fourcc, fps, outSize)
109 
110 #---------------------------------
111 # Main processing loop
112 
113 prevFrame = None
114 
115 idFrame = 0
116 while True
117  # Read one input frame
118  ret, cvFrame = inVideo.read()
119  if not ret
120  break
121 
122  # Convert it to Y8_ER_BL pyramid format to be used by VPI
123  # No single backend can convert from OpenCV's BGR8 to Y8_ER_BL
124  # required by the algorithm. We must do in two steps using CUDA and VIC.
125  curFrame = vpi.asimage(cvFrame, vpi.Format.BGR8) \
126  .convert(vpi.Format.Y8_ER, backend=vpi.Backend.CUDA) \
127  .gaussian_pyramid(args.numLevels, backend=vpi.Backend.CUDA) \
128  .convert(vpi.Format.Y8_ER_BL, backend=vpi.Backend.VIC)
129 
130  # Need at least 2 frames to start processing
131  if prevFrame is not None
132  print("Processing frame {}".format(idFrame))
133 
134  # Calculate the motion vectors from previous to current frame
135  with backend
136  motion_vectors = vpi.optflow_dense(prevFrame, curFrame, quality = quality, gridsize = args.gridSize)
137 
138  # Turn motion vectors into an image
139  motion_image = process_motion_vectors(motion_vectors)
140 
141  # Save it to output video
142  outVideo.write(motion_image)
143 
144  # Prepare next iteration
145  prevFrame = curFrame
146  idFrame += 1
29 #include <opencv2/core/version.hpp>
30 #include <opencv2/imgcodecs.hpp>
31 #include <opencv2/imgproc/imgproc.hpp>
32 #include <opencv2/videoio.hpp>
33 #include <vpi/OpenCVInterop.hpp>
34 
35 #include <vpi/Array.h>
36 #include <vpi/Image.h>
37 #include <vpi/ImageFormat.h>
38 #include <vpi/Pyramid.h>
39 #include <vpi/Status.h>
40 #include <vpi/Stream.h>
44 
45 #include <iostream>
46 #include <sstream>
47 
48 #define CHECK_STATUS(STMT) \
49  do \
50  { \
51  VPIStatus status = (STMT); \
52  if (status != VPI_SUCCESS) \
53  { \
54  char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \
55  vpiGetLastStatusMessage(buffer, sizeof(buffer)); \
56  std::ostringstream ss; \
57  ss << "line " << __LINE__ << ": "; \
58  ss << vpiStatusGetName(status) << ": " << buffer; \
59  throw std::runtime_error(ss.str()); \
60  } \
61  } while (0);
62 
63 static void ProcessMotionVector(VPIImage mvImg, cv::Mat &outputImage)
64 {
65  // Lock the input image to access it from CPU
66  VPIImageData mvData;
68 
69  // Create a cv::Mat that points to the input image data
70  cv::Mat mvImage;
71  CHECK_STATUS(vpiImageDataExportOpenCVMat(mvData, &mvImage));
72 
73  // Convert S10.5 format to float
74  cv::Mat flow(mvImage.size(), CV_32FC2);
75  mvImage.convertTo(flow, CV_32F, 1.0f / (1 << 5));
76 
77  // Image not needed anymore, we can unlock it.
78  CHECK_STATUS(vpiImageUnlock(mvImg));
79 
80  // Create an image where the motion vector angle is
81  # mapped to a color hue, and intensity is proportional
82  # to vector's magnitude.
83  cv::Mat magnitude, angle;
84  {
85  cv::Mat flowChannels[2];
86  split(flow, flowChannels);
87  cv::cartToPolar(flowChannels[0], flowChannels[1], magnitude, angle, true);
88  }
89 
90  float clip = 5;
91  cv::threshold(magnitude, magnitude, clip, clip, cv::THRESH_TRUNC);
92 
93  // build hsv image
94  cv::Mat _hsv[3], hsv, bgr;
95  _hsv[0] = angle;
96  _hsv[1] = cv::Mat::ones(angle.size(), CV_32F);
97  _hsv[2] = magnitude / clip; // intensity must vary from 0 to 1
98  merge(_hsv, 3, hsv);
99 
100  cv::cvtColor(hsv, bgr, cv::COLOR_HSV2BGR);
101  bgr.convertTo(outputImage, CV_8U, 255.0);
102 }
103 
104 int main(int argc, char *argv[])
105 {
106  // OpenCV image that will be wrapped by a VPIImage.
107  // Define it here so that it's destroyed *after* wrapper is destroyed
108  cv::Mat cvPrevFrame, cvCurFrame;
109 
110  // VPI objects that will be used
111  VPIStream stream = NULL;
112  VPIImage imgPrevFramePL = NULL;
113  VPIImage imgPrevFrameTmp = NULL;
114  VPIImage imgCurFramePL = NULL;
115  VPIImage imgCurFrameTmp = NULL;
116  VPIImage imgMotionVecBL = NULL;
117  VPIImage imgMotionVecPL = NULL;
118 
119  VPIPyramid prevPyrTmp = NULL;
120  VPIPyramid prevPyrBL = NULL;
121  VPIPyramid curPyrTmp = NULL;
122  VPIPyramid curPyrBL = NULL;
123 
124  VPIPayload payload = NULL;
125 
126  int retval = 0;
127 
128  try
129  {
130  if (argc != 6)
131  {
132  throw std::runtime_error(std::string("Usage: ") + argv[0] +
133  " <ofa> <input_video> <low|medium|high> <gridsize> <numlevels>");
134  }
135 
136  // Parse input parameters
137  std::string strBackend = argv[1];
138  std::string strInputVideo = argv[2];
139  std::string strQuality = argv[3];
140  std::string strGridSize = argv[4];
141  std::string strNumLevels = argv[5];
142 
143  VPIOpticalFlowQuality quality;
144  if (strQuality == "low")
145  {
147  }
148  else if (strQuality == "medium")
149  {
151  }
152  else if (strQuality == "high")
153  {
155  }
156  else
157  {
158  throw std::runtime_error("Unknown quality provided");
159  }
160 
161  VPIBackend backend;
162  if (strBackend == "ofa")
163  {
164  backend = VPI_BACKEND_OFA;
165  }
166  else
167  {
168  throw std::runtime_error("Backend '" + strBackend + "' not recognized, it must be ofa.");
169  }
170 
171  char *endptr;
172  int gridSize = strtol(strGridSize.c_str(), &endptr, 10);
173  if (*endptr != '\0')
174  {
175  throw std::runtime_error("Syntax error parsing gridsize " + strGridSize);
176  }
177 
178  int numLevels = strtol(strNumLevels.c_str(), &endptr, 10);
179  if (*endptr != '\0')
180  {
181  throw std::runtime_error("Syntax error parsing numlevels " + strNumLevels);
182  }
183 
184  // Load the input video
185  cv::VideoCapture invid;
186  if (!invid.open(strInputVideo))
187  {
188  throw std::runtime_error("Can't open '" + strInputVideo + "'");
189  }
190 
191  // Create the stream where processing will happen. We'll use user-provided backend
192  // for Optical Flow, and CUDA/VIC for image format conversions.
193  CHECK_STATUS(vpiStreamCreate(backend | VPI_BACKEND_CUDA | VPI_BACKEND_VIC, &stream));
194 
195  // Fetch the first frame
196  if (!invid.read(cvPrevFrame))
197  {
198  throw std::runtime_error("Cannot read frame from input video");
199  }
200 
201  // Create the previous and current frame wrapper using the first frame. This wrapper will
202  // be set to point to every new frame in the main loop.
203  CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvPrevFrame, 0, &imgPrevFramePL));
204  CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvPrevFrame, 0, &imgCurFramePL));
205 
206  // Define the image formats we'll use throughout this sample.
209 
210  int32_t width = cvPrevFrame.cols;
211  int32_t height = cvPrevFrame.rows;
212 
213  // Create Dense Optical Flow payload to be executed on the given backend
214  std::vector<int32_t> pyrGridSize(numLevels, gridSize); // all levels will have the same grid size
215  CHECK_STATUS(vpiCreateOpticalFlowDense(backend, width, height, imgFmtBL, &pyrGridSize[0], pyrGridSize.size(),
216  quality, &payload));
217 
218  // The Dense Optical Flow on NVENC or OFA backends expects input to be in block-linear format.
219  // Since Convert Image Format algorithm doesn't currently support direct BGR
220  // pitch-linear (from OpenCV) to Y8 block-linear conversion, it must be done in two
221  // passes, first from BGR/PL to Y8/PL using CUDA, then from Y8/PL to Y8/BL using VIC.
222  // The temporary image buffer below will store the intermediate Y8/PL representation.
223  CHECK_STATUS(vpiImageCreate(width, height, imgFmt, 0, &imgPrevFrameTmp));
224  CHECK_STATUS(vpiImageCreate(width, height, imgFmt, 0, &imgCurFrameTmp));
225 
226  // Now create the final block-linear buffer that'll be used as input to the
227  // algorithm.
228 
229  CHECK_STATUS(vpiPyramidCreate(width, height, imgFmt, pyrGridSize.size(), 0.5, 0, &prevPyrTmp));
230  CHECK_STATUS(vpiPyramidCreate(width, height, imgFmt, pyrGridSize.size(), 0.5, 0, &curPyrTmp));
231 
232  CHECK_STATUS(vpiPyramidCreate(width, height, imgFmtBL, pyrGridSize.size(), 0.5, 0, &prevPyrBL));
233  CHECK_STATUS(vpiPyramidCreate(width, height, imgFmtBL, pyrGridSize.size(), 0.5, 0, &curPyrBL));
234 
235  // Motion vector image width and height, align to be multiple of gridSize
236  int32_t mvWidth = (width + gridSize - 1) / gridSize;
237  int32_t mvHeight = (height + gridSize - 1) / gridSize;
238 
239  // 输出视频将是运动向量图像的热图
240  int fourcc = cv::VideoWriter::fourcc('M', 'P', 'E', 'G');
241  double fps = invid.get(cv::CAP_PROP_FPS);
242 
243  cv::VideoWriter outVideo("denseoptflow_mv_" + strBackend + ".mp4", fourcc, fps, cv::Size(mvWidth, mvHeight));
244  if (!outVideo.isOpened())
245  {
246  throw std::runtime_error("无法创建输出视频");
247  }
248 
249  // 创建输出运动向量缓冲区
250  CHECK_STATUS(vpiImageCreate(mvWidth, mvHeight, VPI_IMAGE_FORMAT_2S16_BL, 0, &imgMotionVecBL));
251  CHECK_STATUS(vpiImageCreate(mvWidth, mvHeight, VPI_IMAGE_FORMAT_2S16, 0, &imgMotionVecPL));
252 
253  // 首先将第一帧转换为 Y8_BL 金字塔格式。当算法被调用时,它将被用作前一帧。
254  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, imgPrevFramePL, imgPrevFrameTmp, nullptr));
255  CHECK_STATUS(
256  vpiSubmitGaussianPyramidGenerator(stream, VPI_BACKEND_CUDA, imgPrevFrameTmp, prevPyrTmp, VPI_BORDER_CLAMP));
257  CHECK_STATUS(vpiSubmitConvertImageFormatPyramid(stream, VPI_BACKEND_VIC, prevPyrTmp, prevPyrBL, NULL));
258 
259  // 创建一个输出图像,用于保存渲染后的运动向量图像。
260  cv::Mat mvOutputImage;
261 
262  // 获取新帧直到视频结束
263  int idxFrame = 1;
264  while (invid.read(cvCurFrame))
265  {
266  printf("正在处理帧 %d\n", idxFrame++);
267  // 将帧封装到 VPIImage 中,重用现有的 imgCurFramePL。
268  CHECK_STATUS(vpiImageSetWrappedOpenCVMat(imgCurFramePL, cvCurFrame));
269 
270  // 将当前帧转换为 Y8_BL 金字塔格式
271  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, imgCurFramePL, imgCurFrameTmp, nullptr));
272  CHECK_STATUS(vpiSubmitGaussianPyramidGenerator(stream, VPI_BACKEND_CUDA, imgCurFrameTmp, curPyrTmp,
274  CHECK_STATUS(vpiSubmitConvertImageFormatPyramid(stream, VPI_BACKEND_VIC, curPyrTmp, curPyrBL, NULL));
275 
276  CHECK_STATUS(
277  vpiSubmitOpticalFlowDensePyramid(stream, backend, payload, prevPyrBL, curPyrBL, imgMotionVecBL));
278 
279  // 将 BL 格式的输出转换为 PL 格式。
280  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_VIC, imgMotionVecBL, imgMotionVecPL, NULL));
281 
282  // 等待处理完成。
283  CHECK_STATUS(vpiStreamSync(stream));
284 
285  // 在输出图像中渲染生成的运动向量
286  ProcessMotionVector(imgMotionVecPL, mvOutputImage);
287 
288  // 保存到输出视频
289  outVideo << mvOutputImage;
290 
291  // 交换前一帧和下一帧
292  std::swap(cvPrevFrame, cvCurFrame);
293  std::swap(imgPrevFramePL, imgCurFramePL);
294  std::swap(prevPyrBL, curPyrBL);
295  }
296  }
297  catch (std::exception &e)
298  {
299  std::cerr << e.what() << std::endl;
300  retval = 1;
301  }
302 
303  // 销毁所有已使用的资源
304  vpiStreamDestroy(stream);
305  vpiPayloadDestroy(payload);
306 
307  vpiImageDestroy(imgPrevFramePL);
308  vpiImageDestroy(imgPrevFrameTmp);
309  vpiImageDestroy(imgCurFramePL);
310  vpiImageDestroy(imgCurFrameTmp);
311  vpiImageDestroy(imgMotionVecBL);
312  vpiImageDestroy(imgMotionVecPL);
313 
314  vpiPyramidDestroy(prevPyrTmp);
315  vpiPyramidDestroy(prevPyrBL);
316  vpiPyramidDestroy(curPyrTmp);
317  vpiPyramidDestroy(curPyrBL);
318 
319  return retval;
320 }
用于处理 VPI 数组的函数和结构。
声明处理图像格式转换的函数。
声明处理高斯金字塔的函数。
定义用于处理图像格式的类型和函数。
#define VPI_IMAGE_FORMAT_Y8_ER_BL
单平面,带有一个块线性 8 位无符号整数通道,具有全范围亮度(灰度)...
Definition: ImageFormat.h:164
#define VPI_IMAGE_FORMAT_2S16_BL
单平面,带有两个交错的块线性 16 位有符号整数通道。
Definition: ImageFormat.h:131
#define VPI_IMAGE_FORMAT_Y8_ER
单平面,带有一个倾斜线性 8 位无符号整数通道,具有全范围亮度(灰度)...
Definition: ImageFormat.h:159
#define VPI_IMAGE_FORMAT_2S16
单平面,带有两个交错的 16 位有符号整数通道。
Definition: ImageFormat.h:128
用于处理 VPI 图像的函数和结构。
用于处理 OpenCV 与 VPI 互操作性的函数。
声明实现密集光流的函数。
用于处理 VPI 金字塔的函数和结构。
VPI 状态码处理函数的声明。
声明处理 VPI 流的函数。
VPIStatus vpiSubmitConvertImageFormatPyramid(VPIStream stream, uint64_t backend, VPIPyramid input, VPIPyramid output, const VPIConvertImageFormatParams *params)
将金字塔内容转换为所需的格式,可选择缩放和偏移。
VPIStatus vpiSubmitConvertImageFormat(VPIStream stream, uint64_t backend, VPIImage input, VPIImage output, const VPIConvertImageFormatParams *params)
将图像内容转换为所需的格式,可选择缩放和偏移。
VPIStatus vpiSubmitGaussianPyramidGenerator(VPIStream stream, uint64_t backend, VPIImage input, VPIPyramid output, VPIBorderExtension border)
从输入图像计算高斯金字塔。
uint64_t VPIImageFormat
预定义的图像格式。
Definition: ImageFormat.h:94
void vpiImageDestroy(VPIImage img)
销毁图像实例。
struct VPIImageImpl * VPIImage
图像的句柄。
Definition: Types.h:256
VPIStatus vpiImageLockData(VPIImage img, VPILockMode mode, VPIImageBufferType bufType, VPIImageData *data)
获取图像对象的锁并返回图像内容。
VPIStatus vpiImageCreate(int32_t width, int32_t height, VPIImageFormat fmt, uint64_t flags, VPIImage *img)
使用指定的标志创建空的图像实例。
VPIStatus vpiImageUnlock(VPIImage img)
释放图像对象上的锁。
@ VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR
主机可访问,平面采用倾斜线性内存布局。
Definition: Image.h:172
存储有关图像特征和内容的信息。
Definition: Image.h:234
VPIStatus vpiImageCreateWrapperOpenCVMat(const cv::Mat &mat, VPIImageFormat fmt, uint64_t flags, VPIImage *img)
使用给定的图像格式将 cv::Mat 封装到 VPIImage 中。
VPIStatus vpiImageDataExportOpenCVMat(const VPIImageData &imgData, cv::Mat *mat)
使用来自锁定 VPIImage 的 VPIImageData 数据填充现有的 cv::Mat。
VPIStatus vpiImageSetWrappedOpenCVMat(VPIImage img, const cv::Mat &mat)
重新定义现有 VPIImage 包装器的封装 cv::Mat。
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 创建负载。
VPIStatus vpiSubmitOpticalFlowDensePyramid(VPIStream stream, uint64_t backend, VPIPayload payload, VPIPyramid prevPyr, VPIPyramid curPyr, VPIImage mvImg)
在两个帧上运行密集光流,输出运动向量。
struct VPIPayloadImpl * VPIPayload
算法负载的句柄。
Definition: Types.h:268
void vpiPayloadDestroy(VPIPayload payload)
释放负载对象和所有相关联的资源。
VPIStatus vpiPyramidCreate(int32_t width, int32_t height, VPIImageFormat fmt, int32_t numLevels, float scale, uint64_t flags, VPIPyramid *pyr)
使用指定的标志创建空的图像金字塔实例。
struct VPIPyramidImpl * VPIPyramid
图像金字塔的句柄。
Definition: Types.h:262
void vpiPyramidDestroy(VPIPyramid pyr)
销毁图像金字塔实例以及它拥有的所有资源。
struct VPIStreamImpl * VPIStream
流的句柄。
Definition: Types.h:250
VPIStatus vpiStreamSync(VPIStream stream)
阻塞调用线程,直到此流队列中的所有提交命令都完成(队列为空)...
VPIBackend
VPI 后端类型。
Definition: Types.h:91
void vpiStreamDestroy(VPIStream stream)
销毁流实例并释放所有硬件资源。
VPIStatus vpiStreamCreate(uint64_t flags, VPIStream *stream)
创建流实例。
@ VPI_BACKEND_CUDA
CUDA 后端。
Definition: Types.h:93
@ VPI_BACKEND_OFA
OFA 后端。
Definition: Types.h:97
@ VPI_BACKEND_VIC
VIC 后端。
Definition: Types.h:95
VPIOpticalFlowQuality
定义光流算法的质量。
Definition: Types.h:598
@ VPI_BORDER_CLAMP
边界像素无限重复。
Definition: Types.h:279
@ VPI_OPTICAL_FLOW_QUALITY_LOW
快速但低质量的光流实现。
Definition: Types.h:600
@ VPI_OPTICAL_FLOW_QUALITY_HIGH
慢速但高质量的光流实现。
Definition: Types.h:606
@ VPI_OPTICAL_FLOW_QUALITY_MEDIUM
速度和质量介于 VPI_OPTICAL_FLOW_QUALITY_LOW 和 VPI_OPTICAL_FLOW_QUALITY_HIGH 之间。
Definition: Types.h:603
@ VPI_LOCK_READ
仅为读取锁定内存。
Definition: Types.h:617