VPI - Vision Programming Interface

3.0 Release

Image View

Overview

The Image View application splits the input image in 4 views, in a 2x2 grid, processing each image view differently, potentially in parallel and using different backends. The processing consumes the same input parent image and produce a single output, saving the result as an image file on disk. The user defines the input image for processing. The backends used for each processing, and each algorithm and their parameters, are fixed as follows:

  1. CPU Bilateral filter
  2. CPU Image flip in horizontal direction
  3. CUDA Image flip in vertical direction
  4. CUDA Image flip in both directions

Instructions

The command line parameters are:

<input image>

where

  • input image: input image file name; it accepts png, jpeg and possibly others.

Here's one example:

  • C++
    ./vpi_sample_15_image_view ../assets/kodim08.png
  • Python
    python3 main.py ../assets/kodim08.png

This uses one of the provided sample images. You can try with other images, respecting the constraints imposed by each algorithm running on each image view.

Results

Input imageOutput image

Source Code

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

Language:
27 import sys
28 import vpi
29 import numpy as np
30 from PIL import Image
31 from argparse import ArgumentParser
32 
33 # Parse command line arguments
34 parser = ArgumentParser()
35 parser.add_argument('input', help='Image to be used as input')
36 
37 args = parser.parse_args();
38 
39 # Load input parent image into a vpi.Image
40 try:
41  input = vpi.asimage(np.asarray(Image.open(args.input)))
42 except IOError:
43  sys.exit("Input file not found")
44 except:
45  sys.exit("Error with input file")
46 
47 # Create 4 streams for independent processing
48 streams = []
49 for _ in range(4):
50  streams.append(vpi.Stream())
51 
52 # Create input parent image and output as grayscale images in the CPU using numpy
53 np_parent = np.zeros((input.height, input.width), np.uint8)
54 np_output = np.zeros((input.height, input.width), np.uint8)
55 
56 # Wrap the input parent and output using VPI, this is important
57 # because views on Tegra devices can only be created on wrapped CPU or
58 # CUDA buffers. If an image is created with vpi.Image, and there is a
59 # different backend available, the view cannot be created.
60 parent = vpi.asimage(np_parent)
61 output = vpi.asimage(np_output)
62 
63 # Convert input parent image to grayscale
64 with vpi.Backend.CPU, streams[0]:
65  input.convert(out=parent)
66 
67 # Calculate views size: width x height
68 viewSize = (input.width // 2, input.height // 2)
69 
70 # Define clip bounds for each view
71 clipBounds = vpi.RectangleI(0, 0, viewSize[0], viewSize[1])
72 
73 # Create 4 input and output child image views
74 parViews = []
75 outViews = []
76 for i in range(4):
77  clipBounds.x = (i % 2) * viewSize[0]
78  clipBounds.y = (i // 2) * viewSize[1]
79  parViews.append(parent.view(clipBounds))
80  outViews.append(output.view(clipBounds))
81 
82 # Run one algorithm on each view:
83 # Each algorithm runs on a different stream, potentially in parallel, all streams working together
84 # consuming the same input to produce a single output, using CPU and CUDA backends
85 
86 with vpi.Backend.CPU:
87  parViews[0].bilateral_filter(3, 45, 35, out=outViews[0], border=vpi.Border.ZERO, stream=streams[0])
88  parViews[1].image_flip(vpi.Flip.HORIZ, out=outViews[1], stream=streams[1])
89 
90 with vpi.Backend.CUDA:
91  parViews[2].image_flip(vpi.Flip.VERT, out=outViews[2], stream=streams[2])
92  parViews[3].image_flip(vpi.Flip.BOTH, out=outViews[3], stream=streams[3])
93 
94 # Sync all streams to guarantee they finish before reading the output image
95 for i in range(4):
96  streams[i].sync()
97 
98 # Save result to disk
99 Image.fromarray(output.cpu()).save('output_views_python'+str(sys.version_info[0])+'.png')
29 #include <opencv2/core/version.hpp>
30 #include <opencv2/imgcodecs.hpp>
31 #include <opencv2/imgproc/imgproc.hpp>
32 #include <vpi/OpenCVInterop.hpp>
33 
34 #include <vpi/Event.h>
35 #include <vpi/Image.h>
36 #include <vpi/Status.h>
37 #include <vpi/Stream.h>
40 #include <vpi/algo/ImageFlip.h>
41 
42 #include <cstring> // for memset
43 #include <iostream>
44 #include <sstream>
45 
46 #define CHECK_STATUS(STMT) \
47  do \
48  { \
49  VPIStatus status = (STMT); \
50  if (status != VPI_SUCCESS) \
51  { \
52  char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \
53  vpiGetLastStatusMessage(buffer, sizeof(buffer)); \
54  std::ostringstream ss; \
55  ss << vpiStatusGetName(status) << ": " << buffer; \
56  throw std::runtime_error(ss.str()); \
57  } \
58  } while (0);
59 
60 int main(int argc, char *argv[])
61 {
62  // OpenCV image that will be wrapped by a VPIImage.
63  // Define it here so that it's destroyed *after* wrapper is destroyed
64  cv::Mat cvImage;
65 
66  // VPI objects that will be used
67  VPIImage imageBGR = NULL;
68  VPIImage inParent = NULL;
69  VPIImage outParent = NULL;
70  VPIEvent parentEvent = NULL;
71  VPIImage inViews[4] = {};
72  VPIImage outViews[4] = {};
73  VPIStream streams[4] = {};
74 
75  int retval = 0;
76 
77  // Views on Tegra devices need parent images to have only CPU or CUDA backends enabled.
78  int imgFlags = VPI_BACKEND_CPU | VPI_BACKEND_CUDA;
79 
80  try
81  {
82  if (argc != 2)
83  {
84  throw std::runtime_error(std::string("Usage: ") + argv[0] + " <input image>");
85  }
86 
87  std::string strInputFileName = argv[1];
88 
89  // Load the input image
90  cvImage = cv::imread(strInputFileName);
91  if (cvImage.empty())
92  {
93  throw std::runtime_error("Can't open '" + strInputFileName + "'");
94  }
95 
96  // Wrap the loaded image into a VPIImage object to be used by VPI
97  CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvImage, 0, &imageBGR));
98 
99  // Retrieve the width and height of the loaded parent image
100  const int parentWidth = cvImage.cols, parentHeight = cvImage.rows;
101 
102  // Calculate the width and height of the child image views
103  const int viewWidth = parentWidth / 2, viewHeight = parentHeight / 2;
104 
105  if ((viewWidth == 0) || (viewHeight == 0))
106  {
107  throw std::runtime_error(std::string("Input image too small, it must be at least 2x2"));
108  }
109 
110  // Create the input parent image as a single unsigned 8-bit channel
111  CHECK_STATUS(vpiImageCreate(parentWidth, parentHeight, VPI_IMAGE_FORMAT_U8, imgFlags, &inParent));
112 
113  // Create the output parent image, single unsigned 8-bit channel
114  CHECK_STATUS(vpiImageCreate(parentWidth, parentHeight, VPI_IMAGE_FORMAT_U8, imgFlags, &outParent));
115 
116  // Create 1 event for the parent image
117  CHECK_STATUS(vpiEventCreate(0, &parentEvent));
118 
119  for (int i = 0; i < 4; ++i)
120  {
121  // Create 4 streams to execute algorithms on
122  CHECK_STATUS(vpiStreamCreate(0, &streams[i]));
123 
124  // Define the clip bounds to be the rectangular region of each view inside the parent image
125  VPIRectangleI clipBounds;
126 
127  clipBounds.x = static_cast<int>(i % 2) * viewWidth;
128  clipBounds.y = static_cast<int>(i / 2) * viewHeight;
129  clipBounds.width = viewWidth;
130  clipBounds.height = viewHeight;
131 
132  // Create each input child image view
133  CHECK_STATUS(vpiImageCreateView(inParent, &clipBounds, imgFlags, &inViews[i]));
134 
135  // Create each output child image view
136  CHECK_STATUS(vpiImageCreateView(outParent, &clipBounds, imgFlags, &outViews[i]));
137  }
138 
139  // Run one algorithm on each view:
140  // Each algorithm runs on a different stream, potentially in parallel, all streams working together
141  // consuming the same input to produce a single output, using CPU and CUDA backends
142 
143  // Convert the loaded parent image to grayscale
144  CHECK_STATUS(vpiSubmitConvertImageFormat(streams[0], VPI_BACKEND_CPU, imageBGR, inParent, NULL));
145 
146  // Record the parent event announcing the conversion above has finished
147  CHECK_STATUS(vpiEventRecord(parentEvent, streams[0]));
148 
149  // Wait the conversion above to finish, then release all streams for view processing
150  CHECK_STATUS(vpiStreamWaitEvent(streams[1], parentEvent));
151  CHECK_STATUS(vpiStreamWaitEvent(streams[2], parentEvent));
152  CHECK_STATUS(vpiStreamWaitEvent(streams[3], parentEvent));
153 
154  // Submit an algorithm on 1st view
155  CHECK_STATUS(
156  vpiSubmitBilateralFilter(streams[0], VPI_BACKEND_CPU, inViews[0], outViews[0], 3, 45, 35, VPI_BORDER_ZERO));
157 
158  // Submit an algorithm on 2nd view
159  CHECK_STATUS(vpiSubmitImageFlip(streams[1], VPI_BACKEND_CPU, inViews[1], outViews[1], VPI_FLIP_HORIZ));
160 
161  // Submit an algorithm on 3rd view
162  CHECK_STATUS(vpiSubmitImageFlip(streams[2], VPI_BACKEND_CUDA, inViews[2], outViews[2], VPI_FLIP_VERT));
163 
164  // Submit an algorithm on 4th view
165  CHECK_STATUS(vpiSubmitImageFlip(streams[3], VPI_BACKEND_CUDA, inViews[3], outViews[3], VPI_FLIP_BOTH));
166 
167  // Synchronize all streams to make sure all views finished processing
168  CHECK_STATUS(vpiStreamSync(streams[0]));
169  CHECK_STATUS(vpiStreamSync(streams[1]));
170  CHECK_STATUS(vpiStreamSync(streams[2]));
171  CHECK_STATUS(vpiStreamSync(streams[3]));
172 
173  // Retrieve the output parent image contents and write it to disk
174 
175  // Lock output parent image to retrieve its data, checking it consists of
176  // host-accessible memory buffers in pitch-linear layout
177  VPIImageData outData;
178  CHECK_STATUS(vpiImageLockData(outParent, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &outData));
180 
181  cv::Mat cvOut;
182  vpiImageDataExportOpenCVMat(outData, &cvOut);
183  imwrite("output_views.png", cvOut);
184 
185  // Done handling output parent image, don't forget to unlock it
186  CHECK_STATUS(vpiImageUnlock(outParent));
187  }
188  catch (std::exception &e)
189  {
190  std::cerr << e.what() << std::endl;
191  retval = 1;
192  }
193 
194  // Clean up
195 
196  // Make sure all streams are synchronized before destroying the objects possibly in use
197  for (int i = 0; i < 4; ++i)
198  {
199  if (streams[i] != NULL)
200  {
201  vpiStreamSync(streams[i]);
202  }
203  }
204 
205  // Make sure to destroy all views before destroying their parents
206  for (int i = 0; i < 4; ++i)
207  {
208  vpiStreamDestroy(streams[i]);
209 
210  vpiImageDestroy(inViews[i]);
211  vpiImageDestroy(outViews[i]);
212  }
213 
214  vpiEventDestroy(parentEvent);
215 
216  vpiImageDestroy(imageBGR);
217  vpiImageDestroy(inParent);
218  vpiImageDestroy(outParent);
219 
220  return retval;
221 }
Declares functions that implement the Bilateral Filter algorithm.
Declares functions that handle image format conversion.
Functions and structures for dealing with VPI events.
Declares functions that implement Image flip algorithms.
#define VPI_IMAGE_FORMAT_U8
Single plane with one 8-bit unsigned integer channel.
Definition: ImageFormat.h:100
Functions and structures for dealing with VPI images.
Functions for handling OpenCV interoperability with VPI.
Declaration of VPI status codes handling functions.
Declares functions dealing with VPI streams.
@ VPI_FLIP_HORIZ
Flip horizontally.
Definition: Types.h:689
@ VPI_FLIP_VERT
Flip vertically.
Definition: Types.h:692
@ VPI_FLIP_BOTH
Flip horizontally and vertically.
Definition: Types.h:697
VPIStatus vpiSubmitBilateralFilter(VPIStream stream, uint64_t backend, VPIImage input, VPIImage output, int32_t kernelSize, float sigmaRange, float sigmaSpace, VPIBorderExtension border)
Runs a 2D bilateral filter over an image.
VPIStatus vpiSubmitConvertImageFormat(VPIStream stream, uint64_t backend, VPIImage input, VPIImage output, const VPIConvertImageFormatParams *params)
Converts the image contents to the desired format, with optional scaling and offset.
struct VPIEventImpl * VPIEvent
A handle to an event.
Definition: Types.h:244
VPIStatus vpiEventRecord(VPIEvent event, VPIStream stream)
Captures in the event the contents of the stream command queue at the time of this call.
VPIStatus vpiEventCreate(uint64_t flags, VPIEvent *event)
Create an event instance.
void vpiEventDestroy(VPIEvent event)
Destroy an event instance as well as all resources it owns.
VPIStatus vpiSubmitImageFlip(VPIStream stream, uint64_t backend, VPIImage input, VPIImage output, VPIFlipMode flipMode)
Flips a 2D image either horizontally, vertically or both.
VPIImageBufferType bufferType
Type of image buffer.
Definition: Image.h:238
void vpiImageDestroy(VPIImage img)
Destroy an image instance.
struct VPIImageImpl * VPIImage
A handle to an image.
Definition: Types.h:256
VPIStatus vpiImageCreateView(VPIImage imgParent, const VPIRectangleI *clipBounds, uint64_t flags, VPIImage *imgView)
Create an image that wraps an axis-aligned rectangular sub-region of an existing image.
VPIStatus vpiImageLockData(VPIImage img, VPILockMode mode, VPIImageBufferType bufType, VPIImageData *data)
Acquires the lock on an image object and returns the image contents.
VPIStatus vpiImageCreate(int32_t width, int32_t height, VPIImageFormat fmt, uint64_t flags, VPIImage *img)
Create an empty image instance with the specified flags.
VPIStatus vpiImageUnlock(VPIImage img)
Releases the lock on an image object.
@ VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR
Host-accessible with planes in pitch-linear memory layout.
Definition: Image.h:172
Stores information about image characteristics and content.
Definition: Image.h:234
VPIStatus vpiImageCreateWrapperOpenCVMat(const cv::Mat &mat, VPIImageFormat fmt, uint64_t flags, VPIImage *img)
Wraps a cv::Mat in an VPIImage with the given image format.
VPIStatus vpiImageDataExportOpenCVMat(const VPIImageData &imgData, cv::Mat *mat)
Fills an existing cv::Mat with data from VPIImageData coming from a locked VPIImage.
struct VPIStreamImpl * VPIStream
A handle to a stream.
Definition: Types.h:250
VPIStatus vpiStreamWaitEvent(VPIStream stream, VPIEvent event)
Pushes a command that blocks the processing of all future commands submitted to the stream until the ...
VPIStatus vpiStreamSync(VPIStream stream)
Blocks the calling thread until all submitted commands in this stream queue are done (queue is empty)...
void vpiStreamDestroy(VPIStream stream)
Destroy a stream instance and deallocate all HW resources.
VPIStatus vpiStreamCreate(uint64_t flags, VPIStream *stream)
Create a stream instance.
@ VPI_BACKEND_CUDA
CUDA backend.
Definition: Types.h:93
@ 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:278
@ VPI_LOCK_READ
Lock memory only for reading.
Definition: Types.h:595
Stores the geometric information of a rectangle.
Definition: Types.h:566