NVIDIA Vision Programming Interface ( VPI )是 NVIDIA 的一個計算機視覺和圖像處理軟件庫,使您能夠在 NVIDIA Jetson 嵌入式設備和離散 GPU 上提供的不同硬件后端上實現加速算法。
庫中的一些算法包括濾波方法、透視扭曲、時間降噪、直方圖均衡化、立體視差和鏡頭畸變校正。 VPI 提供了易于使用的 Python 綁定,以及一個 C ++ API 。
除了 與 OpenCV 接口 , VPI 還能夠與 PyTorch 和其他基于 Python 的庫進行互操作。在本文中,我們將通過一個基于 PyTorch 的對象檢測和跟蹤示例向您展示這種互操作性是如何工作的。有關更多信息,請參閱 視覺編程接口( VPI ) 頁和 Vision 編程接口 文檔。
與 PyTorch 和其他庫的互操作性
根據您在計算機視覺和深度學習管道中實現的應用程序,您可能必須使用多個庫。開發此類管道的挑戰之一是這些庫之間交互的效率。例如,當在內存拷貝之間交換圖像數據時,可能會由于內存拷貝而出現性能問題。
使用 VPI ,您現在可以與 PyTorch 或任何其他支持__cuda_array_interace__
的庫進行互操作。__cuda_array_interface__
( CUDA Array Interface )是 Python 中的一個屬性,它支持各種項目(如庫)中類似 GPU Array 對象的不同實現之間的互操作性。
陣列對象(如圖像)可以在一個庫中創建,在另一個庫中修改,而無需復制 GPU 中的數據或將其傳遞給 CPU 。

時間噪聲抑制以改進目標檢測和跟蹤
噪聲是視頻中跨幀的常見特征。這種時間噪聲會對視頻中目標檢測和跟蹤算法的性能產生負面影響。
VPI 庫提供了一種時間降噪( TNR )算法,這是計算機視覺應用中用于降低視頻數據中噪聲的常用方法。有關更多信息,請參閱 在 NVIDIA Jetson 嵌入式計算機上使用 NVIDIA VPI 降低圖像的時間噪聲 .
在本演練中,您將在嘈雜的視頻上使用基于 PyTorch 的對象檢測和跟蹤示例(圖 2 )。然后應用 VPI 中的 TNR 算法來減少噪聲,從而改進目標檢測和跟蹤。
我們表明,在執行 VPI 和 PyTorch 算法的過程中, VPI 和 PyTorch 都可以無縫工作,沒有任何內存拷貝。

