VPI - Vision Programming Interface

3.2 版本

时域噪声消除

概述

时域噪声消除 示例应用程序接受一个带有噪声的输入视频,降低其噪声并将结果写入磁盘。您可以定义将用于处理的后端。

说明

命令行参数为

<后端> <输入视频>

其中

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

VPI 示例安装程序包含一个人工添加噪声的示例视频,位于 /opt/nvidia/vpi3/samples/assets/noisy.mp4

这是一个调用示例

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

应用程序将处理 noisy.mp4 并创建 denoised_cuda.mp4,其中包含输入视频的降噪版本。

结果

输入视频降噪视频

源代码

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

语言
27 import sys
28 import vpi
29 import numpy as np
30 from argparse import ArgumentParser
31 import cv2
32 
33 # ----------------------------
34 # 解析命令行参数
35 
36 parser = ArgumentParser()
37 parser.add_argument('backend', choices=['cuda','vic'],
38  help='用于处理的后端')
39 
40 parser.add_argument('input',
41  help='要降噪的输入视频')
42 
43 args = parser.parse_args();
44 
45 if args.backend == 'cuda'
46  backend = vpi.Backend.CUDA
47 else
48  assert args.backend == 'vic'
49  backend = vpi.Backend.VIC
50 
51 # -----------------------------
52 # 打开输入和输出视频
53 
54 inVideo = cv2.VideoCapture(args.input)
55 
56 fourcc = cv2.VideoWriter_fourcc(*'MPEG')
57 inSize = (int(inVideo.get(cv2.CAP_PROP_FRAME_WIDTH)), int(inVideo.get(cv2.CAP_PROP_FRAME_HEIGHT)))
58 fps = inVideo.get(cv2.CAP_PROP_FPS)
59 
60 outVideo = cv2.VideoWriter('denoised_python'+str(sys.version_info[0])+'_'+args.backend+'.mp4',
61  fourcc, fps, inSize)
62 
63 #--------------------------------------------------------------
64 # 使用用户指定的后端创建 TNR 对象
65 with backend
66  tnr = vpi.TemporalNoiseReduction(inSize, vpi.Format.NV12_ER)
67 
68 #--------------------------------------------------------------
69 # 主处理循环
70 curFrame = 0
71 while True
72  curFrame+=1
73  print("帧: {}".format(curFrame))
74 
75  # 读取一个输入帧
76  ret, cvFrame = inVideo.read()
77  if not ret
78  break
79 
80  # 将其转换为 VPI 使用的 NV12_ER 格式
81  with vpi.Backend.CUDA
82  frame = vpi.asimage(cvFrame).convert(vpi.Format.NV12_ER)
83 
84  # 检索相应的降噪帧
85  denoised = tnr(frame, preset=vpi.TNRPreset.INDOOR_MEDIUM_LIGHT, strength=1)
86 
87  # 使用 CUDA 后端将其转换为 RGB8 以进行输出
88  with vpi.Backend.CUDA
89  denoised = denoised.convert(vpi.Format.RGB8)
90 
91  # 将降噪帧写入输出视频
92  with denoised.rlock_cpu() as data
93  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 <map>
46 #include <sstream>
47 #include <vector>
48 
49 #define CHECK_STATUS(STMT) \
50  do \
51  { \
52  VPIStatus status = (STMT); \
53  if (status != VPI_SUCCESS) \
54  { \
55  char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH]; \
56  vpiGetLastStatusMessage(buffer, sizeof(buffer)); \
57  std::ostringstream ss; \
58  ss << vpiStatusGetName(status) << ": " << buffer; \
59  throw std::runtime_error(ss.str()); \
60  } \
61  } while (0);
62 
63 int main(int argc, char *argv[])
64 {
65  // OpenCV 图像,将被 VPIImage 包裹。
66  // 在此处定义它,以便在包装器销毁*后*销毁它
67  cv::Mat cvFrame;
68 
69  // 在此处声明我们将需要的所有 VPI 对象,以便我们
70  // 可以在最后销毁它们。
71  VPIStream stream = NULL;
72  VPIImage imgPrevious = NULL, imgCurrent = NULL, imgOutput = NULL;
73  VPIImage frameBGR = NULL;
74  VPIPayload tnr = NULL;
75 
76  // 主要返回值
77  int retval = 0;
78 
79  try
80  {
81  // =============================
82  // 解析命令行参数
83 
84  if (argc != 3)
85  {
86  throw std::runtime_error(std::string("用法: ") + argv[0] + " <vic|cuda> <input_video>");
87  }
88 
89  std::string strBackend = argv[1];
90  std::string strInputVideo = argv[2];
91 
92  // 现在解析后端
93  VPIBackend backend;
94 
95  if (strBackend == "cuda")
96  {
97  backend = VPI_BACKEND_CUDA;
98  }
99  else if (strBackend == "vic")
100  {
101  backend = VPI_BACKEND_VIC;
102  }
103  else
104  {
105  throw std::runtime_error("后端 '" + strBackend + "' 无法识别,它必须是 cuda 或 vic。");
106  }
107 
108  // ===============================
109  // 准备输入和输出视频
110 
111  // 加载输入视频
112  cv::VideoCapture invid;
113  if (!invid.open(strInputVideo))
114  {
115  throw std::runtime_error("无法打开 '" + strInputVideo + "'");
116  }
117 
118  // 使用输入的特性打开输出视频以进行写入
119  int w = invid.get(cv::CAP_PROP_FRAME_WIDTH);
120  int h = invid.get(cv::CAP_PROP_FRAME_HEIGHT);
121  int fourcc = cv::VideoWriter::fourcc('M', 'P', 'E', 'G');
122  double fps = invid.get(cv::CAP_PROP_FPS);
123 
124  // 创建输出视频
125  cv::VideoWriter outVideo("denoised_" + strBackend + ".mp4", fourcc, fps, cv::Size(w, h));
126  if (!outVideo.isOpened())
127  {
128  throw std::runtime_error("无法创建输出视频");
129  }
130 
131  // =================================
132  // 分配所有需要的 VPI 资源
133 
134  // 我们将使用传递的后端运行重映射算法和 CUDA 来进行图像格式
135  // 转换,因此我们必须强制启用 CUDA 后端,以及
136  // 所需的后端。
137  CHECK_STATUS(vpiStreamCreate(VPI_BACKEND_CUDA | backend, &stream));
138 
139  CHECK_STATUS(vpiImageCreate(w, h, VPI_IMAGE_FORMAT_NV12_ER, 0, &imgPrevious));
140  CHECK_STATUS(vpiImageCreate(w, h, VPI_IMAGE_FORMAT_NV12_ER, 0, &imgCurrent));
141  CHECK_STATUS(vpiImageCreate(w, h, VPI_IMAGE_FORMAT_NV12_ER, 0, &imgOutput));
142 
143  // 创建配置为在室内中等光线下处理 NV12_ER
144  // 帧的时域噪声消除负载
145  CHECK_STATUS(vpiCreateTemporalNoiseReduction(backend, w, h, VPI_IMAGE_FORMAT_NV12_ER, VPI_TNR_DEFAULT, &tnr));
146 
147  // ====================
148  // 主处理循环
149 
150  int curFrame = 0;
151  while (invid.read(cvFrame))
152  {
153  printf("帧: %d\n", ++curFrame);
154 
155  // frameBGR 尚未分配?
156  if (frameBGR == NULL)
157  {
158  // 创建一个包裹帧的 VPIImage
159  CHECK_STATUS(vpiImageCreateWrapperOpenCVMat(cvFrame, 0, &frameBGR));
160  }
161  else
162  {
163  // 重用现有的 VPIImage 包装器来包裹新帧。
164  CHECK_STATUS(vpiImageSetWrappedOpenCVMat(frameBGR, cvFrame));
165  }
166 
167  // 首先将其转换为 NV12_ER
168  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, frameBGR, imgCurrent, NULL));
169 
170  // 应用时域噪声消除
171  // 对于第一帧,我们必须传递 NULL 作为上一帧,
172  // 这将重置内部状态。
173  VPITNRParams params;
174  CHECK_STATUS(vpiInitTemporalNoiseReductionParams(&params));
175 
177  params.strength = 1.0f;
178 
179  CHECK_STATUS(vpiSubmitTemporalNoiseReduction(stream, 0, tnr, curFrame == 1 ? NULL : imgPrevious, imgCurrent,
180  imgOutput, &params));
181 
182  // 将输出转换回 BGR
183  CHECK_STATUS(vpiSubmitConvertImageFormat(stream, VPI_BACKEND_CUDA, imgOutput, frameBGR, NULL));
184  CHECK_STATUS(vpiStreamSync(stream));
185 
186  // 现在将其添加到输出视频流
187  VPIImageData imgdata;
188  CHECK_STATUS(vpiImageLockData(frameBGR, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &imgdata));
189 
190  cv::Mat outFrame;
191  CHECK_STATUS(vpiImageDataExportOpenCVMat(imgdata, &outFrame));
192  outVideo << outFrame;
193 
194  CHECK_STATUS(vpiImageUnlock(frameBGR));
195 
196  // 此迭代的输出将是下一个迭代的上一帧。将被丢弃的上一帧将被重用
197  // 以存储下一帧。
198  std::swap(imgPrevious, imgOutput);
199  };
200  }
201  catch (std::exception &e)
202  {
203  std::cerr << e.what() << std::endl;
204  retval = 1;
205  }
206 
207  // =========================
208  // 销毁所有 VPI 资源
209  vpiStreamDestroy(stream);
210  vpiPayloadDestroy(tnr);
211  vpiImageDestroy(imgPrevious);
212  vpiImageDestroy(imgCurrent);
213  vpiImageDestroy(imgOutput);
214  vpiImageDestroy(frameBGR);
215 
216  return retval;
217 }
声明处理图像格式转换的函数。
#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。
struct VPIPayloadImpl * VPIPayload
算法负载的句柄。
定义: Types.h:268
void vpiPayloadDestroy(VPIPayload payload)
释放负载对象和所有关联的资源。
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
float strength
噪声消除强度。
VPITNRPreset preset
要使用的场景预设。
VPIStatus vpiSubmitTemporalNoiseReduction(VPIStream stream, uint64_t backend, VPIPayload payload, VPIImage prevFrame, VPIImage curFrame, VPIImage outFrame, const VPITNRParams *params)
将时域噪声消除操作提交到流。
VPIStatus vpiInitTemporalNoiseReductionParams(VPITNRParams *params)
使用默认值初始化 vpiSubmitTemporalNoiseReduction。
VPIStatus vpiCreateTemporalNoiseReduction(uint64_t backends, int32_t width, int32_t height, VPIImageFormat imgFormat, VPITNRVersion version, VPIPayload *payload)
为时域噪声消除算法创建负载。
@ VPI_TNR_PRESET_INDOOR_MEDIUM_LIGHT
中等光线室内场景。
@ VPI_TNR_DEFAULT
选择当前设备和给定后端中可用的最佳质量版本。
定义 vpiSubmitTemporalNoiseReduction 参数的结构。
@ VPI_LOCK_READ
仅锁定内存以进行读取。
定义: Types.h:617