NVIDIA Vision Programming Interface ( VPI )是 NVIDIA 的一個(gè)計(jì)算機(jī)視覺(jué)和圖像處理軟件庫(kù),使您能夠在 NVIDIA Jetson 嵌入式設(shè)備和離散 GPU 上提供的不同硬件后端上實(shí)現(xiàn)加速算法。
庫(kù)中的一些算法包括濾波方法、透視扭曲、時(shí)間降噪、直方圖均衡化、立體視差和鏡頭畸變校正。 VPI 提供了易于使用的 Python 綁定,以及一個(gè) C ++ API 。
除了 與 OpenCV 接口 , VPI 還能夠與 PyTorch 和其他基于 Python 的庫(kù)進(jìn)行互操作。在本文中,我們將通過(guò)一個(gè)基于 PyTorch 的對(duì)象檢測(cè)和跟蹤示例向您展示這種互操作性是如何工作的。有關(guān)更多信息,請(qǐng)參閱 視覺(jué)編程接口( VPI ) 頁(yè)和 Vision 編程接口 文檔。
與 PyTorch 和其他庫(kù)的互操作性
根據(jù)您在計(jì)算機(jī)視覺(jué)和深度學(xué)習(xí)管道中實(shí)現(xiàn)的應(yīng)用程序,您可能必須使用多個(gè)庫(kù)。開(kāi)發(fā)此類管道的挑戰(zhàn)之一是這些庫(kù)之間交互的效率。例如,當(dāng)在內(nèi)存拷貝之間交換圖像數(shù)據(jù)時(shí),可能會(huì)由于內(nèi)存拷貝而出現(xiàn)性能問(wèn)題。
使用 VPI ,您現(xiàn)在可以與 PyTorch 或任何其他支持__cuda_array_interace__的庫(kù)進(jìn)行互操作。__cuda_array_interface__( CUDA Array Interface )是 Python 中的一個(gè)屬性,它支持各種項(xiàng)目(如庫(kù))中類似 GPU Array 對(duì)象的不同實(shí)現(xiàn)之間的互操作性。
陣列對(duì)象(如圖像)可以在一個(gè)庫(kù)中創(chuàng)建,在另一個(gè)庫(kù)中修改,而無(wú)需復(fù)制 GPU 中的數(shù)據(jù)或?qū)⑵鋫鬟f給 CPU 。
圖 1 VPI 和其他庫(kù)之間的互操作性使用 __cuda_array_interface__
時(shí)間噪聲抑制以改進(jìn)目標(biāo)檢測(cè)和跟蹤
噪聲是視頻中跨幀的常見(jiàn)特征。這種時(shí)間噪聲會(huì)對(duì)視頻中目標(biāo)檢測(cè)和跟蹤算法的性能產(chǎn)生負(fù)面影響。
VPI 庫(kù)提供了一種時(shí)間降噪( TNR )算法,這是計(jì)算機(jī)視覺(jué)應(yīng)用中用于降低視頻數(shù)據(jù)中噪聲的常用方法。
在本演練中,您將在嘈雜的視頻上使用基于 PyTorch 的對(duì)象檢測(cè)和跟蹤示例。然后應(yīng)用 VPI 中的 TNR 算法來(lái)減少噪聲,從而改進(jìn)目標(biāo)檢測(cè)和跟蹤。
我們表明,在執(zhí)行 VPI 和 PyTorch 算法的過(guò)程中, VPI 和 PyTorch 都可以無(wú)縫工作,沒(méi)有任何內(nèi)存拷貝。
該示例包括以下內(nèi)容:
PyTorch 基于原始輸入視頻的目標(biāo)檢測(cè)與跟蹤
PyTorch 基于 VPI TNR 的干凈輸入視頻目標(biāo)檢測(cè)與跟蹤
使用 CUDA 陣列接口實(shí)現(xiàn) VPI 和 PyTorch 之間的互操作性
PyTorch 基于原始輸入視頻的目標(biāo)檢測(cè)與跟蹤
首先,首先定義一個(gè)基于 PyTorch 的應(yīng)用程序來(lái)檢測(cè)圖像中的對(duì)象。此示例應(yīng)用程序基于 具有 MobileNetV3 主干的 SSDLite ,用于使用 PyTorch 和 Torchvision 進(jìn)行目標(biāo)檢測(cè) Example.
創(chuàng)建一個(gè)名為PyTorchDetection的類來(lái)處理所有 PyTorch 對(duì)象和調(diào)用。創(chuàng)建此類對(duì)象時(shí),應(yīng)用程序正在將用于對(duì)象檢測(cè)的預(yù)訓(xùn)練深度學(xué)習(xí)模型加載到 GPU ,僅用于推理。以下代碼示例顯示了所需的導(dǎo)入和類構(gòu)造函數(shù)定義:
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
類還負(fù)責(zé)從陣列創(chuàng)建 CUDA 圖像幀,有效地將其上載到 GPU 。稍后,您將使用 OpenCV 從文件中讀取輸入視頻,其中每個(gè)視頻幀都是一個(gè) NumPy 數(shù)組,用作該類創(chuàng)建函數(shù)的輸入。
此外,PyTorchDetection
類可以將 CUDA 圖像幀轉(zhuǎn)換為 CUDA 張量對(duì)象,為模型推斷做好準(zhǔn)備,并將基于 VPI 的 CUDA 幀轉(zhuǎn)換為張量。最后一次轉(zhuǎn)換使用 VPI 的__cuda_array_interface__
互操作性來(lái)避免復(fù)制幀。
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)
除了前面定義的函數(shù)外,PyTorchDetection
類還定義了一個(gè)函數(shù),在給定scores_threshold
值的情況下,可以在當(dāng)前 OpenCV 幀中檢測(cè)和繪制對(duì)象:
def DetectAndDraw(self, cv_frame, torch_tensor, title, scores_threshold=0.2): with torch.no_grad(): pred = self.torch_model(torch_tensor) (...)
在這篇文章中,我們省略了代碼,以提請(qǐng)大家注意 PyTorch 模型的預(yù)測(cè)結(jié)果。此處下載或使用本規(guī)范即表示您接受本規(guī)范的條款和條件。 您可以下載并查看代碼 。
下一節(jié)介紹如何使用 VPI 降低輸入視頻中的噪聲,將 VPI 與 PyTorch 耦合以改進(jìn)其目標(biāo)檢測(cè)。
PyTorch 基于 VPI TNR 的干凈輸入視頻目標(biāo)檢測(cè)與跟蹤
在本節(jié)中,定義一個(gè)基于 VPI 的實(shí)用程序類VPITemporalNoiseReduction,以清除視頻幀中的噪聲。
創(chuàng)建此類對(duì)象時(shí),應(yīng)用程序加載主 VPI TNR 對(duì)象和基于 VPI 的 CUDA 幀以存儲(chǔ)清理后的輸出。以下代碼示例顯示了所需的導(dǎo)入和類構(gòu)造函數(shù)定義:
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)
類的構(gòu)造函數(shù)需要每個(gè)輸入圖像幀的形狀(圖像寬度和高度)和格式。為簡(jiǎn)單起見(jiàn),您只接受BGR8圖像格式,因?yàn)檫@是OpenCV在讀取輸入視頻時(shí)使用的格式。
此外,您正在創(chuàng)建 VPI 圖像,以使用提供的形狀和格式存儲(chǔ)輸出幀。然后使用 TNR code 版本 3 和 CUDA 后端為該形狀構(gòu)造 TNR 對(duì)象。 TNR 的輸入格式為 NV12 \ u ER ,與輸入圖像幀中的格式不同。接下來(lái)將在Denoise實(shí)用程序函數(shù)中處理幀轉(zhuǎn)換。
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
最后一個(gè)函數(shù)執(zhí)行輸入圖像幀的實(shí)際清理。此函數(shù)用于從基于 PyTorch 的輸入 CUDA 幀中移除噪聲,返回基于 VPI 的輸出 CUDA 幀。
首先使用 PyTorch 函數(shù)將 PyTorch vpi.asimage幀轉(zhuǎn)換為 VPI 。torch_cuda_frame與vpi_input_frame共享相同的內(nèi)存空間:即不涉及內(nèi)存拷貝。
接下來(lái),將輸入幀從給定的輸入格式( BGR8 )轉(zhuǎn)換為 CUDA 中的 NV12 \ u ER 進(jìn)行處理。
使用 TNR 預(yù)設(shè)OUTDOOR_LOW_LIGHT和給定的 TNR 強(qiáng)度,在轉(zhuǎn)換后的輸入幀上執(zhí)行 TNR 算法。
清理后的輸入幀( TNR 算法的輸出)被轉(zhuǎn)換回原始格式( BGR8 ),并存儲(chǔ)在基于 VPI 的 CUDA 輸出幀中。
生成的輸出幀將返回,以供 PyTorch 稍后使用。
使用 CUDA 陣列接口實(shí)現(xiàn) VPI 和 PyTorch 之間的互操作性
最后,在主模塊中定義一個(gè)MainWindow類。此類基于 PySide2 ,并為本例提供了圖形用戶界面。
窗口界面顯示兩個(gè)輸出圖像幀,一個(gè)僅使用 PyTorch 進(jìn)行檢測(cè),另一個(gè)在 VPI TNR 后使用 PyTorch 。此外,窗口界面包含兩個(gè)滑塊,用于控制用于 PyTorch 檢測(cè)的分?jǐn)?shù)閾值和用于去除 VPI 時(shí)間噪聲的 TNR 強(qiáng)度。
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) (...)
類的構(gòu)造函數(shù)需要輸入視頻的路徑。它使用OpenCV讀取輸入視頻,并創(chuàng)建一個(gè)高度為輸入視頻兩倍的輸出視頻幀。這用于存儲(chǔ)兩個(gè)輸出幀,一個(gè)僅用于PyTorch輸出,另一個(gè)用于VPI+ PyTorch 輸出。
構(gòu)造函數(shù)還為 PyTorch 檢測(cè)和 VPI TNR 創(chuàng)建對(duì)象。在本文中,我們省略了創(chuàng)建圖形用戶界面小部件和處理其回調(diào)的代碼。我們還省略了創(chuàng)建主窗口和啟動(dòng)應(yīng)用程序的代碼。有關(guān) TNR code 這一部分的更多信息,請(qǐng)下載示例。
當(dāng)有新的輸入視頻幀可用時(shí),調(diào)用UpdateDetection函數(shù),從 NumPy OpenCV 輸入幀創(chuàng)建基于 PyTorch 的 CUDA 輸入幀。然后將其轉(zhuǎn)換為張量,以檢測(cè)并繪制PyTorchDetection類。頂部幀的管道直接在輸入視頻幀中運(yùn)行 PyTorch 檢測(cè)。
底部幀的下一條管道首先對(duì)基于 PyTorch CUDA 的輸入幀進(jìn)行去噪。去噪后的輸出是一個(gè)名為vpi_output_frame的基于 VPI 的 CUDA 幀,使用rlock_cuda函數(shù)在 CUDA 中鎖定讀取。此函數(shù)為cuda_frame對(duì)象中的 VPI CUDA 互操作性提供__cuda_array_interface__。該對(duì)象將轉(zhuǎn)換為 PyTorch CUDA 幀,然后轉(zhuǎn)換為張量。再次,對(duì)管道的結(jié)果調(diào)用 detect and draw 函數(shù)。第二條管道在 VPI 去噪功能之后運(yùn)行PyTorchDetection。
結(jié)果
圖 3 顯示了在公共場(chǎng)所行人噪聲輸入視頻上,無(wú) VPI TNR 和有 VPI TNR 的 PyTorch 目標(biāo)檢測(cè)和跟蹤的結(jié)果。正如您可以從帶有注釋的輸出視頻中看到的那樣,在檢測(cè)之前應(yīng)用去噪可以改善檢測(cè)和跟蹤結(jié)果(右)。
圖 3 PyTorch 無(wú) VPI TNR 和(右)有 VPI TNR 的目標(biāo)檢測(cè)和跟蹤(左)
視頻幀右下角顯示的每秒幀數(shù)( FPS )( 32.8 僅適用于 PyTorch , 32.1 適用于 VPI + PyTorch )表明,將 VPI 添加到 PyTorch 檢測(cè)管道不會(huì)增加太多開(kāi)銷。這在一定程度上是由于避免了從 CUDA 內(nèi)存到 CPU 內(nèi)存的每幀超過(guò) 20Mb 的拷貝,這是通過(guò)使用__cuda_array_interface__啟用的。
總結(jié)
在這篇文章中,我們以 PyTorch 對(duì)象檢測(cè)和跟蹤為例,展示了 VPI 和其他支持__cuda_array_interface__的庫(kù)之間的互操作性。在目標(biāo)檢測(cè)和跟蹤之前,您應(yīng)用了 VPI 的時(shí)間噪聲抑制來(lái)改進(jìn)它。我們還證明了在 PyTorch 管道中添加 VPI 不會(huì)導(dǎo)致性能損失。
關(guān)于作者
Sandeep Hiremath 是NVIDIA 計(jì)算機(jī)視覺(jué)的首席技術(shù)產(chǎn)品經(jīng)理。他是一位經(jīng)驗(yàn)豐富的產(chǎn)品領(lǐng)導(dǎo)者,專長(zhǎng)于計(jì)算機(jī)視覺(jué)、機(jī)器學(xué)習(xí)和嵌入式系統(tǒng)領(lǐng)域。在NVIDIA ,他負(fù)責(zé)為汽車、醫(yī)療保健、機(jī)器人和研究領(lǐng)域的開(kāi)發(fā)人員提供一組計(jì)算機(jī)視覺(jué)和圖像處理解決方案的產(chǎn)品愿景和戰(zhàn)略。
André de Almeida Maximo 是 NVIDIA 的系統(tǒng)工程師,專注于嵌入式平臺(tái)的軟件。他曾在多家工業(yè)公司的研究中心工作,幫助塑造不同行業(yè)的人工智能。在空閑時(shí)間,安德烈喜歡跑步和閱讀小說(shuō)。
審核編輯:郭婷
-
嵌入式
+關(guān)注
關(guān)注
5087文章
19153瀏覽量
306428 -
gpu
+關(guān)注
關(guān)注
28文章
4760瀏覽量
129133 -
計(jì)算機(jī)
+關(guān)注
關(guān)注
19文章
7523瀏覽量
88315 -
pytorch
+關(guān)注
關(guān)注
2文章
808瀏覽量
13283
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論