# Accelerate multi-streaming cameras with DeepStream and deploy custom (YOLO) models<br>使用DeepStream加速多串流攝影機並部署客製(YOLO)模型
###### tags: `Edge AI` `DeepStream` `Edge_AI` `deployment` `Nvidia` `Jetson`

## NVIDIA Jetson 平台部署相關筆記
### 基本環境設定
- [Jetson AGX Xavier 系統環境設定1_在windows10環境連接與安裝](https://hackmd.io/@YungHuiHsu/HJ2lcU4Rj)
- [Jetson AGX Xavier 系統環境設定2_Docker安裝或從源程式碼編譯](https://hackmd.io/k-lnDTxVQDWo_V13WEnfOg)
- [NVIDIA Container Toolkit 安裝筆記](https://hackmd.io/wADvyemZRDOeEduJXA9X7g)
- [Jetson 邊緣裝置查詢系統性能指令jtop](https://hackmd.io/VXXV3T5GRIKi6ap8SkR-tg)
- [Jetson Network Setup 網路設定](https://hackmd.io/WiqAB7pLSpm2863N2ISGXQ)
- [OpenCV turns on cuda acceleration in Nvidia Jetson platform<br>OpenCV在Nvidia Jetson平台開啟cuda加速](https://hackmd.io/6IloyiWMQ_qbIpIE_c_1GA)
### 模型部署與加速
- [[Object Detection_YOLO] YOLOv7 論文筆記](https://hackmd.io/xhLeIsoSToW0jL61QRWDcQ)
- [Deploy YOLOv7 on Nvidia Jetson](https://hackmd.io/kZftj6AgQmWJsbXsswIwEQ)
- [Convert PyTorch model to TensorRT for 3-6x speedup<br>將PyTorch模型轉換為TensorRT,實現3-8倍加速](https://hackmd.io/_oaJhYNqTvyL_h01X1Fdmw?both)
- [Accelerate multi-streaming cameras with DeepStream and deploy custom (YOLO) models<br>使用DeepStream加速多串流攝影機並部署客製(YOLO)模型](https://hackmd.io/@YungHuiHsu/rJKx-tv4h)
- [Use Deepstream python API to extract the model output tensor and customize model post-processing (e.g., YOLO-Pose)<br>使用Deepstream python API提取模型輸出張量並定製模型后處理(如:YOLO-Pose)](https://hackmd.io/@YungHuiHsu/rk41ISKY2)
- [Model Quantization Note 模型量化筆記](https://hackmd.io/riYLcrp1RuKHpVI22oEAXA)
---
### yolov7 with multiple cameras running on DeepStream
{%youtube 5BVrPbOyNHE%}
---
## Github 開箱即用
- [github/deepstream-yolov7](https://github.com/YunghuiHsu/deepstream_python_apps/tree/master/apps/deepstream-rtsp-in-rtsp-out)
- [github/deepstream-yolo-pose](https://github.com/YunghuiHsu/deepstream-yolo-pose)

---
## 簡介
> Nvidia DeepStream是一個基於GStreamer的流分析工具包,可用於多傳感器處理、視頻、音頻和圖像理解等多種用途。 目標是提供一個完整的流處理框架,基於AI和計算機視覺技術,以實現對傳感器數據的即時處理和分析
> - 詳見官方介紹[DeepStream SDK | NVIDIA Developer](https://developer.nvidia.com/deepstream-sdk)
白話來說,DeepStream是Nvidia是作為AI部屬平台的加速工具,主要在串接模型資料傳輸處理與協定上進行加速,特別是在多媒體串流處理上更有使用的必要性


### DeepStream 應用架構解析
[DeepStream Reference Application - deepstream-app](https://docs.nvidia.com/metropolis/deepstream/dev-guide/text/DS_ref_app_deepstream.html#application-architecture)
#### DeepStream組件

Nvidia DeepStream是一個人工智能框架,有助於利用Jetson和GPU設備中的Nvidia GPU的最終潛力來實現計算機視覺。它為Jetson Nano等邊緣設備和Jetson系列的其他設備提供動力,實時處理邊緣設備上的並行視頻流。
DeepStream使用Gstreamer流水線(用C語言編寫)在GPU中獲取輸入視頻,最終以更快的速度處理它,以便進一步處理。
- DeepStream的組成部分
DeepStream有一個基於插件的架構。基於圖形的管道接口允許高層組件互連。它可以在GPU和CPU上使用多線程進行異質並行處理(heterogeneous parallel processing)。
- 下面是DeepStream的主要組件和它們的高級功能
| 名稱 | 說明 |
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| 元數據<br>Meta Dat | 它是由圖形生成的,在圖形的每個階段都會生成。利用它,我們可以得到許多重要的字段,如檢測到的物體類型、ROI坐標、物體分類、來源等。 |
| 解碼器<br>Decoder | 解碼器有助於對輸入影片(H.264和H.265)進行解碼。它支持多數據流(multi-stream)同時解碼。它將位元深度(Bit depth)和分辨率(Resolution)作為參數。 |
| 影片聚合器<br>Video Aggregator<br>(nvstreammux) | 它有助於接受n個輸入流並將其轉換為連續的批量幀(sequential batch frames)。它使用低級別的API來訪問GPU和CPU來完成這個過程。 |
| 推理 <br>Inferencing<br>(nvinfer) | 這是用來獲得所使用的模型的推斷。所有與模型相關的工作都是通過nvinfer完成的。它還支持一級和二級模式以及各種聚類方法。 |
| 格式轉換和縮放<br>Format Conversion and Scaling<br>(nvvidconv) | 它將格式從YUV轉換為RGBA/BRGA,縮放分辨率並做圖像旋轉部分。 |
| 對象追蹤器<br>Object Tracker<br>(nvtracker) | 它使用CUDA並基於KLT參考實現。我們也可以用其他追蹤器來替換默認的追蹤器。 |
| 屏幕追蹤器<br>Screen Tiler<br>(nvstreamtiler) | 它管理輸出的視頻,即相當於open cv的imshow函數。 |
| 屏幕顯示<br>On Screen Display<br>(nvdosd) | 它管理屏幕上所有的可畫性,比如畫線、邊界框、圓圈、ROI等。 |
| 訊息轉換器和代理器<br>(nvmsgconv + nvmsgbroker) | 組合在一起,將分析資料發送到雲中的伺服器 |
(source : [Nvidia DeepStream – A Simplistic Guide](https://www.datatobiz.com/blog/nvidia-deepstream-guide/))
- Nvidia DeepStream的執行流程
Decoder -> Muxer -> Inference -> Tracker (if any) -> Tiler -> Format Conversion -> On Screen Display -> Sink
DeepStream應用程序由兩部分組成,一部分是配置文件,另一部分是其驅動文件(可以是C語言或Python語言)。
在設定檔config.txt相關的控制項的說明在[`Configuration Groups`](https://docs.nvidia.com/metropolis/deepstream/dev-guide/text/DS_ref_app_deepstream.html#deepstream-reference-application-deepstream-app)文件內
#### 使用Gstreamer 命令列檢視組件功能
這邊需要先有Gstreamer的基本概念才看得懂:slightly_smiling_face:
- [GStreamer 簡介與筆記](https://hackmd.io/@YungHuiHsu/ryhRTZpt3)
安裝完成gstreamer後,執行`gst-inspect-1.0`指令檢視模組功能,以`nvinfer`模組為例
```
gst-inspect-1.0 nvinfer
```
##### `nvinfer` 插件的屬性定義與功能

###### `Pad Templates` and `Pads` (數據傳輸接口)
- 可以見到有`src`輸出(數據生產)與`sink`輸入(數據消費)的端點
- 支援的影片格式為`video/x-raw(memory:NVMM)` 預設使用NVIDIA的GPU處理
- 可選格式為 `NV12` 與`RGBA`
- 這邊可見輸入模型預設的通道為`RGBA`

###### `Element Properties` (元素屬性)
可以見到有許多參數的設定說明,這也是後面config file中設定的參數項目

- 也可在code中取得或進行設定
- 取得 `.get_property()`
- 設定 `.set_property()`
```
# create "nvinfer" element
pgie = Gst.ElementFactory.make("nvinfer", "primary-inference")
# get "batch size"
pgie.get_property("batch-size")
# set "config file"
pgie.set_property("config-file-path", config_file_path)
```
## Python API(binding)使用入門
- [deepstream-get-started-with-python](https://resources.nvidia.com/en-us-deepstream-get-started-with-python/ds-python-sample-app)
以下主要參考官方提供的DeepStream Python Apps[NVIDIA-AI-IOT/deepstream_python_apps](https://github.com/NVIDIA-AI-IOT/deepstream_python_apps)的範例,內有詳細執行範例
- DeepStream python biding 示意圖

### 環境安裝
按[NVIDIA-AI-IOT/deepstream_python_apps/blob/master/bindings/README.md](https://github.com/NVIDIA-AI-IOT/deepstream_python_apps/blob/master/bindings/README.md)文件指引
:::spoiler
#### 補充 執行到 步驟1.3 deepstream_python_apps倉儲安裝
按以下指示安裝
當執行到 "1.3 Initialization of submodules"時
要將"deepstream_python_apps"的倉儲clone到DeepStream根目錄/source目錄下`<DeepStream 6.2 ROOT>/sources`:
~~我這邊安裝的是deepstream 6.2版,可以用 `dpkg -L deepstream-6.2`指令查找安裝位置,按Linux系統慣例果然在/opt/之下,我查到的安裝位置是:`/opt/nvidia/deepstream/deepstream-6.2/`~~
```
cd /opt/nvidia/deepstream/deepstream/sources
git clone https://github.com/NVIDIA-AI-IOT/deepstream_python_apps
```
#### 補充 執行步驟2 Compiling the bindings時出現錯誤
按照官方指示執行到2.1 Quick build (x86-ubuntu-20.04 | python 3.8 | Deepstream 6.2)時
```
cd deepstream_python_apps/bindings
mkdir build
cd build
cmake ..
make
```
按照官方預設指令先執行`make ..`,再接在執行`make`以後,會在`/opt/nvidia/deepstream/deepstream/sources/deepstream_python_apps/bindings/build`下產出 'dist/pyds-1.1.6-py3-none-linux_x86_64.whl' 檔案,後面接著執行
```
pip3 install ./pyds-1.1.6-py3-none*.whl
```
會出現錯誤訊息"ERROR: pyds-1.1.6-py3-none-linux_x86_64.whl is not a supported wheel on this platform."
- 錯誤原因分析
- 因為預設預設平台環境錯誤
> x86_64 is for dGPU, please add -DPIP_PLATFORM=linux_aarch64 when executing cmake.
> by [nvidia開發者論壇](https://forums.developer.nvidia.com/t/error-pyds-1-1-0-py3-none-linux-x86-64-whl-is-not-a-supported-wheel-on-this-platform/248952)
- 解決方案
- 執行cmake指令時,後面需指定運作的硬體環境,指令應該為 `cmake .. -DPIP_PLATFORM=linux_aarch64 `
:::
---
### 範例
- [範例檔案使用說明](https://docs.nvidia.com/metropolis/deepstream/dev-guide/text/DS_Python_Sample_Apps.html)
- 官方文件內有提供各種應用情境範例的.py檔及配置文件,包含串聯不同模型、multistream、結合Triton或直接使用本機TRT直接推論

---
## 模型轉換
### 模型轉換流程與格式
- Nvidia官方指引[Using the TensorRT Runtime API](https://docs.nvidia.com/deeplearning/tensorrt/quick-start-guide/index.html#runtime)
- 以pytorch > onnx > TensorRT 為例
- model.pt → model.onnx → ==model.engine==
- TensorRT格式有 `.engine` 與 `model.trt`兩種
- `.engine`格式
- 是TensorRT模型的序列化表示形式,它存儲了經過優化和編譯的模型和相關參數
- DeepStream 所必需的讀入格式
- `model.trt`格式
- 實際上與.engine文件相同,是.engine文件的另一種後綴名稱,可以互相轉換使用
- DeepStream無法直接使用
- 在python環境中可以透過import `tensorrt`、`pycuda`等模組讀取二進位的.trt檔,對其序列化後使用
- 在DeepStream文件設定中,指定以`model-engine-file`讀取==.engine==參數配置的必要性
:::info
第一次啟用時,DeepStream app時會根據設定文件的配置,載入.onnx檔構建 TensorRT引擎後產出model.engine檔案,此後再指定“model-engine-file=model.engine”啟動就會快很多
- 在 DeepStream 中,如果已經指定了 "onnx-file",那麽指定 "model-engine-file" 的設置通常是可選的。
- "onnx-file" 參數用於指定 ONNX 模型文件的路徑,DeepStream 將根據該文件構建 TensorRT 引擎。這種方式需要在運行時動態地將 ONNX 模型轉換為 TensorRT 引擎。這可能需要一些額外的時間和計算資源來完成模型的轉換和優化,因為在每次運行應用程序時都需要進行這個過程(在AGX Xavier要20-30min)。
當指定 "model-engine-file" 參數時,它用於指定預先構建好的 TensorRT 引擎文件的路徑。預先構建引擎意味著將 ONNX 模型轉換為 TensorRT 引擎的過程已經提前執行,引擎文件已經生成並存儲在磁盤上。在應用程序運行時,DeepStream 將直接加載該引擎文件,而無需再次進行模型轉換和優化的過程。這可以節省啟動時間並加快執行速度。
如果指定了 "model-engine-file",DeepStream 將忽略 "onnx-file" 的設置,而直接加載和使用預先構建的引擎文件
:::
### YOLOv7模型格式轉換(Onnx → TensorRT Engine)
- 參見[NVIDIA-AI-IOT/yolo_deepstream/tensorrt_yolov7](https://github.com/NVIDIA-AI-IOT/yolo_deepstream/tree/main/tensorrt_yolov7)
- 官方有提供客製的編譯檔與環境, 編譯環境準備請參考官方指示
#### 模型取得
:::spoiler [NVIDIA-AI-IOT/yolo_deepstream/yolov7_qat](https://github.com/NVIDIA-AI-IOT/yolo_deepstream/tree/main/yolov7_qat)
- [NVIDIA-AI-IOT/yolo_deepstream/yolov7_qat](https://github.com/NVIDIA-AI-IOT/yolo_deepstream/tree/main/yolov7_qat)可直接下載量化過後的int8版本
- [x] Quantization Aware Training(QAT-INT8)訓練過的模型版本(`yolov7_qat_640.onnx`)
- [Model Quantization Note 模型量化筆記](https://hackmd.io/riYLcrp1RuKHpVI22oEAXA)
下載後下一步要在自己的硬體平台先轉為TensorRT Engine格式
:::
#### 準備 TensorRT engines
:::spoiler convert onnx model(.onnx) to TensorRT-engine(.engine )
- 轉換方式參考自[NVIDIA-AI-IOT/yolo_deepstream/ tensorrt_yolov7](https://github.com/NVIDIA-AI-IOT/yolo_deepstream/tree/main/tensorrt_yolov7))
- 由於後面流程要串接多路攝影機,因此我選擇轉為動態批次的設定
```
# int8 QAT model, the onnx model with Q&DQ nodes
/usr/src/tensorrt/bin/trtexec --onnx=yolov7_qat_640.onnx \
--saveEngine=yolov7QAT_640.engine --fp16 --int8
# if you want dynamic_patch for batch inference
/usr/src/tensorrt/bin/trtexec --onnx=yolov7_qat_640.onnx \
--minShapes=images:1x3x640x640 \
--optShapes=images:12x3x640x640 \
--maxShapes=images:16x3x640x640 \
--saveEngine=yolov7QAT_640.engine --fp16 --int8
```
- 如果後面串接的數量固定
- '--minShapes'、'--optShapes'、'--maxShapes'都設為一致,似乎可以開啟NV的[DLA (Deep Learning Accelerator)](https://docs.nvidia.com/deeplearning/tensorrt/developer-guide/index.html#dla_topic)加速(待確認)
:::
---
## 使用DeepStream部署YOLO系列模型
主要流程參照NVIDIA官方文件[NVIDIA-AI-IOT/yolo_deepstream](https://github.com/NVIDIA-AI-IOT/yolo_deepstream/tree/main/deepstream_yolo)
另外這篇非官方的導覽也可以參考[marcoslucianops/DeepStream-Yolo/customModels](https://github.com/marcoslucianops/DeepStream-Yolo/blob/master/docs/customModels.md)
### 手動修改DeepStream Python Binding範例文件,並指定使用客製模型(YOLOv7)
- 資料配置方式,在`/opt/nvidia/deepstream/deepstream`目錄內

以下分兩部分來說明:
#### 1. DeepStream 模型檔案配置與客製模型編譯
這裡採取的作法是把客製模型放在模型專用目錄下,方便未來其他專案重複取用
`samples/models/tao_pretrained_models/yolov7/`
- 編譯過程詳見[NVIDIA-AI-IOT/yolo_deepstream/deepstream_yolo](https://github.com/NVIDIA-AI-IOT/yolo_deepstream/tree/main/deepstream_yolo)內的操作指示
:::spoiler
- 詳細編譯過程與檔案配置位置
:::spoiler
- 編譯與設定文件取得
- 為方便閱讀,這邊將`/opt/nvidia/deepstream/deepstream/samples/models/tao_pretrained_models/yolov7`指定為`path_yolov7`變數,方便之後取用
```
cd ~/
sudo git clone https://github.com/NVIDIA-AI-IOT/yolo_deepstream.git
path_yolov7=/opt/nvidia/deepstream/deepstream/samples/models/tao_pretrained_models/yolov7
sudo mmkdir $path_yolov7
sudo mcp -vr /yolo_deepstream/deepstream_yolo/* $path_yolov7/
```
這時可以看到`nvdsinfer_custom_impl_Yolo/`內的資料結構包含3個檔案
```
nvdsinfer_custom_impl_Yolo/
├── Makefile # 用於編譯程式碼的。包含了編譯和建構這個自訂推論實現所需的指令和規則。
├── nvdsparsebbox_Yolo.cpp # C++ 檔案,包含了用於解析和處理YOLO模型的邊界框的程式碼
├── nvdsparsebbox_Yolo_cuda.cu # CUDA 檔案,包含了在 GPU 上執行的加速程式碼
# 針對 YOLO 模型的邊界框解析進行加速計算的 CUDA 程式碼
# YOLO post-processing(decoce yolo result, not include NMS)
```
進入`nvdsinfer_custom_impl_Yolo`目錄內開始編譯
```
cd $path_yolov7/nvdsinfer_custom_impl_Yolo
sudo make
cd ..
```
編譯成功後,就會得到1個主要的.so檔及2個編譯好的物件檔案(.o)
```
nvdsinfer_custom_impl_Yolo/
├── Makefile
├── nvdsparsebbox_Yolo.cpp
├── nvdsparsebbox_Yolo_cuda.cu
├── libnvdsinfer_custom_impl_Yolo.so # 共享程式庫(shared library),用於執行時的動態連結。
# 這個程式庫提供了用於推論(inference)的自訂實現
├── nvdsparsebbox_Yolo_cuda.o # 已編譯的 CUDA 目標檔案(object file),用於在連結階段
# 將 CUDA 代碼與其他目標檔案一起連結成最終的執行檔案或共享程式庫
└── nvdsparsebbox_Yolo.o # 已編譯的 C++ 目標檔案,用於在連結階段將 C++ 程式碼
# 與其他目標檔案一起連結成最終的執行檔案或共享程式庫
```
:::
:::
#### 2. DeepStream app(python API)與配置文件
python biding的範例檔位於`/opt/nvidia/deepstream/deepstream/sources/deepstream_python_apps/apps`
##### 2.1 這邊以[`deepstream_test1_rtsp_in_rtsp_out/`](https://github.com/NVIDIA-AI-IOT/deepstream_python_apps/tree/master/apps/deepstream-rtsp-in-rtsp-out)為例
- [修改的檔案下載處](https://github.com/YunghuiHsu/deepstream_python_apps/blob/master/apps/deepstream-rtsp-in-rtsp-out/README)
- `deepstream_test1_rtsp_in_rtsp_out_getconf.py`
- `dstest1_pgie_inferserver_config.txt`
:::spoiler
- 資料輸入
- 這個範例中可以使用rtsp(使用`rtsp://`或`file:/`)讀入多種格式的影片檔案(.h264、.mp4、.mov等)
- 資料輸出
- 使用rtsp接收
- 本機(直接在server/jetson上檢視)
- 在瀏覽器輸入`rtsp://localhost:8554/ds-test`
- 遠端連線檢視
- 我這邊使用的方案是VLC Player
- 在"媒體/開啟網路"串流的設定中輸入指定的位址`rtsp://<your_server_ip>:8554/ds-test`
- 相關設定參考[dusty-nv/jetson-inference/Camera Streaming and Multimedia](https://github.com/dusty-nv/jetson-inference/blob/master/docs/aux-streaming.md)
- 
- 修改[`deepstream_test1_rtsp_in_rtsp_out.py`](https://github.com/NVIDIA-AI-IOT/deepstream_python_apps/blob/master/apps/deepstream-rtsp-in-rtsp-out/deepstream_test1_rtsp_in_rtsp_out.py#305)讀取設定檔的路徑
:::spoiler
```python=305
pgie.set_property("config-file-path", config_file_path)
```
```python=397
def parse_args():
parser.add_argument("-config", "--config-file", default='dstest1_pgie_config.txt',
help="Set the config file path", type=str)
args = parser.parse_args()
global config_file_path
config_file_path = args.config_file
```
:::
:::
##### 2.2 模型配置文件修改
- 參照[NVIDIA-AI-IOT/yolo_deepstream/deepstream_yoloconfig_infer_primary_yoloV7.txt](https://github.com/NVIDIA-AI-IOT/yolo_deepstream/blob/main/deepstream_yolo/config_infer_primary_yoloV7.txt)的文件配置建立`dstest1_pgie_yolov7_config.txt`
:::spoiler pgie_config.txt 客製模型配置文件說明

- pgie_config.txt 客製模型配置文件說明
以下說明改動部分,主要是跟模型存放路徑有關
- 客製模型檔案路徑
為方便閱讀,這邊以your_model_path 代替完整路徑`/opt/nvidia/deepstream/deepstream/samples/models/tao_pretrained_models/yolov7/`
* `onnx-file=your_model_path/yolov7.onnx`
* `model-engine-file= your_model_path/yolov7.onnx_b16_gpu0_fp16.engine`
* 在第一次載入model.onnx後,DeepStream會動態地將 ONNX 模型轉換為 TensorRT 引擎(AGX Xavier耗時2-30min),然後自動產出已命名好的.engine檔案
* `labelfile-path=your_model_path/labels.txt`
- 模型設定
* `batch-size `
* 模型轉為.onnxe格式時記得要開啟動態批次(),詳見前文[TensorRT轉檔啟用動態批次 Dynamic_batch](https://hackmd.io/_oaJhYNqTvyL_h01X1Fdmw?both)說明
* ~~至於在deepstream中該設多大呢? 參考前人經驗並非越大越好,還需要實際測試,但可以先抓跟你要輸入的camera數量接近~~
* 根據官方文件對於效能提升的提示[DeepStream best practices](https://docs.nvidia.com/metropolis/deepstream/dev-guide/text/DS_ref_app_deepstream.html?highlight=batch%20processing#deepstream-best-practices),批次大小設置與輸入源(batch == sources)一致,達到的效能提升效果最好
* `parse-bbox-func-name=NvDsInferParseCustomYoloV7_cuda` # 使用cuda做後處裡
* `custom-lib-path=your_model_path/nvdsinfer_custom_impl_Yolo/libnvdsinfer_custom_impl_Yolo.so`
* 這邊放預先`make`編譯好的.so檔
* 詳細編譯過程與檔案配置見"DeepStream 模型檔案配置與編譯"小節
:::
:::spoiler dstest1_pgie_yolov7_config.txt 詳細配置說明
```plaintext=
[property]
gpu-id=0
# 使用的GPU設備ID,這裡設定為0
net-scale-factor=0.0039215697906911373
# 圖像預處理的縮放因子,將像素值轉換為0到1之間的浮點數,這個值等於1/255
model-engine-file=../../../../samples/models/tao_pretrained_models/yolov7/yolov7.onnx_b16_gpu0_fp16.engine
# 模型引擎文件的路徑和文件名,這是已經編譯好的TensorRT引擎文件
onnx-file=../../../../samples/models/tao_pretrained_models/yolov7/yolov7.onnx
# 原始ONNX模型文件的路徑和文件名
labelfile-path=../../../../samples/models//tao_pretrained_models/yolov7/labels.txt
# 包含類別標籤的文件路徑和文件名,每行包含一個類別標籤
force-implicit-batch-dim=1
# 強制隱式批次維度為1,用於不支援顯式批次維度的模型
batch-size=1
#batch-size=12
# 模型的批次大小,即一次送入模型推論的圖像數量
# 如果模型格式沒有設定動態批次的話請指定為1
# 批次大小應與輸入的stream數量相等以取得較好的推理效能
## 0=FP32, 1=INT8, 2=FP16 mode
network-mode=2
# 網路模型的運算精度模式,這裡設定為FP16模式
num-detected-classes=80
# 模型能夠檢測的目標類別數量
gie-unique-id=1
# DeepStream GIE (GPU Inference Engine) 的唯一ID
network-type=0
# 網路模型的類型,這裡設定為0,表示物件檢測模型
#is-classifier=0
# 是否為分類器模型的標誌,這裡註解掉了,因此不使用分類器模型
## 1=DBSCAN, 2=NMS, 3=DBSCAN+NMS Hybrid, 4=None(No clustering)
cluster-mode=2
# 物件聚類模式的設定,這裡設定為NMS模式
maintain-aspect-ratio=1
# 是否保持圖像的長寬比例,這裡設定為保持比例
symmetric-padding=1
# 是否對圖像進行對稱填充,這裡設定為對稱填充
## Bilinear Interpolation
scaling-filter=1
# 圖像縮放時使用的插值方法,這裡設定為雙線性插值
#parse-bbox-func-name=NvDsInferParseCustomYoloV7
parse-bbox-func-name=NvDsInferParseCustomYoloV7_cuda
# 物件檢測結果解析的函數名稱,這裡設定為NvDsInferParseCustomYoloV7_cuda
#disable-output-host-copy=0
#disable-output-host-copy=1
# 是否禁用主機複製輸出,這裡註解掉了,因此未禁用複製輸出
custom-lib-path=../../../../samples/models/tao_pretrained_models/yolov7/nvdsinfer_custom_impl_Yolo/libnvdsinfer_custom_impl_Yolo.so
# 自定義的物件檢測實現庫的路徑和文件名
#scaling-compute-hw=0
# 圖像縮放計算硬體的設定,這裡註解掉了,因此未設定
## start from DS6.2
crop-objects-to-roi-boundary=1
# 是否將物件裁剪到ROI邊界,這裡設定為是
[class-attrs-all]
#nms-iou-threshold=0.3
#threshold=0.7
nms-iou-threshold=0.65
# 非最大抑制 (NMS) 的IoU閾值,用於去除重疊的檢測框
pre-cluster-threshold=0.25
# 物件聚類前的閾值,用於過濾低置信度的檢測框
topk=300
# 每張圖像最多保留的檢測框數量
```
:::
---
## Case Study
[deepstream sdk-api](https://docs.nvidia.com/metropolis/deepstream/dev-guide/sdk-api/struct\_\_NvDsUserMeta.html)
### 範例 :讓螢幕畫面的類別標籤展示信任分數confidence scores
完整的程式碼放在[YunghuiHsu/deepstream_python_apps/apps/deepstream-rtsp-in-rtsp-out](https://github.com/YunghuiHsu/deepstream_python_apps/blob/master/apps/deepstream-rtsp-in-rtsp-out)
流程如示意圖,主要在Metadata進入`nvdosd`物件(負責螢幕顯示工作)前加入Probe,告知`nvdosd`該如何顯示想要的資訊,螢幕追蹤資訊要如何呈現,則在`def tiler_src_pad_buffer_probe()`中定義


#### 取出並顯示信賴分數(Confidence score)
##### 在`def tiler_src_pad_buffer_probe()`中撈出meta資料
:::spoiler
- `obj_meta.text_params.display_text` : 顯示meta資料文字
- `obj_meta.confidence` : meta物件資料中預定義提取信賴分數的關鍵字
- 當在設定檔中指定為偵測模型時,會解析偵測模型類別的meta物件格式
- [`deepstream_test1_rtsp_in_rtsp_out_getconf.py`](https://github.com/YunghuiHsu/deepstream_python_apps/blob/master/apps/deepstream-rtsp-in-rtsp-out/deepstream_test1_rtsp_in_rtsp_out_getconf.py#L112)
```python=
def tiler_src_pad_buffer_probe(pad, info, u_data):
batch_meta = pyds.gst_buffer_get_nvds_batch_meta(hash(gst_buffer))
l_frame = batch_meta.frame_meta_list
while l_frame is not None:
try:
frame_meta = pyds.NvDsFrameMeta.cast(l_frame.data)
except StopIteration:
break
l_obj = frame_meta.obj_meta_list
while l_obj is not None:
# Casting l_obj.data to pyds.NvDsObjectMeta
obj_meta = pyds.NvDsObjectMeta.cast(l_obj.data)
msg = f"{class_names[obj_meta.class_id]:s}"
msg += f" {obj_meta.confidence:3.2f}"
obj_meta.text_params.display_text = msg
```
接下來還需要加入探針才能更新要顯示的資訊
:::
##### 在`nvdosd`物件接口前加入探針(probe)更新要顯示的資訊
:::spoiler
- [`deepstream_test1_rtsp_in_rtsp_out_getconf.py`](https://github.com/YunghuiHsu/deepstream_python_apps/blob/master/apps/deepstream-rtsp-in-rtsp-out/deepstream_test1_rtsp_in_rtsp_out_getconf.py#L411)
```python=
# Add probe to get informed of the meta data generated, we add probe to
# the sink pad of the osd element, since by that time, the buffer would have
# had got all the metadata.
# either nvosd.get_static_pad("sink") or pgie.get_static_pad("src") works
osdsinkpad = nvosd.get_static_pad("sink")
if not osdsinkpad.
sys.stderr.write(" Unable to get sink pad of nvosd \n")
osdsinkpad.add_probe(Gst.PadProbeType.BUFFER, tiler_src_pad_buffer_probe, 0)
```
- 執行範例
```
python3 deepstream_test1_rtsp_in_rtsp_out_getconf.py \
-i file:///opt/nvidia/deepstream/deepstream/samples/streams/sample_720p.h264 \
file:///opt/nvidia/deepstream/deepstream/samples/streams/sample_qHD.mp4 \
-config dstest1_pgie_yolov7_config.txt
```
- Reference for display confidence scores
- [How to include YOLOv4 confidence score in the deepstream-app output?](https://forums.developer.nvidia.com/t/how-to-include-yolov4-confidence-score-in-the-deepstream-app-output/214613)
- [https://forums.developer.nvidia.com/t/how-to-display-confidence-with-label-in-deepstream-like-person-0-81/199878/9](https://forums.developer.nvidia.com/t/how-to-display-confidence-with-label-in-deepstream-like-person-0-81/199878/9)
:::
### 自定義傳遞訊息資料
#### 使用NvDsEventMsgMeta物件
- 可用於跟server間溝通傳遞訊息
<div style="text-align: center;">
<figure>
<img src="https://docs.nvidia.com/metropolis/deepstream/dev-guide/sdk-api/structNvDsEventMsgMeta__coll__graph.png" alt="structNvDsEventMsgMeta__coll__graph.png" width="8900">
<figcaption>structNvDsEventMsgMeta</figcaption>
</figure>
</div>
::: spoiler NvDsEventMsgMeta 資料結構中有pose的定義
- `/opt/nvidia/deepstream/deepstream-6.2/sources/includes/nvdsmeta\_schema.h`
```c++=
/**
* Holds event message meta data.
*
* You can attach various types of objects (vehicle, person, face, etc.)
* to an event by setting a pointer to the object in @a extMsg.
*
* Similarly, you can attach a custom object to an event by setting a pointer to the object in @a extMsg.
* A custom object must be handled by the metadata parsing module accordingly.
*/
typedef struct NvDsEventMsgMeta {
/** Holds the event's type. */
NvDsEventType type;
/** Holds the object's type. */
NvDsObjectType objType;
/** Holds the object's bounding box. */
NvDsRect bbox;
/** Holds the object's geolocation. */
NvDsGeoLocation location;
/** Holds the object's coordinates. */
NvDsCoordinate coordinate;
/** Holds the object's signature. */
NvDsObjectSignature objSignature;
/** Holds the object's class ID. */
gint objClassId;
/** Holds the ID of the sensor that generated the event. */
gint sensorId;
/** Holds the ID of the analytics module that generated the event. */
gint moduleId;
/** Holds the ID of the place related to the object. */
gint placeId;
/** Holds the ID of the component (plugin) that generated this event. */
gint componentId;
/** Holds the video frame ID of this event. */
gint frameId;
/** Holds the confidence level of the inference. */
gdouble confidence;
/** Holds the object's tracking ID. */
guint64 trackingId;
/** Holds a pointer to the generated event's timestamp. */
gchar *ts;
/** Holds a pointer to the detected or inferred object's ID. */
gchar *objectId;
/** Holds a pointer to a string containing the sensor's identity. */
gchar *sensorStr;
/** Holds a pointer to a string containing other attributes associated with
the object. */
gchar *otherAttrs;
/** Holds a pointer to the name of the video file. */
gchar *videoPath;
/** Holds a pointer to event message meta data. This can be used to hold
data that can't be accommodated in the existing fields, or an associated
object (representing a vehicle, person, face, etc.). */
gpointer extMsg;
/** Holds the size of the custom object at @a extMsg. */
guint extMsgSize;
/** Holds the object's pose information */
NvDsJoints pose;
/** Holds the object's embedding information */
NvDsEmbedding embedding;
} NvDsEventMsgMeta;
```
:::
### 自定義修改及撈取MetaData
關於客製meta data存放、修改與撈出
#### 使用自定義 NvDsUserMeta物件
<div style="text-align: center;">
<figure>
<img src="https://docs.nvidia.com/metropolis/deepstream/dev-guide/sdk-api/struct__NvDsUserMeta__coll__graph.png" alt="struct__NvDsUserMeta__coll__graph.png" width="500">
<figcaption>NvDsUserMeta</figcaption>
</figure>
</div>
#### NvDsInferTensorMeta
在`[Gst-nvinfer`\]階段,可直接從TensorRT inference engine讀取原始(預測的)輸出張量,轉為meta格式
<div style="text-align: center;">
<figure>
<img src="https://docs.nvidia.com/metropolis/deepstream/dev-guide/sdk-api/structNvDsInferTensorMeta__coll__graph.png" alt="structNvDsInferTensorMeta__coll__graph.png" width="300">
<figcaption>NvDsInferTensorMeta</figcaption>
</figure>
</div>
### 從Deepstream Buffer中取出影像與meta資料進行客製操作
* python biding相關範例見[apps/deepstream-imagedata-multistream](https://github.com/NVIDIA-AI-IOT/deepstream_python_apps/tree/master/apps/deepstream-imagedata-multistream)
從流程圖中可見,分別從FRAME BUFFER與<INFERENCE>模塊撈出影像與模型預測的張量
<div style="text-align: center;">
<figure>
<img src="https://github.com/NVIDIA-AI-IOT/deepstream_python_apps/blob/master/apps/deepstream-imagedata-multistream-redaction/imagedata-app-block-diagram.png?raw=true" alt="imagedata-app-block-diagram.png" width="800">
<figcaption>imagedata diagram</figcaption>
</figure>
</div>
- 更詳細解說見 [Use Deepstream python API to extract the model output tensor and customize model post-processing (e.g., YOLO-Pose)<br>使用Deepstream python API提取模型輸出張量並定製模型后處理(如:YOLO-Pose)](https://hackmd.io/@YungHuiHsu/rk41ISKY2)
#### NvBufSurface
NvBufSurface 是 NVIDIA DeepStream SDK 中的一個結構,用於表示經過解碼和處理的視訊幀的圖像資料
<div style="text-align: center;">
<figure>
<img src="https://docs.nvidia.com/metropolis/deepstream/sdk-api/structNvBufSurface__coll__graph.png" alt="structNvBufSurface__coll__graph.png" width="500">
<figcaption>structNvBufSurface</figcaption>
</figure>
</div>
:::spoiler
NvBufSurface 的功能如下:
* 維護視訊幀的元數據,如圖像的寬度、高度、畫素格式等。
* 提供了對視訊幀數據的訪問和操作接口,如讀取和寫入像素值、設置和獲取 ROI(Region of Interest)等。
* 支持對視訊幀數據進行硬件加速處理,如 GPU 轉換和編碼等。
:::
##### code 範例(c++)
:::spoiler code 範例(c++)
```c++
#include <nvbufsurftransform.h>
void process_nvbufsurface(NvBufSurface *surface) {
// 獲取視訊幀的元數據
int width = surface->surfaceList[0].width;
int height = surface->surfaceList[0].height;
int pitch = surface->surfaceList[0].pitch[0];
NvBufColorFormat colorFormat = surface->surfaceList[0].colorFormat;
// 設置 ROI
NvBufSurfTransformRect src_rect;
src_rect.top = 0;
src_rect.left = 0;
src_rect.width = width;
src_rect.height = height;
NvBufSurfTransformRect dst_rect = src_rect;
// 讀取像素值
unsigned char *buffer = surface->surfaceList[0].mappedAddr.addr[0];
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
unsigned char pixel_value = buffer[row * pitch + col];
// 對像素值進行處理
// ...
}
}
// 寫入像素值
for (int row = 0; row < height; row++) {
for (int col = 0; col < width; col++) {
buffer[row * pitch + col] = 255; // 將所有像素設置為白色
}
}
// 釋放視訊幀資源
NvBufSurfaceParams params;
memset(¶ms, 0, sizeof(params));
params.gpuId = surface->surfaceList[0].gpuId;
params.width = width;
params.height = height;
params.pitch = pitch;
params.colorFormat = colorFormat;
params.nvbuf_tag = surface->surfaceList[0].nvbuf_tag;
nvbufsurface_dma_unmap(&surface->surfaceList[0], -1, -1);
nvbufsurface_free(surface);
}
```
:::
---
## DeepStream效能優化的基本原則
:::spoiler
### DeepStream best practices
以下羅列幾項基本設置,更多請參考官方文件對於效能提升的提示[DeepStream best practices](https://docs.nvidia.com/metropolis/deepstream/dev-guide/text/DS_ref_app_deepstream.html?highlight=batch%20processing#deepstream-best-practices)
- 批次大小設置為等於輸入源(batch == sources)
- streammux的高度和寬度設置為輸入解析度
- 如果從RTSP 從USB 傳輸,配置文件的[streammux]設置live-source=1,可以確保正確的時間戳記
- 視覺輸出(Tiling and visual output )會占用GPU資源。在不需要在屏幕上渲染輸出時,以下三個方法可以禁用以最大限度提高吞吐量
- 關閉OSD或屏幕顯示
- 在配置文件將[osd]參數中設置enable=0
- 平鋪器(tiler)為顯示輸出流創建了一個NxM網格
- 將 [tiled-display]參數中設置enable=0
- 關閉輸出接收器(output sink)
- 將[sink]參數選擇fakesink,即type=1
### Jetson optimization
確保Jetson時脈開至最大
```
$ sudo nvpmodel -m <mode> --for MAX perf and power mode is 0
$ sudo jetson_clocks
```
:::
---
## 參考資料
### NVIDIA DeepStream官方文件
- [DeepStream SDK | NVIDIA Developer](https://developer.nvidia.com/deepstream-sdk)
- 官方文件及載點
- docker [DeepStream-l4t](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/deepstream-l4t)
- [範例檔案使用說明](https://docs.nvidia.com/metropolis/deepstream/dev-guide/text/DS_Python_Sample_Apps.html)
- 官方文件內有提供各種應用情境範例的.py檔及配置文件,包含串聯不同模型、multistream、結合Triton或直接使用本機TRT直接推論

- 部署YOLO模型的文件配置[NVIDIA-AI-IOT/yolo_deepstream](https://github.com/NVIDIA-AI-IOT/yolo_deepstream/tree/main/deepstream_yolo)
- [Building a Real-time Redaction App Using NVIDIA DeepStream, Part 2: Deployment | NVIDIA Technical Blog](https://developer.nvidia.com/blog/real-time-redaction-app-nvidia-deepstream-part-2-deployment/)
#### NVIDIA 官方範例程式
- [NVIDIA-AI-IOT/deepstream_python_apps: DeepStream SDK Python bindings and sample applications (github.com)](https://github.com/NVIDIA-AI-IOT/deepstream_python_apps)
- 提供python接口的範例
- [NVIDIA Jetson Nano 2GB 系列文章(35):Python版test1实战说明](https://zhuanlan.zhihu.com/p/415054673)
#### 其他不錯的DeepStream概念介紹
- [2022。Galliot。NVIDIA DeepStream Python Bindings; Customize your Applications](https://galliot.us/blog/deepstream-python-bindings-customization/)

Figure 2: A DeepStream Pipeline with two processing elements
- [2021。Kavika Roy。Nvidia DeepStream — A Simplistic Guide](https://www.datatobiz.com/blog/nvidia-deepstream-guide/)
#### NVIDIA 教學影片
##### [2023/01。NVIDIA DeepStream Technical Deep Dive: DeepStream Inference Options with Triton & TensorRT](https://www.youtube.com/watch?v=eM4nKWy6anA)
:::spoiler
- outline
1. 使用DeepStream的推理選項來處理Tensorflow、Pytorch和ONNX模型。
2. 與TensorRT和DeepStream合作,進行優化模型。
3. 使用Triton服務器來支持單個或多個DeepStream管道。
4. 使用DeepStream的前/後處理插件。
###### 帶有DS-TensorRT(gst-nvinfer)插件的DeepStream-app設置
- 應用程序和插件的配置文件
- DS-TRT首次將ONNX/TAO/Caffe模型在線轉化為TensorRT引擎文件

###### DeepStream-app with DS-Triton (gst-nvinferserver) Server CAPI
* 推理方法 1(本機):
* ==Triton Server CAPI: 在單一程序(Process)中直接加載模型Repo==

###### DeepStream-app with DS-Triton (gst-nvinferserver) gRPC Inference
* 推理方法 2(遠端):
* ==Triton gRPC Remote: 通過gRPC向遠程tritonserver-app發送INPUT並等待響應==

| | Inference Approach 1:<br> Triton Server CAPI | Inference Approach 2:<br> Triton gRPC Remote |
|:---- |:----------------------------------------------------------------------:|:----------------------------------------------------------------------------- |
| 優點 | - 直接在本機加載模型並使用,效能較佳<br>- 不需要通過網絡傳輸數據 | - 可以將模型部署在遠程服務器上(雲端)<br>- 可以使用Triton Server提供的所有特性 |
| 缺點 | - 受限於單個進程中的記憶體和處理能力<br>- 不支持在遠程服務器上運行模型 | - 需要通過網絡傳輸數據,可能會影響推理性能<br>- 遠程服務器必須支持gRPC協議 |
- 在NVIDIA Triton Inference Server中,"CAPI"和"gRPC"都是指不同的推理模式。
- CAPI
- 代表“C API”。這是一種本地推理模式,其中應用程序直接使用Triton的C++推理庫來加載和運行模型。這種推理模式通常用於將推理服務嵌入到應用程序中,以便實現最佳性能和低延遲。在這種模式下,模型存儲庫直接加載到應用程序進程中,因此可以避免網絡傳輸和通信開銷。
- gRPC
- 代表“general-purpose Remote Procedure Call”。這是一種遠程推理模式,其中客戶端通過網絡連接到遠程Triton推理服務器,使用gRPC協議發送輸入數據,並等待服務器返回推理結果。在這種模式下,應用程序不需要在本地加載模型,因為模型存儲庫是由Triton推理服務器加載和管理的。這種模式通常用於客戶端和服務器之間的跨網絡推理,例如在雲上運行的推理服務。
###### 推理前的DeepStream批(Batching)處理
* `nvstreammux`在推理前將所有的輸入數據流分批輸入 在推理前一起進行
* `nvstreammux`的批處理策略適用於兩個推理插件
* DS-Triton(gst-nvinferserver)插件
* DS-TensorRT (gst-nvinfer) 插件
* 通過配置文件設置批量(batch)大小


###### 解析推理數據的DeepStream示例應用程序


###### DeepStream Triton推理數據流
- 
:::
##### [2022/06。NVIDIA DeepStream Technical Deep Dive : Multi-Object Tracker](https://www.youtube.com/watch?v=4nV-GtqggEw)
##### [2021/01。Implementing Real-time Vision AI Apps Using NVIDIA DeepStream SDK](https://www.youtube.com/watch?v=hSegX0P170s)
### C++ python bindings
#### [python-bindings-overview](https://realpython.com/python-bindings-overview/)
- 中文版 [Python Bindings - 从 Python 调用 C/C++](https://zhuanlan.zhihu.com/p/143356193)
- [给 Python 算法插上性能的翅膀——pybind11 落地实践](https://zhuanlan.zhihu.com/p/444805518)
- [Deepstream Python官方範例解說](https://blog.csdn.net/zyctimes/article/details/122601921)
### YOLO - DeepStream
以下幾個倉儲有提供C++的範例程式,不過如果要結合PYTHON BINDING的範例程式執行,要手動修改的部分還蠻複雜的
#### GITHUB
- [NVIDIA-AI-IOT/yolo_deepstream](https://github.com/NVIDIA-AI-IOT/yolo_deepstream)
- 官方範例檔
- 提供C++的範例程式
- [marcoslucianops/DeepStream-Yolo](https://github.com/marcoslucianops/DeepStream-Yolo)
- 非官方範例,有提供YOLO系列架構支援
- 提供C++的範例程式
- 說明與支援較完整
- [visualcortex-official/yolov7-deepstream](https://github.com/visualcortex-official/yolov7-deepstream)
- 提供整合`efficientNMS`外掛的支援
#### Forum
- [Tutorial: How to run YOLOv7 on Deepstream](https://forums.developer.nvidia.com/t/tutorial-how-to-run-yolov7-on-deepstream/229045)
- 有討論到如何在多媒體串流範例程式上如何修改 deepstream_python_apps/apps/deepstream-rtsp-in-rtsp-out at master · NVIDIA-AI-IOT/deepstream_python_apps
- [Deepstream python app yolov7 integration issue](https://forums.developer.nvidia.com/t/deepstream-python-app-yolov7-integration-issue/246034)
- `python3 deepstream_test_1.py /opt/nvidia/deepstream/deepstream-6.2/samples/streams/sample_720p.h264`
:::spoiler
> - dstest1_pgie_config.txt → from config_infer_primary_yoloV7.txt 1 under yolo_deepstream/deepstream_yolo at main · NVIDIA-AI-IOT/yolo_deepstream · GitHub
> - nvdsinfer_custom_impl_Yolo 1 → from nvdsinfer_custom_impl_Yolo 1 under yolo_deepstream/deepstream_yolo at main · NVIDIA-AI-IOT/yolo_deepstream · GitHub
> - labels.txt → from labels.txt under yolo_deepstream/deepstream_yolo at main · NVIDIA-AI-IOT/yolo_deepstream · GitHub
> - yolov7.onnx → from yolov7.onnx under yolo_deepstream/yolov7_qat at main · NVIDIA-AI-IOT/yolo_deepstream · GitHub
:::
#### GStreamer
- [GStreamer 簡介與筆記](https://hackmd.io/@YungHuiHsu/ryhRTZpt3)
DeepStream的功能是建基於GStreamer上,所以在使用前最好對後者的原理有點概念會比較好修改
[gstreamer/documentation](https://gstreamer.freedesktop.org/documentation/)
#### [Deploy YOLOv8 on NVIDIA Jetson using TensorRT and DeepStream SDK](https://wiki.seeedstudio.com/YOLOv8-DeepStream-TRT-Jetson/)