VPI - Vision Programming Interface

3.2 版本

透视扭曲

概述

透视扭曲 示例应用程序接受一个输入视频,并输出一个视频,其中每一帧都应用了不同的透视扭曲。 结果是透视弹跳效果。 可以修改示例应用程序以从相机获取输入并实时应用效果。

说明

命令行参数为

<backend> <input video>

其中

  • backend: 可以是 cpucudavic (仅在 Jetson 设备上)。 它定义了将执行处理的后端。
  • input video: 应用效果的视频文件;它接受 .mp4、.avi 以及可能取决于 OpenCV 支持的其他格式。

VPI 示例安装程序包含一些可以用作输入的示例视频。 它们位于 /opt/nvidia/vpi3/samples/assets/ 目录中。

这是一个调用示例

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

应用程序将处理 noisy.mp4 并创建 perspwarp_cuda.mp4,其中时间变化的透视扭曲应用于输入帧。

结果

输入视频透视效果

源代码

为了方便起见,这里提供了也安装在示例目录中的代码。

语言
27 import sys
28 import vpi
29 import numpy as np
30 from math import sin, cos, pi
31 from argparse import ArgumentParser
32 import cv2
33 
34 
35 # ----------------------------
36 # Parse command line arguments
37 
38 parser = ArgumentParser()
39 parser.add_argument('backend', choices=['cpu', 'cuda','vic'],
40  help='Backend to be used for processing')
41 
42 parser.add_argument('input',
43  help='Input video to be denoised')
44 
45 args = parser.parse_args();
46 
47 if args.backend == 'cuda'
48  backend = vpi.Backend.CUDA
49 elif args.backend == 'cpu'
50  backend = vpi.Backend.CPU
51 else
52  assert args.backend == 'vic'
53  backend = vpi.Backend.VIC
54 
55 # -----------------------------
56 # Open input and output videos
57 
58 inVideo = cv2.VideoCapture(args.input)
59 
60 fourcc = cv2.VideoWriter_fourcc(*'MPEG')
61 inSize = (int(inVideo.get(cv2.CAP_PROP_FRAME_WIDTH)), int(inVideo.get(cv2.CAP_PROP_FRAME_HEIGHT)))
62 fps = inVideo.get(cv2.CAP_PROP_FPS)
63 
64 outVideo = cv2.VideoWriter('perspwarp_python'+str(sys.version_info[0])+'_'+args.backend+'.mp4',
65  fourcc, fps, inSize)
66 
67 #--------------------------------------------------------------
68 # Main processing loop
69 curFrame = 1
70 while True
71  print("Frame: {}".format(curFrame))
72  curFrame+=1
73 
74  # Read one input frame
75  ret, cvFrame = inVideo.read()
76  if not ret
77  break
78 
79  # Convert it to NV12_ER format to be used by VPI
80  with vpi.Backend.CUDA
81  frame = vpi.asimage(cvFrame).convert(vpi.Format.NV12_ER)
82 
83  # Calculate the transformation to be applied ------------
84 
85  # Move image's center to origin of coordinate system
86  T1 = np.array([[1, 0, -frame.width/2.0],
87  [0, 1, -frame.height/2.0],
88  [0, 0, 1]])
89 
90  # Apply some time-dependent perspective transform
91  v1 = sin(curFrame/30.0*2*pi/2)*0.0005
92  v2 = cos(curFrame/30.0*2*pi/3)*0.0005
93  P = np.array([[0.66, 0, 0],
94  [0, 0.66, 0],
95  [v1, v2, 1]])
96 
97  # Move image's center back to where it was
98  T2 = np.array([[1, 0, frame.width/2.0],
99  [0, 1, frame.height/2.0],
100  [0, 0, 1]])
101 
102  # Do perspective warp using the backend passed in the command line.
103  with backend
104  frame = frame.perspwarp(np.matmul(T2, np.matmul(P, T1)))
105 
106  # Convert it to RGB8 for output using the CUDA backend
107  with vpi.Backend.CUDA
108  frame = frame.convert(vpi.Format.RGB8)
109 
110  # Write the denoised frame to the output video
111  with frame.rlock_cpu() as data
112  outVideo.write(data)
29 #include <opencv2/core/version.hpp>
30 #include <opencv2/imgcodecs.hpp>
31 #include <opencv2/imgproc/imgproc.hpp>
32 #include <opencv2/videoio.hpp>
33 #include <vpi/OpenCVInterop.hpp>
34 
35 #include <vpi/Image.h>
36 #include <vpi/Status.h>
37 #include <vpi/Stream.h>
40 
41 #include <algorithm>
42 #include <cstring> // for memset
43 #include <fstream>
44 #include <iostream>
45 #include <random>
46 #include <sstream>
47 
48 #define CHECK_STATUS(STMT) \
49  do \
50  { \
51  VPIStatus status = (STMT); \
52  if (status != VPI_SUCCESS) \
53  { \
54  char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \
55  vpiGetLastStatusMessage(buffer, sizeof(buffer)); \
56  std::ostringstream ss; \
57  ss << vpiStatusGetName(status) << ": " << buffer; \
58  throw std::runtime_error(ss.str()); \
59  } \
60  } while (0);
61 
62 static void MatrixMultiply(VPIPerspectiveTransform &r, const VPIPerspectiveTransform &a,
63  const VPIPerspectiveTransform &b)
64 {
65  for (int i = 0; i < 3; ++i)
66  {
67  for (int j = 0; j < 3; ++j)
68  {
69  r[i][j] = a[i][0] * b[0][j];
70  for (int k = 1; k < 3; ++k)
71  {
72  r[i][j] += a[i][k] * b[k][j];
73  }
74  }
75  }
76 }
77 
78 int main(int argc, char *argv[])
79 {
80  // OpenCV image that will be wrapped by a VPIImage.
81  // Define it here so that it's destroyed *after* wrapper is destroyed
82  cv::Mat cvFrame;
83 
84  // VPI objects that will be used
85  VPIStream stream = NULL;
86  VPIImage imgInput = NULL, imgOutput = NULL;
87  VPIImage frameBGR = NULL;
88 
89  int retval = 0;
90 
91  try
92  {
93  // =============================
94  // Parse command line parameters
95 
96  if (argc != 3)
97  {
98  throw std::runtime_error(std::string("Usage: ") + argv[0] + " <cpu|vic|cuda> <input_video>");
99  }
100 
101  std::string strBackend = argv[1];
102  std::string strInputVideo = argv[2];
103 
104  // Now parse the backend
105  VPIBackend backend;
106 
107  if (strBackend == "cpu")
108  {
109  backend = VPI_BACKEND_CPU;
110  }
111  else if (strBackend == "cuda")
112  {
113  backend = VPI_BACKEND_CUDA;
114  }
115  else if (strBackend == "vic")
116  {
117  backend = VPI_BACKEND_VIC;
118  }
119  else
120  {
121  throw std::runtime_error("Backend '" + strBackend +
122  "' not recognized, it must be either cpu, cuda or vic.");
123  }
124 
125  // ===============================
126  // Prepare input and output videos
127 
128  // Load the input video
129  cv::VideoCapture invid;
130  if (!invid.open(strInputVideo))
131  {
132  throw std::runtime_error("Can't open '" + strInputVideo + "'");
133  }
134 
135  // Open the output video for writing using input's characteristics
136  int w = invid.get(cv::CAP_PROP_FRAME_WIDTH);
137  int h = invid.get(cv::CAP_PROP_FRAME_HEIGHT);
138  int fourcc = cv::VideoWriter::fourcc('M', 'P', 'E', 'G');
139  double fps = invid.get(cv::CAP_PROP_FPS);
140 
141  cv::VideoWriter outVideo("perspwarp_" + strBackend + ".mp4", fourcc, fps, cv::Size(w, h));
142  if (!outVideo.isOpened())
143  {
144  throw std::runtime_error("Can't create output video");
145  }
146 
147  // =================================
148  // Allocate all VPI resources needed
149 
150  // Create the stream for the given backend. We'll be using CUDA for image format conversion.
151  CHECK_STATUS(vpiStreamCreate(backend | VPI_BACKEND_CUDA, &stream));
152 
153  CHECK_STATUS(vpiImageCreate(w, h, VPI_IMAGE_FORMAT_NV12_ER, 0, &imgInput));
154  CHECK_STATUS(vpiImageCreate(w, h, VPI_IMAGE_FORMAT_NV12_ER, 0, &imgOutput));
155 
157  memset(&xform, 0, sizeof(xform));
158 
159  // ====================
160  // Main processing loop
161 
162  int curFrame = 1;
163  while (invid.read(cvFrame))
164  {
165  printf("Frame: %d\n", curFrame++);
166 
167  if (frameBGR == NULL)
168  {
169  // Ceate a VPIImage that wraps the frame
170  CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvFrame, 0, &frameBGR));
171  }
172  else
173  {
174  // reuse existing VPIImage wrapper to wrap the new frame.
175  CHECK_STATUS(vpiImageSetWrappedOpenCVMat(frameBGR, cvFrame));
176  }
177 
178  // First convert it to NV12 using CUDA
179  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, frameBGR, imgInput, NULL));
180 
181  // move image's center to origin of coordinate system
182  VPIPerspectiveTransform t1 = {{1, 0, -w / 2.0f}, {0, 1, -h / 2.0f}, {0, 0, 1}};
183 
184  // Apply some time-dependent perspective transform
185  float v1 = sin(curFrame / 30.0 * 2 * M_PI / 2) * 0.0005f;
186  float v2 = cos(curFrame / 30.0 * 2 * M_PI / 3) * 0.0005f;
187  VPIPerspectiveTransform P = {{0.66, 0, 0}, {0, 0.66, 0}, {v1, v2, 1}};
188 
189  // move image's center back to where it was.
190  VPIPerspectiveTransform t2 = {{1, 0, w / 2.0f}, {0, 1, h / 2.0f}, {0, 0, 1}};
191 
192  // Apply the transforms defined above.
194  MatrixMultiply(tmp, P, t1);
195  MatrixMultiply(xform, t2, tmp);
196 
197  // Do perspective warp using the backend passed in the command line.
198  // Passing NULL as grid to make it use a dense grid, for better quality.
199  CHECK_STATUS(vpiSubmitPerspectiveWarp(stream, backend, imgInput, xform, imgOutput, NULL, VPI_INTERP_LINEAR,
200  VPI_BORDER_ZERO, 0));
201 
202  // Convert output back to BGR using CUDA
203  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, imgOutput, frameBGR, NULL));
204  CHECK_STATUS(vpiStreamSync(stream));
205 
206  // Now add it to the output video stream
207  VPIImageData imgdata;
208  CHECK_STATUS(vpiImageLockData(frameBGR, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &imgdata));
209 
210  cv::Mat outFrame;
211  CHECK_STATUS(vpiImageDataExportOpenCVMat(imgdata, &outFrame));
212  outVideo << outFrame;
213 
214  CHECK_STATUS(vpiImageUnlock(frameBGR));
215  }
216  }
217  catch (std::exception &e)
218  {
219  std::cerr << e.what() << std::endl;
220  retval = 1;
221  }
222 
223  // =========================
224  // Destroy all VPI resources
225 
226  vpiStreamDestroy(stream);
227  vpiImageDestroy(imgInput);
228  vpiImageDestroy(imgOutput);
229  vpiImageDestroy(frameBGR);
230 
231  return retval;
232 }
声明处理图像格式转换的函数。
#define VPI_IMAGE_FORMAT_NV12_ER
YUV420sp 8 位 pitch-linear 格式,具有全范围。
用于处理 VPI 图像的函数和结构。
用于处理 OpenCV 与 VPI 互操作性的函数。
声明实现透视扭曲算法的函数。
VPI 状态代码处理函数的声明。
声明处理 VPI 流的函数。
VPIStatus vpiSubmitConvertImageFormat(VPIStream stream, uint64_t backend, VPIImage input, VPIImage output, const VPIConvertImageFormatParams *params)
将图像内容转换为所需的格式,具有可选的缩放和偏移。
void vpiImageDestroy(VPIImage img)
销毁图像实例。
struct VPIImageImpl * VPIImage
图像的句柄。
定义: Types.h:256
VPIStatus vpiImageLockData(VPIImage img, VPILockMode mode, VPIImageBufferType bufType, VPIImageData *data)
获取图像对象的锁并返回图像内容。
VPIStatus vpiImageCreate(int32_t width, int32_t height, VPIImageFormat fmt, uint64_t flags, VPIImage *img)
使用指定的标志创建空的图像实例。
VPIStatus vpiImageUnlock(VPIImage img)
释放图像对象上的锁。
@ VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR
主机可访问,平面采用 pitch-linear 内存布局。
定义: Image.h:172
存储有关图像特征和内容的信息。
定义: Image.h:234
VPIStatus vpiImageCreateWrapperOpenCVMat(const cv::Mat &mat, VPIImageFormat fmt, uint64_t flags, VPIImage *img)
使用给定的图像格式将 cv::Mat 封装在 VPIImage 中。
VPIStatus vpiImageDataExportOpenCVMat(const VPIImageData &imgData, cv::Mat *mat)
使用来自锁定的 VPIImage 的 VPIImageData 中的数据填充现有的 cv::Mat。
VPIStatus vpiImageSetWrappedOpenCVMat(VPIImage img, const cv::Mat &mat)
重新定义现有 VPIImage 封装器的被封装 cv::Mat。
VPIStatus vpiSubmitPerspectiveWarp(VPIStream stream, uint64_t backend, VPIImage input, const VPIPerspectiveTransform xform, VPIImage output, const VPIWarpGrid *grid, VPIInterpolationType interp, VPIBorderExtension border, uint64_t flags)
向流提交透视扭曲操作。
float VPIPerspectiveTransform[3][3]
表示 2D 透视变换。
定义: Types.h:689
struct VPIStreamImpl * VPIStream
流的句柄。
定义: Types.h:250
VPIStatus vpiStreamSync(VPIStream stream)
阻塞调用线程,直到此流队列中的所有提交命令完成(队列为空)...
VPIBackend
VPI 后端类型。
定义: Types.h:91
void vpiStreamDestroy(VPIStream stream)
销毁流实例并释放所有硬件资源。
VPIStatus vpiStreamCreate(uint64_t flags, VPIStream *stream)
创建流实例。
@ VPI_BACKEND_CUDA
CUDA 后端。
定义: Types.h:93
@ VPI_BACKEND_VIC
VIC 后端。
定义: Types.h:95
@ VPI_BACKEND_CPU
CPU 后端。
定义: Types.h:92
@ VPI_BORDER_ZERO
图像外部的所有像素都被视为零。
定义: Types.h:278
@ VPI_INTERP_LINEAR
线性插值。
@ VPI_LOCK_READ
仅为读取锁定内存。
定义: Types.h:617