VPI - Vision Programming Interface

1.2 Release

Perspective Warp

Overview

The Perspective Warp sample application takes an input video and outputs a video where for each frame, a different perspective warp is applied. The result is a perspective bouncy effect. Sample application could be modified to get the input from a camera and apply the effect in real-time.

Instructions

The command line parameters are:

<backend> <input video>

where

  • backend: Either cpu, cuda or vic (on Jetson devices only). It defines the backend that will perform the processing.
  • input video: video file to the effect applied on; it accepts .mp4, .avi and possibly others, depending on OpenCV's support.

VPI samples installer includes some sample videos that can be used as input. They are found in /opt/nvidia/vpi1/samples/assets/ directory.

Here's one invocation example:

  • C++
    ./vpi_sample_10_perspwarp cuda ../assets/noisy.mp4
  • Python
    python main.py cuda ../assets/noisy.mp4

The application will process noisy.mp4 and create perspwarp_cuda.mp4 with time-variant perspective warps applied to input frames.

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

Results

Input videoPerspective effect

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 math import sin, cos, pi
32 from argparse import ArgumentParser
33 
34 # ----------------------------
35 # Parse command line arguments
36 
37 parser = ArgumentParser()
38 parser.add_argument('backend', choices=['cpu', 'cuda','vic'],
39  help='Backend to be used for processing')
40 
41 parser.add_argument('input',
42  help='Input video to be denoised')
43 
44 args = parser.parse_args();
45 
46 if args.backend == 'cuda':
47  backend = vpi.Backend.CUDA
48 elif args.backend == 'cpu':
49  backend = vpi.Backend.CPU
50 else:
51  assert args.backend == 'vic'
52  backend = vpi.Backend.VIC
53 
54 # -----------------------------
55 # Open input and output videos
56 
57 inVideo = cv2.VideoCapture(args.input)
58 
59 if int(cv2.__version__.split('.')[0]) >= 3:
60  extOutputVideo = '.mp4'
61  fourcc = cv2.VideoWriter_fourcc(*'avc1')
62  inSize = (int(inVideo.get(cv2.CAP_PROP_FRAME_WIDTH)), int(inVideo.get(cv2.CAP_PROP_FRAME_HEIGHT)))
63  fps = inVideo.get(cv2.CAP_PROP_FPS)
64 else:
65  # MP4 support with OpenCV-2.4 has issues, we'll use
66  # avi/mpeg instead.
67  extOutputVideo = '.avi'
68  fourcc = cv2.cv.CV_FOURCC('M','P','E','G')
69  inSize = (int(inVideo.get(cv2.cv.CV_CAP_PROP_FRAME_WIDTH)), int(inVideo.get(cv2.cv.CV_CAP_PROP_FRAME_HEIGHT)))
70  fps = inVideo.get(cv2.cv.CV_CAP_PROP_FPS)
71 
72 outVideo = cv2.VideoWriter('perspwarp_python'+str(sys.version_info[0])+'_'+args.backend+extOutputVideo,
73  fourcc, fps, inSize)
74 
75 #--------------------------------------------------------------
76 # Main processing loop
77 curFrame = 1
78 while True:
79  print("Frame: {}".format(curFrame))
80  curFrame+=1
81 
82  # Read one input frame
83  ret, cvFrame = inVideo.read()
84  if not ret:
85  break
86 
87  # Convert it to NV12_ER format to be used by VPI
88  with vpi.Backend.CUDA:
89  frame = vpi.asimage(cvFrame).convert(vpi.Format.NV12_ER)
90 
91  # Calculate the transformation to be applied ------------
92 
93  # Move image's center to origin of coordinate system
94  T1 = np.array([[1, 0, -frame.width/2.0],
95  [0, 1, -frame.height/2.0],
96  [0, 0, 1]])
97 
98  # Apply some time-dependent perspective transform
99  v1 = sin(curFrame/30.0*2*pi/2)*0.0005
100  v2 = cos(curFrame/30.0*2*pi/3)*0.0005
101  P = np.array([[0.66, 0, 0],
102  [0, 0.66, 0],
103  [v1, v2, 1]])
104 
105  # Move image's center back to where it was
106  T2 = np.array([[1, 0, frame.width/2.0],
107  [0, 1, frame.height/2.0],
108  [0, 0, 1]])
109 
110  # Do perspective warp using the backend passed in the command line.
111  with backend:
112  frame = frame.perspwarp(np.matmul(T2, np.matmul(P, T1)))
113 
114  # Convert it to RGB8 for output using the CUDA backend
115  with vpi.Backend.CUDA:
116  frame = frame.convert(vpi.Format.RGB8)
117 
118  # Write the denoised frame to the output video
119  with frame.rlock():
120  outVideo.write(frame.cpu())
121 
122 # 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/Image.h>
41 #include <vpi/Status.h>
42 #include <vpi/Stream.h>
45 
46 #include <algorithm>
47 #include <cstring> // for memset
48 #include <fstream>
49 #include <iostream>
50 #include <random>
51 #include <sstream>
52 
53 #define CHECK_STATUS(STMT) \
54  do \
55  { \
56  VPIStatus status = (STMT); \
57  if (status != VPI_SUCCESS) \
58  { \
59  char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \
60  vpiGetLastStatusMessage(buffer, sizeof(buffer)); \
61  std::ostringstream ss; \
62  ss << vpiStatusGetName(status) << ": " << buffer; \
63  throw std::runtime_error(ss.str()); \
64  } \
65  } while (0);
66 
67 static void MatrixMultiply(VPIPerspectiveTransform &r, const VPIPerspectiveTransform &a,
68  const VPIPerspectiveTransform &b)
69 {
70  for (int i = 0; i < 3; ++i)
71  {
72  for (int j = 0; j < 3; ++j)
73  {
74  r[i][j] = a[i][0] * b[0][j];
75  for (int k = 1; k < 3; ++k)
76  {
77  r[i][j] += a[i][k] * b[k][j];
78  }
79  }
80  }
81 }
82 
83 int main(int argc, char *argv[])
84 {
85  // OpenCV image that will be wrapped by a VPIImage.
86  // Define it here so that it's destroyed *after* wrapper is destroyed
87  cv::Mat cvFrame;
88 
89  // VPI objects that will be used
90  VPIStream stream = NULL;
91  VPIImage imgInput = NULL, imgOutput = NULL;
92  VPIPayload warp = NULL;
93  VPIImage frameBGR = NULL;
94 
95  int retval = 0;
96 
97  try
98  {
99  // =============================
100  // Parse command line parameters
101 
102  if (argc != 3)
103  {
104  throw std::runtime_error(std::string("Usage: ") + argv[0] + " <cpu|vic|cuda> <input_video>");
105  }
106 
107  std::string strBackend = argv[1];
108  std::string strInputVideo = argv[2];
109 
110  // Now parse the backend
111  VPIBackend backend;
112 
113  if (strBackend == "cpu")
114  {
115  backend = VPI_BACKEND_CPU;
116  }
117  else if (strBackend == "cuda")
118  {
119  backend = VPI_BACKEND_CUDA;
120  }
121  else if (strBackend == "vic")
122  {
123  backend = VPI_BACKEND_VIC;
124  }
125  else
126  {
127  throw std::runtime_error("Backend '" + strBackend +
128  "' not recognized, it must be either cpu, cuda or vic.");
129  }
130 
131  // ===============================
132  // Prepare input and output videos
133 
134  // Load the input video
135  cv::VideoCapture invid;
136  if (!invid.open(strInputVideo))
137  {
138  throw std::runtime_error("Can't open '" + strInputVideo + "'");
139  }
140 
141  // Open the output video for writing using input's characteristics
142 #if CV_MAJOR_VERSION >= 3
143  int w = invid.get(cv::CAP_PROP_FRAME_WIDTH);
144  int h = invid.get(cv::CAP_PROP_FRAME_HEIGHT);
145  int fourcc = cv::VideoWriter::fourcc('a', 'v', 'c', '1');
146  double fps = invid.get(cv::CAP_PROP_FPS);
147  std::string extOutputVideo = ".mp4";
148 #else
149  // MP4 support with OpenCV-2.4 has issues, we'll use
150  // avi/mpeg instead.
151  int w = invid.get(CV_CAP_PROP_FRAME_WIDTH);
152  int h = invid.get(CV_CAP_PROP_FRAME_HEIGHT);
153  int fourcc = CV_FOURCC('M', 'P', 'E', 'G');
154  double fps = invid.get(CV_CAP_PROP_FPS);
155  std::string extOutputVideo = ".avi";
156 #endif
157 
158  cv::VideoWriter outVideo("perspwarp_" + strBackend + extOutputVideo, fourcc, fps, cv::Size(w, h));
159  if (!outVideo.isOpened())
160  {
161  throw std::runtime_error("Can't create output video");
162  }
163 
164  // =================================
165  // Allocate all VPI resources needed
166 
167  // Create the stream for the given backend. We'll be using CUDA for image format conversion.
168  CHECK_STATUS(vpiStreamCreate(backend | VPI_BACKEND_CUDA, &stream));
169 
170  CHECK_STATUS(vpiImageCreate(w, h, VPI_IMAGE_FORMAT_NV12_ER, 0, &imgInput));
171  CHECK_STATUS(vpiImageCreate(w, h, VPI_IMAGE_FORMAT_NV12_ER, 0, &imgOutput));
172 
173  // Create a Perspective Warp payload.
174  CHECK_STATUS(vpiCreatePerspectiveWarp(backend, &warp));
175 
177  memset(&xform, 0, sizeof(xform));
178 
179  // ====================
180  // Main processing loop
181 
182  int curFrame = 1;
183  while (invid.read(cvFrame))
184  {
185  printf("Frame: %d\n", curFrame++);
186 
187  if (frameBGR == NULL)
188  {
189  // Ceate a VPIImage that wraps the frame
190  CHECK_STATUS(vpiImageCreateOpenCVMatWrapper(cvFrame, 0, &frameBGR));
191  }
192  else
193  {
194  // reuse existing VPIImage wrapper to wrap the new frame.
195  CHECK_STATUS(vpiImageSetWrappedOpenCVMat(frameBGR, cvFrame));
196  }
197 
198  // First convert it to NV12 using CUDA
199  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, frameBGR, imgInput, NULL));
200 
201  // move image's center to origin of coordinate system
202  VPIPerspectiveTransform t1 = {{1, 0, -w / 2.0f}, {0, 1, -h / 2.0f}, {0, 0, 1}};
203 
204  // Apply some time-dependent perspective transform
205  float v1 = sin(curFrame / 30.0 * 2 * M_PI / 2) * 0.0005f;
206  float v2 = cos(curFrame / 30.0 * 2 * M_PI / 3) * 0.0005f;
207  VPIPerspectiveTransform P = {{0.66, 0, 0}, {0, 0.66, 0}, {v1, v2, 1}};
208 
209  // move image's center back to where it was.
210  VPIPerspectiveTransform t2 = {{1, 0, w / 2.0f}, {0, 1, h / 2.0f}, {0, 0, 1}};
211 
212  // Apply the transforms defined above.
214  MatrixMultiply(tmp, P, t1);
215  MatrixMultiply(xform, t2, tmp);
216 
217  // Do perspective warp using the backend passed in the command line.
218  CHECK_STATUS(vpiSubmitPerspectiveWarp(stream, 0, warp, imgInput, xform, imgOutput, VPI_INTERP_LINEAR,
219  VPI_BORDER_ZERO, 0));
220 
221  // Convert output back to BGR using CUDA
222  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, imgOutput, frameBGR, NULL));
223  CHECK_STATUS(vpiStreamSync(stream));
224 
225  // Now add it to the output video stream
226  VPIImageData imgdata;
227  CHECK_STATUS(vpiImageLock(frameBGR, VPI_LOCK_READ, &imgdata));
228 
229  cv::Mat outFrame;
230  CHECK_STATUS(vpiImageDataExportOpenCVMat(imgdata, &outFrame));
231  outVideo << outFrame;
232 
233  CHECK_STATUS(vpiImageUnlock(frameBGR));
234  }
235  }
236  catch (std::exception &e)
237  {
238  std::cerr << e.what() << std::endl;
239  retval = 1;
240  }
241 
242  // =========================
243  // Destroy all VPI resources
244 
245  vpiStreamDestroy(stream);
246  vpiPayloadDestroy(warp);
247  vpiImageDestroy(imgInput);
248  vpiImageDestroy(imgOutput);
249  vpiImageDestroy(frameBGR);
250 
251  return retval;
252 }
253 
254 // vim: ts=8:sw=4:sts=4:et:ai
Declares functions that handle image format conversion.
Functions and structures for dealing with VPI images.
Functions for handling OpenCV interoperability with VPI.
Declares functions that implement the Perspective Warp algorithm.
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.
@ VPI_IMAGE_FORMAT_NV12_ER
YUV420sp 8-bit pitch-linear format with full range.
Definition: ImageFormat.h:194
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.
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.
float VPIPerspectiveTransform[3][3]
Represents a 2D perspective transform.
Definition: Types.h:455
VPIStatus vpiSubmitPerspectiveWarp(VPIStream stream, uint32_t backend, VPIPayload payload, VPIImage input, const VPIPerspectiveTransform xform, VPIImage output, VPIInterpolationType interp, VPIBorderExtension border, uint32_t flags)
Submits the Perspective Warp operation to the stream associated with the payload.
VPIStatus vpiCreatePerspectiveWarp(uint32_t backends, VPIPayload *payload)
Creates a payload for Perspective Warp algorithm.
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_VIC
VIC backend.
Definition: Types.h:95
@ VPI_BACKEND_CPU
CPU backend.
Definition: Types.h:92
@ VPI_BORDER_ZERO
All pixels outside the image are considered to be zero.
Definition: Types.h:237
@ VPI_INTERP_LINEAR
Linear interpolation.
Definition: Interpolation.h:93
@ VPI_LOCK_READ
Lock memory only for reading.
Definition: Types.h:383