在本文中,我們將向您展示如何在 Jetson 產品系列上運行時間降噪( TNR )示例應用程序。
在 Jetson 設備上設置 VPI
通過 SDK 管理器設置 Jetson 設備時,請確保選中 Jetson SDK 組件框。然后在設備閃存時安裝 VPI 。
安裝完成后,可以在以下路徑下找到 VPI :
/opt/nvidia/vpi1/
要驗證環境設置是否正確,請將 VPI 示例應用程序復制到主目錄中,然后構建 TNR 示例。
$ vpi1_install_samples.sh $HOME $ cd $HOME/NVIDIA_VPI–samples/09-tnr $ cmake . $ make
VPI 在運行離散 GPU 的 x86 機器上也受支持。有關更多信息,請參閱 VPI – Vision 編程接口文檔中的 Installation 。
TNR 示例應用程序
VPI 提供了一組 CV 算法,這些算法利用多個后端高效地使用設備的可用計算資源。 TNR 是在 Jetson 設備上運行的計算機視覺應用程序中常用的一種降噪方法。本文使用 TNR 示例應用程序來演示如何使用 VPI 中的一些關鍵概念和組件來實現自己的應用程序。
我們將在本帖中介紹以下主題:
創建構建 VPI 管道所需的元素
了解如何與 OpenCV 進行互操作
向流提交處理任務
同步流中的任務
鎖定圖像緩沖區以便可以從 CPU 訪問
TNR 樣本可在以下路徑中找到:
$HOME/NVIDIA_VPI–samples/09-tnr/main.cpp
有關示例應用程序和算法的更多信息,請參閱以下參考資料:
時間降噪示例應用程序
時域降噪算法
算法版本和后端支持
硬件引擎在 VPI 中被命名為 backends 。通過使用 Jetson 設備固有的可用系統級并行性,這些后端使您能夠卸載可并行處理階段并加速應用程序。后端是 CPU 、 CUDA ( GPU )、 PVA 和 VIC 。特定后端引擎的確切可用性取決于應用程序部署到的 Jetson 平臺。有關特定平臺上可用算法、后端支持和后端可用性的更多信息,請參閱 Algorithms 。
VPI 目前為 TNR 提供了兩種不同的實現,每種實現都適合不同的場景和需求。這些版本采用雙邊濾波平滑平坦區域,同時保留邊緣,和時間無限脈沖響應( IIR )濾波與運動檢測器的結合,以處理跨幀的時間噪聲。
VPI_TNR_V2 – 與 VPI_TNR_V3 相比,該版本提供了更輕的噪音降低,并且具有一定程度的可配置性,即可以調整照明條件以更好地適應給定場景。這個版本有一個減少的計算需求,這轉化為速度。它適用于執行時間比降噪質量更重要的用例。
VPI_TNR_V3 —用于需要更好質量的降噪的用例。與 VPI_TNR_V2 相比,使用這個變體,您應該期望計算需求會增加。除此之外,還進一步擴展了可配置性。建議用于具有挑戰性的弱光場景。
VPI_TNR_DEFAULT – 您可以使用默認值,而不是指定確切的版本,該值選擇給定后端支持的噪聲抑制最強的版本。
在決定哪個算法版本適合您的用例時,需要考慮的另一個標準是它對不同后端和設備的支持。下表總結了 TNR 支持。
VPI_TNR_V2 和 VPI_TNR_V3 都允許顯式設置要捕獲的場景的照明條件,從而啟用調整。這在低光場景或以高增益捕獲的流的上下文中是重要的,所述低光場景或流可能包含更高的噪聲級,并且因此要求更高的噪聲降低水平。
較高的強度級別可能會影響幀的紋理區域中的細節數量,從而使其平滑。另一個副作用是在有快速移動物體的場景中重影。支持的場景照明條件在類型(室內、室外)和強度(低、中、高)方面有所不同,如下表所示。
使用不同的版本和相關的照明條件預設,您可以根據用例的具體情況調整 TNR 算法。這可以通過所謂的強度系數進一步定制。它是一個浮動參數,范圍從 0 到 1 ,其中較大的值對應于增加的去噪強度。
VPI 應用程序
VPI 的一個關鍵方面是如何管理和協調在不同后端之間運行應用程序所需的資源。使用 VPI ,可以避免在處理階段之間浪費內存拷貝。 VPI 為實現高效的內存管理而實施的另一種機制是在其接口處進行內存包裝。
利用 VPI 的所有內存管理特性取決于代碼的結構。最佳實踐是將代碼視為三階段工作流:
Initialization
處理回路
Cleanup
大部分內存分配應該在初始化階段進行。對于在可用資源受限的設備上運行的嵌入式應用程序,這一點尤為重要。除此之外,還可以更有效、更謹慎地進行內存管理,以避免可能的內存泄漏。
VPI 中的一個好做法是指定使用內存的后端。在這點上,只將 VPI 對象訂閱到所需的后端集可以保證在管道在這些后端之間流動時獲得最有效的內存路徑。
處理循環是執行處理管道的地方。想象一下,一個應用程序在一個包含數百個單獨幀的視頻文件上迭代。主循環將主要負責對像素信息執行所需的變換,以實現給定計算機視覺任務的預期結果。
最后,清理階段處理在任務執行期間使用的資源的所有必要釋放和釋放。堅持這種模式可以使 VPI 盡可能使用最有效的處理管道,并幫助您堅持良好的編碼實踐。
與 OpenCV 接口
VPI 與 OpenCV 的互操作性是該庫的一個顯著特征。如果您熟悉 OpenCV ,您可以輕松地將 VPI 與工作流集成,或者擴展現有的數據管道,以便更好地使用 VPI 提供的硬件加速。
TNR 示例中通過以下實用函數演示了這一點,該函數將使用 OpenCV 捕獲的輸入視頻幀包裝到 VPI 圖像對象中。
69 // Utility function to wrap a cv::Mat into a VPIImage 70 static VPIImage ToVPIImage(VPIImage image, const cv::Mat &frame) 71 { 72 if (image == nullptr) 73 { 74 // Create a VPIImage that wraps the frame 75 CHECK_STATUS(vpiImageCreateOpenCVMatWrapper(frame, 0, &image)); 76 } 77 else 78 { 79 // reuse existing VPIImage wrapper to wrap the new frame. 80 CHECK_STATUS(vpiImageSetWrappedOpenCVMat(image, frame)); 81 } 82 return image; 83 }
從更深入地研究前面描述的函數開始。它意味著將 OpenCV 矩陣( cv::Mat )對象包裝到 VPI 圖像對象( VPIImage )。要上下文化, VPI 圖像基本上是任何可以用寬度、高度和格式來描述的 2D 數據結構。盡管將圖像數據視為 VPIImage 對象是直觀的,但它的用法也可以擴展到其他類型的數據,例如二維向量場和熱圖。
The utility wrapping function invokes two other functions that pertain to the VPI OpenCVInterop.hpp module, which aims to provide useful infrastructure to integrate OpenCV-based code with VPI.
vpiImageCreateOpenCVMatWrapper —一個重載函數,它以兩種不同的方式將 cv:Mat 對象包裝到 VPIImage 中。第一種方法嘗試直接從輸入類型推斷格式(遵循特定的規則),而第二種方法將顯式格式作為其參數之一。
vpiImageSetWrappedOpenCVMat – 重用為特定 cv::Mat 對象定義的包裝器來包裝新的傳入 cv::Mat 對象。這里的重點是避免在第一時間創建包裝時產生的內存分配,這樣效率更高。傳入的 cv::Mat 對象必須呈現與創建時使用的原始對象相同的特征(格式和尺寸)。
流創建
main 函數捕獲設置 VPI 管道以完成工作的相關步驟。管道的定義很簡單,也很直觀。在 VPI 中,管道是一個或多個數據流的組合,這些數據流流經不同的處理階段。
圖 1 以一種通用的方式顯示了管道及其構建塊(流、緩沖區、算法等)。為了簡單起見,省略了一些組件。
圖 1 通用 VPI 管道。
流的目的是強制執行一個排隊的步驟序列,數據需要通過該序列來完成特定的計算機視覺任務。這些步驟可能包括數據的預處理或后處理,甚至包括 TNR 之類的成熟算法。圖 2 顯示了 VPIStream 對象的示例。
圖 2 VPIStream 對象。
VPI 可適應各種不同的管道復雜性。您可以用一個流實現一個簡單的管道,或者用幾個不同階段的并行流實現一個更復雜的實現,并將這些并行流卸載到不同的計算后端。這是 API 的一個強大功能,因為它使您能夠獲得對 Jetson 設備提供的系統級并行性的更多控制。
下面的代碼示例演示如何在 TNR 示例中創建流。
143 VPIStream stream; 144 // PVA backend doesn't have currently Convert Image Format algorithm. 145 // Use the CUDA backend to do that. 146 CHECK_STATUS(vpiStreamCreate(VPI_BACKEND_CUDA | backend, &stream));
選擇的后端正在傳遞到流中。這是一個可選步驟。使用零值將啟用所有可用的后端。但是,分配一組特定的后端是推薦的做法,因為它有助于優化內存分配。
TNR 有效載荷
有效負載基本上是管道執行期間所需的臨時資源。例如,有效負載可以是中間內存緩沖區,用于存儲流的后續階段之間交換的數據。包括 TNR 在內的許多算法都需要顯式地創建有效負載,具體實現如下。
172 // Create a TNR payload configured to process NV12 173 // frames under outdoor low-light scenarios. 174 VPIPayload tnr; 175 CHECK_STATUS(vpiCreateTemporalNoiseReduction(backend, w, h, VPI_IMAGE_FORMAT_NV12_ER, VPI_TNR_DEFAULT, 176 VPI_TNR_PRESET_INDOOR_LOW_LIGHT, 1, &tnr));
對于 TNR 有效負載,請提供以下參數:
圖像尺寸(寬度和高度)
Backend
圖像數據格式(目前僅支持 NV12 )
TNR 算法版本
照明條件
降噪強度
對算法有效負載的引用
最終,該函數創建一個有效負載并將其綁定到指定的后端。
圖像緩沖區
除了創建流和有效負載外,還必須創建 VPI 算法所需的圖像緩沖區。在 TNR 中,使用雙邊和 IIR 濾波器的組合,因此需要三個不同的緩沖器,即當前和先前的圖像輸入和圖像輸出。
可以按如下方式創建圖像緩沖區:
167 VPIImage imgPrevious, imgCurrent, imgOutput; 168 CHECK_STATUS(vpiImageCreate(w, h,VPI_IMAGE_FORMAT_NV12_ER, 0, &imgPrevious)); 169 CHECK_STATUS(vpiImageCreate(w, h,VPI_IMAGE_FORMAT_NV12_ER, 0, &imgCurrent)); 170 CHECK_STATUS(vpiImageCreate(w, h,VPI_IMAGE_FORMAT_NV12_ER, 0, &imgOutput));
這將創建具有以下指定特征的空緩沖區:
圖像尺寸(寬度和高度)
格式(根據算法要求)
圖像標志(當前用于分配后端)
指向返回所創建映像的 VPIImage 句柄的變量的指針
流處理
在構建塊已經就位的情況下,您可以轉到主處理循環,在那里執行降噪算法。在 TNR 樣本上,循環迭代來自視頻文件的每個單獨幀,并執行必要的連續步驟以獲得所需的結果。
當從視頻中收集幀時,第一步是使用前面描述的實用程序函數將其包裝成 VPIImage 對象。
186 frameBGR = ToVPIImage(frameBGR, cvFrame);
包裝完成后, VPI 現在可以對 VPIImage 對象中的像素數據進行操作。因為 TNR 要求幀是 NV12 格式,所以需要一個轉換步驟。
188 // First convert it to NV12 189 CHECK_STATUS(vpiSubmitConvertImageFormat(stream,VPI_BACKEND_CUDA, frameBGR, imgCurrent, NULL));
在此階段,轉換圖像的特定任務與先前實例化的流相關聯。除此之外,任務被設置為在 GPU 上執行。輸入幀的圖像緩沖區以及剛剛從cv::Mat
對象包裝的數據都用于此目的。
格式轉換完成后,可以將輸入緩沖區傳遞給 TNR 算法進行處理。
191 // Apply TNR 192 // For first frame, you must pass nullptr as the previous frame, this resets the internal 193 // state. 194 CHECK_STATUS(vpiSubmitTemporalNoiseReduction(stream, 0, tnr, curFrame == 1 ? nullptr : imgPrevious, 195 imgCurrent, imgOutput)); 196
要調用 TNR 算法,請設置以下參數:
- 與算法關聯的流
- 后端
- 算法有效負載,如前面實例化的一樣
- 圖像緩沖區:以前和當前的輸入和輸出
在第一次迭代(curFrame == 1
)時,緩沖區上沒有有效的前一個映像,而是傳遞一個空指針。對于下面的迭代,緩沖區將相應地填充。在執行 TNR 算法之后,輸出緩沖區可以從 NV12 轉換回以前的 BGR 格式。
197 // Convert output back to BGR 198 CHECK_STATUS(vpiSubmitConvertImageFormat(stream,VPI_BACKEND_CUDA, imgOutput, frameBGR, NULL));
在這一點上,必須提到 VPI 對流階段實施了非阻塞異步范例。這對于作為后端分布在不同協處理器之間的工作負載的平滑和高效的編排是必不可少的。對于進一步的步驟,請確保在繼續之前已完成向流發出的所有活動。這時同步功能就派上用場了。
199 CHECK_STATUS(vpiStreamSync(stream));
VPI 現在確保與流相關的所有正在進行的活動在進入管道的下一個階段之前都已完成。同步完成后,幀就可以在連接到指定后端的輸出緩沖區中使用了。為了能夠將其寫入輸出視頻流(在這種情況下是一個文件),必須鎖定圖像,以便 CPU 可以使用緩沖區。
這就解釋了為什么在鎖定幀之前進行同步是避免處理問題的關鍵步驟。因為 VPI 是異步操作的,所以在沒有同步的情況下,緩沖區會在前一階段完成之前被鎖定。結果是不可預測的。
201 // Now add it to the output video stream 202 VPIImageData imgdata; 203 CHECK_STATUS(vpiImageLock(frameBGR,VPI_LOCK_READ, &imgdata)); 204 205 cv::Mat outFrame; 206 CHECK_STATUS(vpiImageDataExportOpenCVMat(imgdata, &outFrame)); 207 outVideo << outFrame; ?208 ?209???????????? CHECK_STATUS(vpiImageUnlock(frameBGR));
如您所見,鎖定的緩沖區由 CPU 處理,以供進一步使用。鎖被設置為只讀,然后圖像緩沖區被映射到 CPU 。鎖定時, VPI 無法在緩沖區上工作。在 CPU 將輸出幀提供給視頻編碼器后,緩沖區可以被解鎖并被 VPI 進一步使用。
VPI 數據流
TNR 示例應用程序可以概括為以下數據流。其他的小步驟也是應用程序不可分割的一部分,但是為了簡單起見,圖 3 中只包含了宏步驟。
圖 3 TNR 示例應用程序中的數據流。
從視頻流或文件中收集輸入幀。 OpenCV 已用于此目的。
必要的 VPI 元素被實例化:單個流、 TNR 算法負載以及用于先前和當前輸入和輸出圖像的圖像緩沖區。
輸入幀被包裝到 VPIImage 緩沖區中。
緩沖區上的像素數據被轉換成 NV12 ,以便 TNR 算法可以處理它。當算法完成執行時,它會恢復到原始格式。
圖像緩沖區被鎖定,以便 CPU 可以訪問數據。將圖像提供給視頻輸出后,可以解鎖緩沖區, VPI 可以進一步處理它。
概括
在本文中,我們向您展示了如何在 Jetson 產品系列上運行 TNR 示例應用程序。
關于作者
Maycon da Silva Carvalho 是 Jetson 的現場應用工程師。他負責與部署基于 Jetson 的應用程序的不同行業的客戶進行多學科技術合作。
Rodolfo Schulz de Lima 是 VPI 的首席工程師。他擁有 UFRJ 里約熱內盧聯邦大學電子工程學士學位,并在巴西里約熱內盧的 IMPA (純數學和應用數學研究所)學習計算機圖形學。
審核編輯:郭婷
-
嵌入式
+關注
關注
5072文章
19026瀏覽量
303518 -
gpu
+關注
關注
28文章
4703瀏覽量
128725 -
SDK
+關注
關注
3文章
1029瀏覽量
45782
發布評論請先 登錄
相關推薦
評論