該示例包括以下內容:
- PyTorch 基于原始輸入視頻的目標檢測與跟蹤
- PyTorch 基于 VPI TNR 的干凈輸入視頻目標檢測與跟蹤
- 使用 CUDA 陣列接口實現 VPI 和 PyTorch 之間的互操作性
PyTorch 基于原始輸入視頻的目標檢測與跟蹤
首先,首先定義一個基于 PyTorch 的應用程序來檢測圖像中的對象。此示例應用程序基于 具有 MobileNetV3 主干的 SSDLite ,用于使用 PyTorch 和 Torchvision 進行目標檢測 Example.
創建一個名為PyTorchDetection
的類來處理所有 PyTorch 對象和調用。創建此類對象時,應用程序正在將用于對象檢測的預訓練深度學習模型加載到 GPU ,僅用于推理。以下代碼示例顯示了所需的導入和類構造函數定義:
import torch
import torchvision class PyTorchDetection: def __init__(self): assert torch.cuda.is_available() self.cuda_device = torch.device('cuda') self.convert = torchvision.transforms.Compose([ torchvision.transforms.ConvertImageDtype(torch.float32), torchvision.transforms.Lambda(lambda x: x.permute(2, 0, 1)), torchvision.transforms.Lambda(lambda x: x.unsqueeze(0)), ]) model = torchvision.models.detection.ssdlite320_mobilenet_v3_large(
pretrained=True) self.torch_model = model.eval().to(self.cuda_device)
PyTorchDetection
類還負責從陣列創建 CUDA 圖像幀,有效地將其上載到 GPU 。稍后,您將使用 OpenCV 從文件中讀取輸入視頻,其中每個視頻幀都是一個 NumPy 數組,用作該類創建函數的輸入。
此外,PyTorchDetection
類可以將 CUDA 圖像幀轉換為 CUDA 張量對象,為模型推斷做好準備,并將基于 VPI 的 CUDA 幀轉換為張量。最后一次轉換使用 VPI 的__cuda_array_interface__
互操作性來避免復制幀。
def CreateCUDAFrame(self, np_frame): return torch.from_numpy(np_frame).to(self.cuda_device) def ConvertToTensor(self, cuda_frame): return self.convert(cuda_frame) def ConvertFromVPIFrame(self, vpi_cuda_frame): return torch.as_tensor(vpi_cuda_frame, device=self.cuda_device)
除了前面定義的函數外,PyTorchDetection
類還定義了一個函數,在給定scores_threshold
值的情況下,可以在當前 OpenCV 幀中檢測和繪制對象:
def DetectAndDraw(self, cv_frame, torch_tensor, title, scores_threshold=0.2): with torch.no_grad(): pred = self.torch_model(torch_tensor) (...)
在這篇文章中,我們省略了代碼,以提請大家注意 PyTorch 模型的預測結果。此處下載或使用本規范即表示您接受本規范的條款和條件。 您可以下載并查看代碼 。
下一節介紹如何使用 VPI 降低輸入視頻中的噪聲,將 VPI 與 PyTorch 耦合以改進其目標檢測。
PyTorch 基于 VPI TNR 的干凈輸入視頻目標檢測與跟蹤
在本節中,定義一個基于 VPI 的實用程序類VPITemporalNoiseReduction
,以清除視頻幀中的噪聲。
創建此類對象時,應用程序加載主 VPI TNR 對象和基于 VPI 的 CUDA 幀以存儲清理后的輸出。以下代碼示例顯示了所需的導入和類構造函數定義:
import vpi class VPITemporalNoiseReduction: def __init__(self, shape, image_format): if (image_format == 'BGR8'): self.vpi_image_format = vpi.Format.BGR8 else: self.vpi_image_format = vpi.Format.INVALID self.vpi_output_frame = vpi.Image(shape, format=self.vpi_image_format) self.tnr = vpi.TemporalNoiseReduction(shape, vpi.Format.NV12_ER, version=vpi.TNRVersion.V3, backend=vpi.Backend.CUDA)
類的構造函數需要每個輸入圖像幀的形狀(圖像寬度和高度)和格式。為簡單起見,您只接受BGR8圖像格式,因為這是OpenCV在讀取輸入視頻時使用的格式。
此外,您正在創建 VPI 圖像,以使用提供的形狀和格式存儲輸出幀。然后使用 TNR code 版本 3 和 CUDA 后端為該形狀構造 TNR 對象。 TNR 的輸入格式為 NV12 \ u ER ,與輸入圖像幀中的格式不同。接下來將在Denoise
實用程序函數中處理幀轉換。
def Denoise(self, torch_cuda_frame, tnr_strength=1.0): vpi_input_frame = vpi.asimage(torch_cuda_frame, format=self.vpi_image_format) with vpi.Backend.CUDA: vpi_input_frame = vpi_input_frame.convert(vpi.Format.NV12_ER) vpi_input_frame = self.tnr(vpi_input_frame, preset=vpi.TNRPreset.OUTDOOR_LOW_LIGHT, strength=tnr_strength) vpi_input_frame.convert(out=self.vpi_output_frame) return self.vpi_output_frame
最后一個函數執行輸入圖像幀的實際清理。此函數用于從基于 PyTorch 的輸入 CUDA 幀中移除噪聲,返回基于 VPI 的輸出 CUDA 幀。
- 首先使用 PyTorch 函數將 PyTorch
vpi.asimage
幀轉換為 VPI 。torch_cuda_frame
與vpi_input_frame
共享相同的內存空間:即不涉及內存拷貝。 - 接下來,將輸入幀從給定的輸入格式( BGR8 )轉換為 CUDA 中的 NV12 \ u ER 進行處理。
- 使用 TNR 預設
OUTDOOR_LOW_LIGHT
和給定的 TNR 強度,在轉換后的輸入幀上執行 TNR 算法。 - 清理后的輸入幀( TNR 算法的輸出)被轉換回原始格式( BGR8 ),并存儲在基于 VPI 的 CUDA 輸出幀中。
- 生成的輸出幀將返回,以供 PyTorch 稍后使用。
使用 CUDA 陣列接口實現 VPI 和 PyTorch 之間的互操作性
最后,在主模塊中定義一個MainWindow
類。此類基于 PySide2 ,并為本例提供了圖形用戶界面。
窗口界面顯示兩個輸出圖像幀,一個僅使用 PyTorch 進行檢測,另一個在 VPI TNR 后使用 PyTorch 。此外,窗口界面包含兩個滑塊,用于控制用于 PyTorch 檢測的分數閾值和用于去除 VPI 時間噪聲的 TNR 強度。
import cv2
import numpy as np
(...)
from PySide2 import QtWidgets, QtGui, QtCore
(...)
from vpitnr import VPITemporalNoiseReduction
from torchdetection import PyTorchDetection class MainWindow(QMainWindow): def __init__(self, input_path): super().__init__() #-------- OpenCV part -------- self.video_capture = cv2.VideoCapture(input_path) if not self.video_capture.isOpened(): self.Quit() self.input_width = int(self.video_capture.get(cv2.CAP_PROP_FRAME_WIDTH)) self.input_height = int(self.video_capture.get(cv2.CAP_PROP_FRAME_HEIGHT)) self.output_video_shape = (self.input_height * 2, self.input_width, 3) self.output_frame_shape = (self.input_height, self.input_width, 3) self.cv_output_video = np.zeros(self.output_video_shape, dtype=np.uint8) #-------- Main objects of this example -------- self.torch_detection = PyTorchDetection() self.vpi_tnr = VPITemporalNoiseReduction((self.input_width, self.input_height), 'BGR8') (...) def UpdateDetection(self): in_frame = self.cv_input_frame if in_frame is None: return cuda_input_frame = self.torch_detection.CreateCUDAFrame(in_frame) # -------- Top Frame: No VPI --------- cuda_tensor = self.torch_detection.ConvertToTensor(cuda_input_frame) self.torch_detection.DetectAndDraw(self.TopFrame(), cuda_tensor, 'Pytorch only (no VPI)', self.scores_threshold) # -------- Bottom Frame: With VPI --------- vpi_output_frame = self.vpi_tnr.Denoise(cuda_input_frame, self.tnr_strength) with vpi_output_frame.rlock_cuda() as cuda_frame: cuda_output_frame=self.torch_detection.ConvertFromVPIFrame(cuda_frame) cuda_tensor = self.torch_detection.ConvertToTensor(cuda_output_frame) self.torch_detection.DetectAndDraw(self.BottomFrame(), cuda_tensor, 'Pytorch + VPI TNR', self.scores_threshold) (...)
類的構造函數需要輸入視頻的路徑。它使用OpenCV讀取輸入視頻,并創建一個高度為輸入視頻兩倍的輸出視頻幀。這用于存儲兩個輸出幀,一個僅用于PyTorch輸出,另一個用于VPI+ PyTorch 輸出。
構造函數還為 PyTorch 檢測和 VPI TNR 創建對象。在本文中,我們省略了創建圖形用戶界面小部件和處理其回調的代碼。我們還省略了創建主窗口和啟動應用程序的代碼。有關 TNR code 這一部分的更多信息,請下載示例。
當有新的輸入視頻幀可用時,調用UpdateDetection
函數,從 NumPy OpenCV 輸入幀創建基于 PyTorch 的 CUDA 輸入幀。然后將其轉換為張量,以檢測并繪制PyTorchDetection
類。頂部幀的管道直接在輸入視頻幀中運行 PyTorch 檢測。
底部幀的下一條管道首先對基于 PyTorch CUDA 的輸入幀進行去噪。去噪后的輸出是一個名為vpi_output_frame
的基于 VPI 的 CUDA 幀,使用rlock_cuda
函數在 CUDA 中鎖定讀取。此函數為cuda_frame
對象中的 VPI CUDA 互操作性提供__cuda_array_interface__
。該對象將轉換為 PyTorch CUDA 幀,然后轉換為張量。再次,對管道的結果調用 detect and draw 函數。第二條管道在 VPI 去噪功能之后運行PyTorchDetection
。
結果
圖 3 顯示了在公共場所行人噪聲輸入視頻上,無 VPI TNR 和有 VPI TNR 的 PyTorch 目標檢測和跟蹤的結果。正如您可以從帶有注釋的輸出視頻中看到的那樣,在檢測之前應用去噪可以改善檢測和跟蹤結果(右)。

視頻幀右下角顯示的每秒幀數( FPS )( 32.8 僅適用于 PyTorch , 32.1 適用于 VPI + PyTorch )表明,將 VPI 添加到 PyTorch 檢測管道不會增加太多開銷。這在一定程度上是由于避免了從 CUDA 內存到 CPU 內存的每幀超過 20Mb 的拷貝,這是通過使用__cuda_array_interface__
啟用的。
總結
在這篇文章中,我們以 PyTorch 對象檢測和跟蹤為例,展示了 VPI 和其他支持__cuda_array_interface__
的庫之間的互操作性。在目標檢測和跟蹤之前,您應用了 VPI 的時間噪聲抑制來改進它。我們還證明了在 PyTorch 管道中添加 VPI 不會導致性能損失。
有關更多信息,請參閱 視覺編程接口( VPI ) 頁。
?