VPI - Vision Programming Interface

0.4.4 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.

This sample shows the following:

  • Creating and destroying a VPI stream.
  • Wrapping an OpenCV image to be used by VPI.
  • Wrapping a VPI-managed image into an OpenCV's cv::Mat.
  • Use OpenCV to fetch frames from a video file.
  • Use OpenCV save frames into a video file.
  • Create pipeline that does Convert Image Format to convert from/to NV12 format and run Perspective Warp.
  • Create a perspective transform matrix with origin on the center of input frame.

Instructions

The usage is:

./vpi_sample_10_perspwarp <backend> <input video> <output video>

where

  • backend: currently only pva, as other backends don't have an implementation available. 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.
  • output video: file to write the resulting video. It'll use the same codec and fps as input video.

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

Here's one invocation example:

./vpi_sample_10_perspwarp pva ../assets/noisy.mp4 persp_effect.mp4

Results

Input videoPerspective effect

Source code

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

1 /*
2 * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of NVIDIA CORPORATION nor the names of its
13 * contributors may be used to endorse or promote products derived
14 * from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28 
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 
39 #include <vpi/Context.h>
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 // Utility function to wrap a cv::Mat into a VPIImage
84 static VPIImage ToVPIImage(VPIImage image, const cv::Mat &frame)
85 {
86  VPIImageData imgData;
87  memset(&imgData, 0, sizeof(imgData));
88 
89  switch (frame.type())
90  {
91  case CV_8U:
92  imgData.type = VPI_IMAGE_FORMAT_U8;
93  break;
94  case CV_8UC3:
95  imgData.type = VPI_IMAGE_FORMAT_BGR8;
96  break;
97  case CV_8UC4:
98  imgData.type = VPI_IMAGE_FORMAT_BGRA8;
99  break;
100  default:
101  throw std::runtime_error("Frame type not supported");
102  }
103 
104  // First fill VPIImageData with the, well, image data...
105  imgData.numPlanes = 1;
106  imgData.planes[0].width = frame.cols;
107  imgData.planes[0].height = frame.rows;
108  imgData.planes[0].pitchBytes = frame.step[0];
109  imgData.planes[0].data = frame.data;
110 
111  if (image == nullptr)
112  {
113  // Now create a VPIImage that wraps it.
114  CHECK_STATUS(vpiImageCreateHostMemWrapper(&imgData, 0, &image));
115  }
116  else
117  {
118  // image is already created, we only have to update the wrapped memory,
119  // this is done without allocating memory.
120  CHECK_STATUS(vpiImageSetWrappedHostMem(image, &imgData));
121  }
122  return image;
123 };
124 
125 // Utility function to wrap a VPIImageData into a cv::Mat
126 static cv::Mat ToCV(const VPIImageData &imgData)
127 {
128  cv::Mat out;
129 
130  switch (imgData.type)
131  {
133  out = cv::Mat(imgData.planes[0].height, imgData.planes[0].width, CV_8UC3, imgData.planes[0].data,
134  imgData.planes[0].pitchBytes);
135  break;
136 
138  out = cv::Mat(imgData.planes[0].height, imgData.planes[0].width, CV_8UC4, imgData.planes[0].data,
139  imgData.planes[0].pitchBytes);
140  break;
141 
142  case VPI_IMAGE_FORMAT_U8:
143  out = cv::Mat(imgData.planes[0].height, imgData.planes[0].width, CV_8UC1, imgData.planes[0].data,
144  imgData.planes[0].pitchBytes);
145  break;
146 
147  default:
148  throw std::runtime_error("Frame type not supported");
149  }
150 
151  return out;
152 }
153 
154 int main(int argc, char *argv[])
155 {
156  // We'll create all our objects under this context, so that
157  // we don't have to track what objects to destroy. Just destroying
158  // the context will destroy all objects.
159  VPIContext ctx = nullptr;
160 
161  int retval = 0;
162 
163  try
164  {
165  if (argc != 4)
166  {
167  throw std::runtime_error(std::string("Usage: ") + argv[0] + " <cpu|vic|cuda> <input_video> <output>");
168  }
169 
170  std::string strBackend = argv[1];
171  std::string strInputVideo = argv[2];
172  std::string strOutputVideo = argv[3];
173 
174  // Load the input video
175  cv::VideoCapture invid;
176  if (!invid.open(strInputVideo))
177  {
178  throw std::runtime_error("Can't open '" + strInputVideo + "'");
179  }
180 
181  // Create our context.
182  CHECK_STATUS(vpiContextCreate(0, &ctx));
183 
184  // Activate it. From now on all created objects will be owned by it.
185  CHECK_STATUS(vpiContextSetCurrent(ctx));
186 
187  // Now parse the backend
188  VPIBackend backend;
189 
190  if (strBackend == "cpu")
191  {
192  backend = VPI_BACKEND_CPU;
193  }
194  else if (strBackend == "cuda")
195  {
196  backend = VPI_BACKEND_CUDA;
197  }
198  else if (strBackend == "vic")
199  {
200  backend = VPI_BACKEND_VIC;
201  }
202  else
203  {
204  throw std::runtime_error("Backend '" + strBackend +
205  "' not recognized, it must be either cpu, cuda or vic.");
206  }
207 
208  // Create the stream for the given backend. We'll be using CUDA for image format conversion.
209  VPIStream stream;
210  CHECK_STATUS(vpiStreamCreate(backend | VPI_BACKEND_CUDA, &stream));
211 
212 #if CV_MAJOR_VERSION >= 3
213  int w = invid.get(cv::CAP_PROP_FRAME_WIDTH);
214  int h = invid.get(cv::CAP_PROP_FRAME_HEIGHT);
215  int fourcc = invid.get(cv::CAP_PROP_FOURCC);
216  double fps = invid.get(cv::CAP_PROP_FPS);
217 #else
218  int w = invid.get(CV_CAP_PROP_FRAME_WIDTH);
219  int h = invid.get(CV_CAP_PROP_FRAME_HEIGHT);
220  int fourcc = invid.get(CV_CAP_PROP_FOURCC);
221  double fps = invid.get(CV_CAP_PROP_FPS);
222 #endif
223 
224  cv::VideoWriter outVideo(strOutputVideo, fourcc, fps, cv::Size(w, h));
225 
226  VPIImage imgInput, imgOutput;
227  CHECK_STATUS(vpiImageCreate(w, h, VPI_IMAGE_FORMAT_NV12, 0, &imgInput));
228  CHECK_STATUS(vpiImageCreate(w, h, VPI_IMAGE_FORMAT_NV12, 0, &imgOutput));
229 
230  // Create a Perspective Warp payload.
231  VPIPayload warp;
232  CHECK_STATUS(vpiCreatePerspectiveWarp(backend, &warp));
233 
235  memset(&xform, 0, sizeof(xform));
236 
237  VPIImage frameBGR = nullptr;
238 
239  int curFrame = 1;
240  cv::Mat cvFrame;
241  while (invid.read(cvFrame))
242  {
243  printf("Frame: %d\n", curFrame++);
244 
245  frameBGR = ToVPIImage(frameBGR, cvFrame);
246 
247  // First convert it to NV12 using CUDA
248  CHECK_STATUS(
249  vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, frameBGR, imgInput, VPI_CONVERSION_CAST, 1, 0));
250 
251  // move image's center to origin of coordinate system
252  VPIPerspectiveTransform t1 = {{1, 0, -w / 2.0f}, {0, 1, -h / 2.0f}, {0, 0, 1}};
253 
254  // Apply some time-dependent perspective transform
255  float v1 = sin(curFrame / 30.0 * 2 * M_PI / 2) * 0.0005f;
256  float v2 = cos(curFrame / 30.0 * 2 * M_PI / 3) * 0.0005f;
257  VPIPerspectiveTransform P = {{0.66, 0, 0}, {0, 0.66, 0}, {v1, v2, 1}};
258 
259  // move image's center back to where it was.
260  VPIPerspectiveTransform t2 = {{1, 0, w / 2.0f}, {0, 1, h / 2.0f}, {0, 0, 1}};
261 
262  // Apply the transforms defined above.
264  MatrixMultiply(tmp, P, t1);
265  MatrixMultiply(xform, t2, tmp);
266 
267  // Do perspective warp using the backend passed in the command line.
268  CHECK_STATUS(vpiSubmitPerspectiveWarp(stream, warp, imgInput, xform, imgOutput, VPI_INTERP_LINEAR,
270 
271  // Convert output back to BGR using CUDA
272  CHECK_STATUS(
273  vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, imgOutput, frameBGR, VPI_CONVERSION_CAST, 1, 0));
274  CHECK_STATUS(vpiStreamSync(stream));
275 
276  // Now add it to the output video stream
277  VPIImageData imgdata;
278  CHECK_STATUS(vpiImageLock(frameBGR, VPI_LOCK_READ, &imgdata));
279  outVideo << ToCV(imgdata);
280  CHECK_STATUS(vpiImageUnlock(frameBGR));
281  }
282  }
283  catch (std::exception &e)
284  {
285  std::cerr << e.what() << std::endl;
286  retval = 1;
287  }
288 
289  // Clean up
290  vpiContextDestroy(ctx);
291 
292  return retval;
293 }
VPIImagePlane::height
uint32_t height
Height of this plane in pixels.
Definition: Image.h:138
VPIContext
struct VPIContextImpl * VPIContext
A handle to a context.
Definition: Types.h:178
VPIImagePlane::width
uint32_t width
Width of this plane in pixels.
Definition: Image.h:137
VPI_IMAGE_FORMAT_NV12
@ VPI_IMAGE_FORMAT_NV12
YUV420sp 8-bit pitch-linear format composed of two planes:
Definition: ImageFormat.h:123
vpiStreamCreate
VPIStatus vpiStreamCreate(uint32_t flags, VPIStream *stream)
Create a stream instance.
VPIBackend
VPIBackend
VPI Backend types.
Definition: Types.h:89
vpiContextCreate
VPIStatus vpiContextCreate(uint32_t flags, VPIContext *ctx)
Create a context instance.
vpiContextSetCurrent
VPIStatus vpiContextSetCurrent(VPIContext ctx)
Sets the context for the calling thread.
VPI_LOCK_READ
@ VPI_LOCK_READ
Lock memory only for reading.
Definition: Types.h:447
VPI_IMAGE_FORMAT_BGRA8
@ VPI_IMAGE_FORMAT_BGRA8
Single plane with interleaved BGRA 8-bit channel.
Definition: ImageFormat.h:141
VPI_IMAGE_FORMAT_BGR8
@ VPI_IMAGE_FORMAT_BGR8
Single plane with interleaved BGR 8-bit channel.
Definition: ImageFormat.h:135
vpiImageUnlock
VPIStatus vpiImageUnlock(VPIImage img)
Releases the lock on an image object.
PerspectiveWarp.h
Declares functions that implement the Perspective Warp algorithm.
vpiStreamSync
VPIStatus vpiStreamSync(VPIStream stream)
Blocks the calling thread until all submitted commands in this stream queue are done (queue is empty)...
VPI_BACKEND_CUDA
@ VPI_BACKEND_CUDA
CUDA backend.
Definition: Types.h:91
VPIImageData
Stores information about image characteristics and content.
Definition: Image.h:159
VPIStream
struct VPIStreamImpl * VPIStream
A handle to a stream.
Definition: Types.h:190
VPI_INTERP_LINEAR
@ VPI_INTERP_LINEAR
Alias to fast linear interpolation.
Definition: Types.h:282
vpiContextDestroy
void vpiContextDestroy(VPIContext ctx)
Destroy a context instance as well as all resources it owns.
VPIPerspectiveTransform
float VPIPerspectiveTransform[3][3]
Represents a 2D perspective transform.
Definition: Types.h:512
VPIImageData::planes
VPIImagePlane planes[VPI_MAX_PLANE_COUNT]
Data of all image planes.
Definition: Image.h:166
vpiImageCreate
VPIStatus vpiImageCreate(uint32_t width, uint32_t height, VPIImageFormat fmt, uint32_t flags, VPIImage *img)
Create an empty image instance with the specified flags.
Image.h
Functions and structures for dealing with VPI images.
VPI_IMAGE_FORMAT_U8
@ VPI_IMAGE_FORMAT_U8
Single plane with one 8-bit unsigned integer channel.
Definition: ImageFormat.h:99
VPIImagePlane::pitchBytes
uint32_t pitchBytes
Difference in bytes of beginning of one row and the beginning of the previous.
Definition: Image.h:139
VPIImage
struct VPIImageImpl * VPIImage
A handle to an image.
Definition: Types.h:196
VPIImageData::numPlanes
int32_t numPlanes
Number of planes.
Definition: Image.h:161
VPI_BACKEND_VIC
@ VPI_BACKEND_VIC
VIC backend.
Definition: Types.h:93
VPI_BOUNDARY_COND_ZERO
@ VPI_BOUNDARY_COND_ZERO
All pixels outside the image are considered to be zero.
Definition: Types.h:218
vpiSubmitConvertImageFormat
VPIStatus vpiSubmitConvertImageFormat(VPIStream stream, VPIBackend backend, VPIImage input, VPIImage output, VPIConversionPolicy convPolicy, float scale, float offset)
Converts the image contents to the desired format, with optional scaling and offset.
VPIPayload
struct VPIPayloadImpl * VPIPayload
A handle to an algorithm payload.
Definition: Types.h:208
vpiImageLock
VPIStatus vpiImageLock(VPIImage img, VPILockMode mode, VPIImageData *hostData)
Acquires the lock on an image object and returns a pointer to the image planes.
VPIImageData::type
VPIImageFormat type
Image type.
Definition: Image.h:160
Status.h
Declaration of VPI status codes handling functions.
ConvertImageFormat.h
Declares functions that handle image format conversion.
VPI_BACKEND_CPU
@ VPI_BACKEND_CPU
CPU backend.
Definition: Types.h:90
VPI_CONVERSION_CAST
@ VPI_CONVERSION_CAST
Casts input to the output type.
Definition: Types.h:336
vpiImageCreateHostMemWrapper
VPIStatus vpiImageCreateHostMemWrapper(const VPIImageData *hostData, uint32_t flags, VPIImage *img)
Create an image object by wrapping around an existing host memory block.
Stream.h
Declares functions dealing with VPI streams.
vpiImageSetWrappedHostMem
VPIStatus vpiImageSetWrappedHostMem(VPIImage img, const VPIImageData *hostData)
Redefines the wrapped host memory in an existing VPIImage wrapper.
VPIImagePlane::data
void * data
Pointer to the first row of this plane.
Definition: Image.h:147
vpiCreatePerspectiveWarp
VPIStatus vpiCreatePerspectiveWarp(VPIBackend backend, VPIPayload *payload)
Creates a payload for Perspective Warp algorithm.
vpiSubmitPerspectiveWarp
VPIStatus vpiSubmitPerspectiveWarp(VPIStream stream, VPIPayload payload, VPIImage input, const VPIPerspectiveTransform xform, VPIImage output, VPIInterpolationType interp, VPIBoundaryCond bcond, uint32_t flags)
Submits the Perspective Warp operation to the stream associated with the payload.
Context.h
Functions and structures for dealing with VPI contexts.