VPI - Vision Programming Interface

0.4.4 Release

FFT

Overview

The FFT application outputs a spectrum representation of an input image, saving it into spectrum.png. The user can define what backend will be used for processing.

This sample shows the following:

  • Creating and destroying a VPI stream.
  • Wrapping an image hosted on CPU (the input) to be used by VPI.
  • Convert an image to another format
  • Creating a VPI-managed 2D image where output will be written to.
  • Create a FFT algorithm and call it.
  • Simple stream synchronization.
  • Image locking to access its contents from CPU side.
  • Interpret and process the resulting spectrum information.
  • Error handling.
  • Environment clean up.

Instructions

The usage is:

./vpi_sample_07_fft <backend> <input image>

where

  • backend: either cpu or cuda; it defines the backend that will perform the processing.
  • input image: input image file name; it accepts png, jpeg and possibly others.
Note
Currently FFT isn't available on PVA backend.

Here's one example:

./vpi_sample_07_fft cpu ../assets/kodim8.png

This is using the CUDA backend and one of the provided sample images. You can try with other images, respecting the constraints imposed by the algorithm.

Results

Input imageOutput image, spectrum

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 #else
33 # include <opencv2/highgui/highgui.hpp>
34 #endif
35 
36 #include <vpi/Image.h>
37 #include <vpi/Status.h>
38 #include <vpi/Stream.h>
40 #include <vpi/algo/FFT.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 // Auxiliary functions to process spectrum before saving it to disk.
61 cv::Mat LogMagnitude(cv::Mat cpx);
62 cv::Mat CompleteFullHermitian(cv::Mat in, cv::Size fullSize);
63 cv::Mat InplaceFFTShift(cv::Mat mag);
64 
65 int main(int argc, char *argv[])
66 {
67  VPIImage image = NULL;
68  VPIImage imageF32 = NULL;
69  VPIImage spectrum = NULL;
70  VPIStream stream = NULL;
71  VPIPayload fft = NULL;
72 
73  int retval = 0;
74 
75  try
76  {
77  if (argc != 3)
78  {
79  throw std::runtime_error(std::string("Usage: ") + argv[0] + " <cpu|cuda> <input image>");
80  }
81 
82  std::string strBackend = argv[1];
83  std::string strInputFileName = argv[2];
84 
85  // Load the input image
86  cv::Mat cvImage = cv::imread(strInputFileName, cv::IMREAD_GRAYSCALE);
87  if (cvImage.empty())
88  {
89  throw std::runtime_error("Can't open '" + strInputFileName + "'");
90  }
91 
92  assert(cvImage.type() == CV_8UC1);
93 
94  // Now parse the backend
95  VPIBackend backendType;
96 
97  if (strBackend == "cpu")
98  {
99  backendType = VPI_BACKEND_CPU;
100  }
101  else if (strBackend == "cuda")
102  {
103  backendType = VPI_BACKEND_CUDA;
104  }
105  else
106  {
107  throw std::runtime_error("Backend '" + strBackend + "' not recognized, it must be either cpu or cuda.");
108  }
109 
110  // Create the stream for the given backend.
111  CHECK_STATUS(vpiStreamCreate(backendType, &stream));
112 
113  // We now wrap the loaded image into a VPIImage object to be used by VPI.
114  {
115  // First fill VPIImageData with the, well, image data...
116  VPIImageData imgData;
117  memset(&imgData, 0, sizeof(imgData));
118  imgData.type = VPI_IMAGE_FORMAT_U8;
119  imgData.numPlanes = 1;
120  imgData.planes[0].width = cvImage.cols;
121  imgData.planes[0].height = cvImage.rows;
122  imgData.planes[0].pitchBytes = cvImage.step[0];
123  imgData.planes[0].data = cvImage.data;
124 
125  // Wrap it into a VPIImage. VPI won't make a copy of it, so the original
126  // image must be in scope at all times.
127  CHECK_STATUS(vpiImageCreateHostMemWrapper(&imgData, 0, &image));
128  }
129 
130  // Temporary image that holds the float version of input
131  CHECK_STATUS(vpiImageCreate(cvImage.cols, cvImage.rows, VPI_IMAGE_FORMAT_F32, 0, &imageF32));
132 
133  // Now create the output image. Note that for real inputs, the output spectrum is a Hermitian
134  // matrix (conjugate-symmetric), so only the non-redundant components are output, basically the
135  // left half. We adjust the output width accordingly.
136  CHECK_STATUS(vpiImageCreate(cvImage.cols / 2 + 1, cvImage.rows, VPI_IMAGE_FORMAT_2F32, 0, &spectrum));
137 
138  // Create the FFT payload that does real (space) to complex (frequency) transformation
139  CHECK_STATUS(
140  vpiCreateFFT(backendType, cvImage.cols, cvImage.rows, VPI_IMAGE_FORMAT_F32, VPI_IMAGE_FORMAT_2F32, &fft));
141 
142  // Now our processing pipeline:
143 
144  // Convert image to float
145  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, backendType, image, imageF32, VPI_CONVERSION_CAST, 1, 0));
146 
147  // Submit it for processing passing the image to be gradient and the result image
148  CHECK_STATUS(vpiSubmitFFT(stream, fft, imageF32, spectrum, 0));
149 
150  // Wait until the algorithm finishes processing
151  CHECK_STATUS(vpiStreamSync(stream));
152 
153  // Now let's retrieve the result spectrum
154  {
155  // Lock output image to retrieve its data on cpu memory
156  VPIImageData outData;
157  CHECK_STATUS(vpiImageLock(spectrum, VPI_LOCK_READ, &outData));
158 
159  assert(outData.type == VPI_IMAGE_FORMAT_2F32);
160 
161  // Wrap spectrum to be used by OpenCV
162  cv::Mat cvSpectrum(outData.planes[0].height, outData.planes[0].width, CV_32FC2, outData.planes[0].data,
163  outData.planes[0].pitchBytes);
164 
165  // Process it
166  cv::Mat mag = InplaceFFTShift(LogMagnitude(CompleteFullHermitian(cvSpectrum, cvImage.size())));
167 
168  // Normalize the result to fit in 8-bits
169  normalize(mag, mag, 0, 255, cv::NORM_MINMAX);
170 
171  // Write to disk
172  imwrite("spectrum_" + strBackend + ".png", mag);
173 
174  // Done handling output image, don't forget to unlock it.
175  CHECK_STATUS(vpiImageUnlock(spectrum));
176  }
177  }
178  catch (std::exception &e)
179  {
180  std::cerr << e.what() << std::endl;
181  retval = 1;
182  }
183 
184  // Clean up
185 
186  // Make sure stream is synchronized before destroying the objects
187  // that might still be in use.
188  if (stream != NULL)
189  {
190  vpiStreamSync(stream);
191  }
192 
193  vpiImageDestroy(image);
194  vpiImageDestroy(imageF32);
195  vpiImageDestroy(spectrum);
196  vpiStreamDestroy(stream);
197 
198  // Payload is owned by the stream, so it's already destroyed
199  // since the stream is now destroyed.
200 
201  return retval;
202 }
203 
204 // Auxiliary functions --------------------------------
205 
206 cv::Mat LogMagnitude(cv::Mat cpx)
207 {
208  // Split spectrum into real and imaginary parts
209  cv::Mat reim[2];
210  assert(cpx.channels() == 2);
211  split(cpx, reim);
212 
213  // Calculate the magnitude
214  cv::Mat mag;
215  magnitude(reim[0], reim[1], mag);
216 
217  // Convert to logarithm scale
218  mag += cv::Scalar::all(1);
219  log(mag, mag);
220  mag = mag(cv::Rect(0, 0, mag.cols & -2, mag.rows & -2));
221 
222  return mag;
223 }
224 
225 cv::Mat CompleteFullHermitian(cv::Mat in, cv::Size fullSize)
226 {
227  assert(in.type() == CV_32FC2);
228 
229  cv::Mat out(fullSize, CV_32FC2);
230  for (int i = 0; i < out.rows; ++i)
231  {
232  for (int j = 0; j < out.cols; ++j)
233  {
234  cv::Vec2f p;
235  if (j < in.cols)
236  {
237  p = in.at<cv::Vec2f>(i, j);
238  }
239  else
240  {
241  p = in.at<cv::Vec2f>((out.rows - i) % out.rows, (out.cols - j) % out.cols);
242  p[1] = -p[1];
243  }
244  out.at<cv::Vec2f>(i, j) = p;
245  }
246  }
247 
248  return out;
249 }
250 
251 cv::Mat InplaceFFTShift(cv::Mat mag)
252 {
253  // Rearrange the quadrants of the fourier spectrum
254  // so that the origin is at the image center.
255 
256  // Create a ROI for each 4 quadrants.
257  int cx = mag.cols / 2;
258  int cy = mag.rows / 2;
259  cv::Mat qTL(mag, cv::Rect(0, 0, cx, cy)); // top-left
260  cv::Mat qTR(mag, cv::Rect(cx, 0, cx, cy)); // top-right
261  cv::Mat qBL(mag, cv::Rect(0, cy, cx, cy)); // bottom-left
262  cv::Mat qBR(mag, cv::Rect(cx, cy, cx, cy)); // bottom-right
263 
264  // swap top-left with bottom-right quadrants
265  cv::Mat tmp;
266  qTL.copyTo(tmp);
267  qBR.copyTo(qTL);
268  tmp.copyTo(qBR);
269 
270  // swap top-right with bottom-left quadrants
271  qTR.copyTo(tmp);
272  qBL.copyTo(qTR);
273  tmp.copyTo(qBL);
274 
275  return mag;
276 }
VPIImagePlane::height
uint32_t height
Height of this plane in pixels.
Definition: Image.h:138
VPIImagePlane::width
uint32_t width
Width of this plane in pixels.
Definition: Image.h:137
vpiStreamCreate
VPIStatus vpiStreamCreate(uint32_t flags, VPIStream *stream)
Create a stream instance.
VPIBackend
VPIBackend
VPI Backend types.
Definition: Types.h:89
VPI_LOCK_READ
@ VPI_LOCK_READ
Lock memory only for reading.
Definition: Types.h:447
vpiImageUnlock
VPIStatus vpiImageUnlock(VPIImage img)
Releases the lock on an image object.
FFT.h
Declares functions that implement the Fast Fourier Transform algorithm and its inverse.
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
vpiStreamDestroy
void vpiStreamDestroy(VPIStream stream)
Destroy a stream instance and deallocate all HW resources.
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.
vpiImageDestroy
void vpiImageDestroy(VPIImage img)
Destroy an image instance.
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
VPI_IMAGE_FORMAT_2F32
@ VPI_IMAGE_FORMAT_2F32
Single plane with two interleaved 32-bit floating point channels.
Definition: ImageFormat.h:117
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
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.
vpiSubmitFFT
VPIStatus vpiSubmitFFT(VPIStream stream, VPIPayload payload, VPIImage input, VPIImage output, uint32_t flags)
Runs the direct Fast Fourier Transform on single image.
VPI_IMAGE_FORMAT_F32
@ VPI_IMAGE_FORMAT_F32
Single plane with one 32-bit floating point channel.
Definition: ImageFormat.h:111
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.
VPIImagePlane::data
void * data
Pointer to the first row of this plane.
Definition: Image.h:147
vpiCreateFFT
VPIStatus vpiCreateFFT(VPIBackend backend, uint32_t inputWidth, uint32_t inputHeight, const VPIImageFormat inFormat, const VPIImageFormat outFormat, VPIPayload *payload)
Creates payload for direct Fast Fourier Transform algorithm.