VPI - Vision Programming Interface

1.2 Release

Dense Optical Flow

Overview

This application fetches frames from input video source, runs the algorithms on the previous and current images, and then calculate the motion vectors for every 4x4 pixel block. The output motion vectors will be mapped to the HSV colorspace, where hue relates to motion angle, value relates to motion speed, and the result will be saved to a video file.

Instructions

The command line parameters are:

<backend> <input video> <quality>

where

  • backend: Defines the backend that will perform the processing. Only nvenc is currently supported.
  • input video: Input video file name, it accepts .mp4, .avi and possibly others, depending on OpenCV's support.
  • quality: Specify the quality that the algorithm will use. Available options are: low (fastest), medium (balanced perf and quality) and high (slowest).

Here's one example:

  • C++
    ./vpi_sample_13_optflow_dense nvenc ../assets/pedestrians.mp4 high
  • Python
    python main.py nvenc ../assets/pedestrians.mp4 high

The application will process pedestrians.mp4 and create denseoptflow_mv_nvenc.mp4.

Note
If using OpenCV-2.4 or older (i.e. on Ubuntu 16.04), output file is denseoptflow_mv_nvenc.avi.

Results

Input videoMotion vector video

Source Code

For convenience, here's the code that is also installed in the samples directory.

Language:
27 import cv2
28 import sys
29 import vpi
30 import numpy as np
31 from os import path
32 from argparse import ArgumentParser
33 from contextlib import contextmanager
34 
35 # ----------------------------
36 # Some utility functions
37 
38 def process_motion_vectors(mv):
39  with mv.rlock():
40  # convert S10.5 format to float
41  flow = np.float32(mv.cpu())/(1<<5)
42 
43  # Create an image where the motion vector angle is
44  # mapped to a color hue, and intensity is proportional
45  # to vector's magnitude
46  magnitude, angle = cv2.cartToPolar(flow[:,:,0], flow[:,:,1], angleInDegrees=True)
47 
48  clip = 5.0
49  cv2.threshold(magnitude, clip, clip, cv2.THRESH_TRUNC, magnitude)
50 
51  # build the hsv image
52  hsv = np.ndarray([flow.shape[0], flow.shape[1], 3], np.float32)
53  hsv[:,:,0] = angle
54  hsv[:,:,1] = np.ones((angle.shape[0], angle.shape[1]), np.float32)
55  hsv[:,:,2] = magnitude / clip
56 
57  # Convert HSV to BGR8
58  bgr = cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR)
59  return np.uint8(bgr*255)
60 
61 # ----------------------------
62 # Parse command line arguments
63 
64 parser = ArgumentParser()
65 parser.add_argument('backend', choices=['nvenc'],
66  help='Backend to be used for processing')
67 
68 parser.add_argument('input',
69  help='Input video to be processed')
70 
71 parser.add_argument('quality', choices=['low', 'medium', 'high'],
72  help='Quality setting')
73 
74 args = parser.parse_args();
75 
76 assert args.backend == 'nvenc'
77 backend = vpi.Backend.NVENC
78 
79 if args.quality == "low":
80  quality = vpi.OptFlowQuality.LOW
81 elif args.quality == "medium":
82  quality = vpi.OptFlowQuality.MEDIUM
83 else:
84  assert args.quality == "high"
85  quality = vpi.OptFlowQuality.HIGH
86 
87 # -----------------------------
88 # Open input and output videos
89 
90 inVideo = cv2.VideoCapture(args.input)
91 
92 if int(cv2.__version__.split('.')[0]) >= 3:
93  extOutputVideo = '.mp4'
94  fourcc = cv2.VideoWriter_fourcc(*'avc1')
95  inSize = (int(inVideo.get(cv2.CAP_PROP_FRAME_WIDTH)), int(inVideo.get(cv2.CAP_PROP_FRAME_HEIGHT)))
96  fps = inVideo.get(cv2.CAP_PROP_FPS)
97 else:
98  # MP4 support with OpenCV-2.4 has issues, we'll use
99  # avi/mpeg instead.
100  extOutputVideo = '.avi'
101  fourcc = cv2.cv.CV_FOURCC('M','P','E','G')
102  inSize = (int(inVideo.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH)), int(inVideo.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT)))
103  fps = inVideo.get(cv2.cv.CV_CAP_PROP_FPS)
104 
105 if backend == vpi.Backend.NVENC:
106  # NVENC always returns 1/4th resolution
107  outSize = (inSize[0]//4, inSize[1]//4)
108 else:
109  outSize = inSize
110 
111 outVideo = cv2.VideoWriter('denseoptflow_mv_python'+str(sys.version_info[0])+'_'+args.backend+extOutputVideo,
112  fourcc, fps, outSize)
113 
114 #---------------------------------
115 # Main processing loop
116 
117 prevFrame = None
118 
119 idFrame = 0
120 while True:
121  # Read one input frame
122  ret, cvFrame = inVideo.read()
123  if not ret:
124  break
125 
126  # Convert it to NV12_ER format to be used by VPI
127  # No single backend can convert from OpenCV's BGR8 to NV12_ER_BL
128  # required by the algorithm. We must do in two steps using CUDA and VIC.
129  curFrame = vpi.asimage(cvFrame, vpi.Format.BGR8) \
130  .convert(vpi.Format.NV12_ER, backend=vpi.Backend.CUDA) \
131  .convert(vpi.Format.NV12_ER_BL, backend=vpi.Backend.VIC)
132 
133  # Need at least 2 frames to start processing
134  if prevFrame is not None:
135  print("Processing frame {}".format(idFrame))
136 
137  # Calculate the motion vectors from previous to current frame
138  with backend:
139  motion_vectors = vpi.optflow_dense(prevFrame, curFrame, quality = quality)
140 
141  # Turn motion vectors into an image
142  motion_image = process_motion_vectors(motion_vectors)
143 
144  # Save it to output video
145  outVideo.write(motion_image)
146 
147  # Prepare next iteration
148  prevFrame = curFrame
149  idFrame += 1
150 
151 # vim: ts=8:sw=4:sts=4:et:ai
29 #include <opencv2/core/version.hpp>
30 #if CV_MAJOR_VERSION >= 3
31 # include <opencv2/imgcodecs.hpp>
32 # include <opencv2/videoio.hpp>
33 #else
34 # include <opencv2/highgui/highgui.hpp>
35 #endif
36 
37 #include <opencv2/imgproc/imgproc.hpp>
38 #include <vpi/OpenCVInterop.hpp>
39 
40 #include <vpi/Array.h>
41 #include <vpi/Image.h>
42 #include <vpi/ImageFormat.h>
43 #include <vpi/Pyramid.h>
44 #include <vpi/Status.h>
45 #include <vpi/Stream.h>
48 
49 #include <iostream>
50 #include <sstream>
51 
52 #define CHECK_STATUS(STMT) \
53  do \
54  { \
55  VPIStatus status = (STMT); \
56  if (status != VPI_SUCCESS) \
57  { \
58  char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \
59  vpiGetLastStatusMessage(buffer, sizeof(buffer)); \
60  std::ostringstream ss; \
61  ss << vpiStatusGetName(status) << ": " << buffer; \
62  throw std::runtime_error(ss.str()); \
63  } \
64  } while (0);
65 
66 static void ProcessMotionVector(VPIImage mvImg, cv::Mat &outputImage)
67 {
68  // Lock the input image to access it from CPU
69  VPIImageData mvData;
70  CHECK_STATUS(vpiImageLock(mvImg, VPI_LOCK_READ, &mvData));
71 
72  // Create a cv::Mat that points to the input image data
73  cv::Mat mvImage;
74  CHECK_STATUS(vpiImageDataExportOpenCVMat(mvData, &mvImage));
75 
76  // Convert S10.5 format to float
77  cv::Mat flow(mvImage.size(), CV_32FC2);
78  mvImage.convertTo(flow, CV_32F, 1.0f / (1 << 5));
79 
80  // Image not needed anymore, we can unlock it.
81  CHECK_STATUS(vpiImageUnlock(mvImg));
82 
83  // Create an image where the motion vector angle is
84  // mapped to a color hue, and intensity is proportional
85  // to vector's magnitude.
86  cv::Mat magnitude, angle;
87  {
88  cv::Mat flowChannels[2];
89  split(flow, flowChannels);
90  cv::cartToPolar(flowChannels[0], flowChannels[1], magnitude, angle, true);
91  }
92 
93  float clip = 5;
94  cv::threshold(magnitude, magnitude, clip, clip, cv::THRESH_TRUNC);
95 
96  // build hsv image
97  cv::Mat _hsv[3], hsv, bgr;
98  _hsv[0] = angle;
99  _hsv[1] = cv::Mat::ones(angle.size(), CV_32F);
100  _hsv[2] = magnitude / clip; // intensity must vary from 0 to 1
101  merge(_hsv, 3, hsv);
102 
103  cv::cvtColor(hsv, bgr, cv::COLOR_HSV2BGR);
104  bgr.convertTo(outputImage, CV_8U, 255.0);
105 }
106 
107 int main(int argc, char *argv[])
108 {
109  // OpenCV image that will be wrapped by a VPIImage.
110  // Define it here so that it's destroyed *after* wrapper is destroyed
111  cv::Mat cvPrevFrame, cvCurFrame;
112 
113  // VPI objects that will be used
114  VPIStream stream = NULL;
115  VPIImage imgPrevFramePL = NULL;
116  VPIImage imgPrevFrameTmp = NULL;
117  VPIImage imgPrevFrameBL = NULL;
118  VPIImage imgCurFramePL = NULL;
119  VPIImage imgCurFrameTmp = NULL;
120  VPIImage imgCurFrameBL = NULL;
121  VPIImage imgMotionVecBL = NULL;
122  VPIPayload payload = NULL;
123 
124  int retval = 0;
125 
126  try
127  {
128  if (argc != 4)
129  {
130  throw std::runtime_error(std::string("Usage: ") + argv[0] + " <nvenc> <input_video> <low|medium|high>");
131  }
132 
133  // Parse input parameters
134  std::string strBackend = argv[1];
135  std::string strInputVideo = argv[2];
136  std::string strQuality = argv[3];
137 
138  VPIOpticalFlowQuality quality;
139  if (strQuality == "low")
140  {
142  }
143  else if (strQuality == "medium")
144  {
146  }
147  else if (strQuality == "high")
148  {
150  }
151  else
152  {
153  throw std::runtime_error("Unknown quality provided");
154  }
155 
156  VPIBackend backend;
157  if (strBackend == "nvenc")
158  {
159  backend = VPI_BACKEND_NVENC;
160  }
161  else
162  {
163  throw std::runtime_error("Backend '" + strBackend + "' not recognized, it must be nvenc.");
164  }
165 
166  // Load the input video
167  cv::VideoCapture invid;
168  if (!invid.open(strInputVideo))
169  {
170  throw std::runtime_error("Can't open '" + strInputVideo + "'");
171  }
172 
173  // Create the stream where processing will happen. We'll use user-provided backend
174  // for Optical Flow, and CUDA/VIC for image format conversions.
175  CHECK_STATUS(vpiStreamCreate(backend | VPI_BACKEND_CUDA | VPI_BACKEND_VIC, &stream));
176 
177  // Fetch the first frame
178  if (!invid.read(cvPrevFrame))
179  {
180  throw std::runtime_error("Cannot read frame from input video");
181  }
182 
183  // Create the previous and current frame wrapper using the first frame. This wrapper will
184  // be set to point to every new frame in the main loop.
185  CHECK_STATUS(vpiImageCreateOpenCVMatWrapper(cvPrevFrame, 0, &imgPrevFramePL));
186  CHECK_STATUS(vpiImageCreateOpenCVMatWrapper(cvPrevFrame, 0, &imgCurFramePL));
187 
188  // Define the image formats we'll use throughout this sample.
191 
192  int32_t width = cvPrevFrame.cols;
193  int32_t height = cvPrevFrame.rows;
194 
195  // Create Dense Optical Flow payload to be executed on the given backend
196  CHECK_STATUS(vpiCreateOpticalFlowDense(backend, width, height, imgFmtBL, quality, &payload));
197 
198  // The Dense Optical Flow on NVENC backend expects input to be in block-linear format.
199  // Since Convert Image Format algorithm doesn't currently support direct BGR
200  // pitch-linear (from OpenCV) to NV12 block-linear conversion, it must be done in two
201  // passes, first from BGR/PL to NV12/PL using CUDA, then from NV12/PL to NV12/BL using VIC.
202  // The temporary image buffer below will store the intermediate NV12/PL representation.
203  CHECK_STATUS(vpiImageCreate(width, height, imgFmt, 0, &imgPrevFrameTmp));
204  CHECK_STATUS(vpiImageCreate(width, height, imgFmt, 0, &imgCurFrameTmp));
205 
206  // Now create the final block-linear buffer that'll be used as input to the
207  // algorithm.
208  CHECK_STATUS(vpiImageCreate(width, height, imgFmtBL, 0, &imgPrevFrameBL));
209  CHECK_STATUS(vpiImageCreate(width, height, imgFmtBL, 0, &imgCurFrameBL));
210 
211  // Motion vector image width and height, align to be multiple of 4
212  int32_t mvWidth = (width + 3) / 4;
213  int32_t mvHeight = (height + 3) / 4;
214 
215  // The output video will be heatmap of motion vector image
216 #if CV_MAJOR_VERSION >= 3
217  int fourcc = cv::VideoWriter::fourcc('a', 'v', 'c', '1');
218  double fps = invid.get(cv::CAP_PROP_FPS);
219  std::string extOutputVideo = ".mp4";
220 #else
221  // MP4 support with OpenCV-2.4 has issues, we'll use
222  // avi/mpeg instead.
223  int fourcc = CV_FOURCC('M', 'P', 'E', 'G');
224  double fps = invid.get(CV_CAP_PROP_FPS);
225  std::string extOutputVideo = ".avi";
226 #endif
227 
228  cv::VideoWriter outVideo("denseoptflow_mv_" + strBackend + extOutputVideo, fourcc, fps,
229  cv::Size(mvWidth, mvHeight));
230  if (!outVideo.isOpened())
231  {
232  throw std::runtime_error("Can't create output video");
233  }
234 
235  // Create the output motion vector buffer
236  CHECK_STATUS(vpiImageCreate(mvWidth, mvHeight, VPI_IMAGE_FORMAT_2S16_BL, 0, &imgMotionVecBL));
237 
238  // First convert the first frame to NV12_BL. It'll be used as previous frame when the algorithm is called.
239  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, imgPrevFramePL, imgPrevFrameTmp, nullptr));
240  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_VIC, imgPrevFrameTmp, imgPrevFrameBL, nullptr));
241 
242  // Create a output image which holds the rendered motion vector image.
243  cv::Mat mvOutputImage;
244 
245  // Fetch a new frame until video ends
246  int idxFrame = 1;
247  while (invid.read(cvCurFrame))
248  {
249  printf("Processing frame %d\n", idxFrame++);
250  // Wrap frame into a VPIImage, reusing the existing imgCurFramePL.
251  CHECK_STATUS(vpiImageSetWrappedOpenCVMat(imgCurFramePL, cvCurFrame));
252 
253  // Convert current frame to NV12_BL format
254  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, imgCurFramePL, imgCurFrameTmp, nullptr));
255  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_VIC, imgCurFrameTmp, imgCurFrameBL, nullptr));
256 
257  CHECK_STATUS(
258  vpiSubmitOpticalFlowDense(stream, backend, payload, imgPrevFrameBL, imgCurFrameBL, imgMotionVecBL));
259 
260  // Wait for processing to finish.
261  CHECK_STATUS(vpiStreamSync(stream));
262 
263  // Render the resulting motion vector in the output image
264  ProcessMotionVector(imgMotionVecBL, mvOutputImage);
265 
266  // Save to output video
267  outVideo << mvOutputImage;
268 
269  // Swap previous frame and next frame
270  std::swap(cvPrevFrame, cvCurFrame);
271  std::swap(imgPrevFramePL, imgCurFramePL);
272  std::swap(imgPrevFrameBL, imgCurFrameBL);
273  }
274  }
275  catch (std::exception &e)
276  {
277  std::cerr << e.what() << std::endl;
278  retval = 1;
279  }
280 
281  // Destroy all resources used
282  vpiStreamDestroy(stream);
283  vpiPayloadDestroy(payload);
284 
285  vpiImageDestroy(imgPrevFramePL);
286  vpiImageDestroy(imgPrevFrameTmp);
287  vpiImageDestroy(imgPrevFrameBL);
288  vpiImageDestroy(imgCurFramePL);
289  vpiImageDestroy(imgCurFrameTmp);
290  vpiImageDestroy(imgCurFrameBL);
291  vpiImageDestroy(imgMotionVecBL);
292 
293  return retval;
294 }
295 
296 // vim: ts=8:sw=4:sts=4:et:ai
Functions and structures for dealing with VPI arrays.
Declares functions that handle image format conversion.
Defines types and functions to handle image formats.
Functions and structures for dealing with VPI images.
Functions for handling OpenCV interoperability with VPI.
Declares functions that implement the dense optical flow.
Functions and structures for dealing with VPI pyramids.
Declaration of VPI status codes handling functions.
Declares functions dealing with VPI streams.
VPIStatus vpiSubmitConvertImageFormat(VPIStream stream, uint32_t backend, VPIImage input, VPIImage output, const VPIConvertImageFormatParams *params)
Converts the image contents to the desired format, with optional scaling and offset.
VPIImageFormat
Pre-defined image formats.
Definition: ImageFormat.h:99
@ VPI_IMAGE_FORMAT_2S16_BL
Single plane with two interleaved 16-bit signed integer channel.
Definition: ImageFormat.h:125
@ VPI_IMAGE_FORMAT_NV12_ER
YUV420sp 8-bit pitch-linear format with full range.
Definition: ImageFormat.h:194
@ VPI_IMAGE_FORMAT_NV12_ER_BL
YUV420sp 8-bit block-linear format with full range.
Definition: ImageFormat.h:204
VPIStatus vpiImageLock(VPIImage img, VPILockMode mode, VPIImageData *hostData)
Acquires the lock on an image object and returns a pointer to the image planes.
void vpiImageDestroy(VPIImage img)
Destroy an image instance.
struct VPIImageImpl * VPIImage
A handle to an image.
Definition: Types.h:215
VPIStatus vpiImageCreate(int32_t width, int32_t height, VPIImageFormat fmt, uint32_t flags, VPIImage *img)
Create an empty image instance with the specified flags.
VPIStatus vpiImageUnlock(VPIImage img)
Releases the lock on an image object.
Stores information about image characteristics and content.
Definition: Image.h:159
VPIStatus vpiImageDataExportOpenCVMat(const VPIImageData &imgData, cv::Mat *mat)
Fills an existing cv::Mat with data from VPIImageData coming from a locked VPIImage.
VPIStatus vpiImageSetWrappedOpenCVMat(VPIImage img, const cv::Mat &mat)
Redefines the wrapped cv::Mat of an existing VPIImage wrapper.
VPIStatus vpiImageCreateOpenCVMatWrapper(const cv::Mat &mat, VPIImageFormat fmt, uint32_t flags, VPIImage *img)
Wraps a cv::Mat in an VPIImage with the given image format.
VPIStatus vpiSubmitOpticalFlowDense(VPIStream stream, uint32_t backend, VPIPayload payload, VPIImage prevImg, VPIImage curImg, VPIImage mvImg)
Runs dense Optical Flow on two frames.
VPIStatus vpiCreateOpticalFlowDense(uint32_t backends, int32_t width, int32_t height, VPIImageFormat inputFmt, VPIOpticalFlowQuality quality, VPIPayload *payload)
Creates payload for vpiSubmitOpticalFlowDense.
struct VPIPayloadImpl * VPIPayload
A handle to an algorithm payload.
Definition: Types.h:227
void vpiPayloadDestroy(VPIPayload payload)
Deallocates the payload object and all associated resources.
struct VPIStreamImpl * VPIStream
A handle to a stream.
Definition: Types.h:209
VPIStatus vpiStreamSync(VPIStream stream)
Blocks the calling thread until all submitted commands in this stream queue are done (queue is empty)...
VPIBackend
VPI Backend types.
Definition: Types.h:91
void vpiStreamDestroy(VPIStream stream)
Destroy a stream instance and deallocate all HW resources.
VPIStatus vpiStreamCreate(uint32_t flags, VPIStream *stream)
Create a stream instance.
@ VPI_BACKEND_CUDA
CUDA backend.
Definition: Types.h:93
@ VPI_BACKEND_NVENC
NVENC backend.
Definition: Types.h:96
@ VPI_BACKEND_VIC
VIC backend.
Definition: Types.h:95
VPIOpticalFlowQuality
Defines the quality of the optical flow algorithm.
Definition: Types.h:364
@ VPI_OPTICAL_FLOW_QUALITY_LOW
Fast but low quality optical flow implementation.
Definition: Types.h:366
@ VPI_OPTICAL_FLOW_QUALITY_HIGH
Slow but high quality optical flow implementation.
Definition: Types.h:372
@ VPI_OPTICAL_FLOW_QUALITY_MEDIUM
Speed and quality in between of VPI_OPTICAL_FLOW_QUALITY_LOW and VPI_OPTICAL_FLOW_QUALITY_HIGH.
Definition: Types.h:369
@ VPI_LOCK_READ
Lock memory only for reading.
Definition: Types.h:383