Jetson Nanoでyolov7-tinyを動かしてみた

# はじめに
[AMBL株式会社](https://www.ambl.co.jp/) AI開発事業部の見原です。
この記事は、AMBL株式会社 [Advent Calendar](https://qiita.com/advent-calendar/2022/ambl-rd) 2022の19日目の記事になります。  
業務では画像系のエッジAIアプリの開発を行っています。
この記事では、Jetsonにyolov7-tinyを組込んでDeepStream上でリアルタイムに物体検出を行う方法を紹介します。参考になれば幸いです。

# 目的
yolov7をJetsonのDeepStream上で動かせるようにする。サンプルアプリdeepstream-appを使ってyolov7で推論を行う。

# 環境
- Jetson Nano 4GB
- Jetpack 4.5 
- DeepStream SDK 5.1

# Jetsonとは？
- NVIDIAが開発した小型コンピュータ
- NVIDIAのGPUが搭載されている

<img width="300" alt="PXL_20221211_050420330" src="https://user-images.githubusercontent.com/65023550/206888562-6710308d-6a9e-4a88-9005-a7cff4ed1fd3.jpg">

# DeepStreamとは？
+ GStreamerをベースにしたストリーム分析ツールキット
+ AIを使ったビデオ解析、音声分析、画像分析のためのパイプラインを構築できる
+ C/C++またはPythonで開発できる（Graph ComposerというUIを使った開発もできるらしい）

<img width="600" alt="スクリーンショット 2022-12-11 15 03 53" src="https://user-images.githubusercontent.com/65023550/206888961-ac57008a-4b36-4ff4-a712-fb3204070770.png">

# 組込み

組込み手順  
1. モデルのTensorRT化: モデルをDeepStreamで使える形式に変換する。デバイスに合わせてモデルを高速化する。
2. コンフィグファイルの作成: 前処理の設定などを行う。
3. パーサー作成: 後処理(NMS処理など)を行うファイルを作成する。
4. サンプルアプリ実行

## 1. モデルのTensorRT化
.pt -> .wts -> .engineの順に変換する。　　
.wtsファイルまではgoogle colabで変換し、.engineファイルへの変換はJetsonで行う。  　　
以下のgithubを参考に行う。
https://github.com/wang-xinyu/tensorrtx/tree/master/yolov7
### .ptファイルの変換

まず必要なリポジトリをクローンする。
```
!git clone https://github.com/WongKinYiu/yolov7
!git clone https://github.com/wang-xinyu/tensorrtx.git -b yolov5-v6.2
```
以下のリンクから事前学習済みモデルyolov7-tiny.ptをダウンロードし、yolov7フォルダに保存する。
https://github.com/WongKinYiu/yolov7/releases/download/v0.1/yolov7-tiny.pt

gen_wts.pyファイルをyolov7下にコピーする。
```
!cp tensorrtx/yolov7/gen_wts.py yolov7
```
以下のように変換前の.ptファイルと変換後の.wtsファイルを指定して実行する。
```
%cd yolov7
!python3 gen_wts.py -w yolov7-tiny.pt -o yolov7-tiny.wts
```

yolov7下にyolov7-tiny.wtsが生成する。

### .wtsファイルの変換
Jetsonで行う。
以下のリポジトリをダウンロードし、yolov7フォルダをホームディレクトリにコピーする。
https://github.com/mihara-shoko/yolov7_tensorrt
buildフォルダを作成し、yolov7-tiny.wtsファイルをbuildフォルダにコピーする。
yololayer.hの"static constexpr int INPUT_H"と"static constexpr int INPUT_W"を416に変更する。
```yololayer.h
    static constexpr int CHECK_COUNT = 3;
    static constexpr float IGNORE_THRESH = 0.5f;
    struct YoloKernel
    {
        int width;
        int height;
        float anchors[CHECK_COUNT * 2];
    };
    static constexpr int MAX_OUTPUT_BBOX_COUNT = 1000;
    static constexpr int CLASS_NUM = 80;
    static constexpr int INPUT_H = 416;  // <-変更
    static constexpr int INPUT_W = 416;  // <-変更
```
buildフォルダに移動し、以下を実行する。
```
cmake ..
make
sudo ./yolov7 -s yolov7-tiny.wts yolov7-tiny.engine t
```

buildフォルダにyolov7-tiny.engineが生成する。これがTensorRT化されたモデル。


## 2. コンフィグファイルの作成
前処理、モデルファイル、信頼度の閾値、クラス数の設定などを行う。
以下のリポジトリをダウンロードし、parserフォルダをJetsonの/opt/nvidia/deepstream/deepstream-5.1/sources/ディレクトリにコピーする。  
https://github.com/mihara-shoko/yolo_parser　　
「1. モデルのTensorRT化」で作成したyolov7-tiny.engineファイルを/opt/nvidia/deepstream/deepstream-5.1/sources/parser下にコピーする。
/opt/nvidia/deepstream/deepstream-5.1/sources/parser下にあるconfig_infer_primary.txtを必要に応じて修正する。

```config_infer_primary.txt
[property]
gpu-id=0
net-scale-factor=0.0039215697906911373
model-color-format=0
model-engine-file=yolov7-tiny.engine # TensorRT化したモデルのパス
labelfile-path=labels.txt # ラベルが書かれたテキストファイルのパス
num-detected-classes=80 # クラス数
interval=0
gie-unique-id=1
process-mode=1
network-type=0
cluster-mode=4
maintain-aspect-ratio=1
parse-bbox-func-name=NvDsInferParseCustomYoloV5
custom-lib-path=nvdsinfer_custom_impl_Yolo/libnvdsinfer_custom_impl_Yolo.so

[class-attrs-all]
pre-cluster-threshold=0.25 # 信頼度の閾値
```

前処理に関係する項目
|  項目  |  説明  |  yolov7の場合の設定値  |
| ---- | ---- | ---- |
|  net-scale-factor |  ピクセル値にかける値<br>モデルにはyのピクセル値が入力される。<br>y = net scale factor*(x-offset)  |  0.0039215697906911373<br>0-255を0-1のrangeにしている  |
|  offset |  ピクセル値から引く値<br>RGBそれぞれ別に設定できる。<br>モデルにはyのピクセル値が入力される。<br>y = net scale factor*(x-offset)  |  0:0:0<br>設定しなくて良い  |
|  model-color-format |  0:RGB<br>1:BGR<br>2:GRAY  |  0  |
|  maintain-aspect-ratio |  リサイズするときにアスペクト比を維持するかどうか  |  1  |

詳しくはNVIDIAのページを参照  
https://docs.nvidia.com/metropolis/deepstream/dev-guide/text/DS_plugin_gst-nvinfer.html

## 3. パーサー作成
後処理(NMS処理など)を行うファイルを作成する。

Jetsonの/opt/nvidia/deepstream/deepstream-5.1/sources/parserに移動する。  
nvdsinfer_custom_impl_Yolo/yololayer.hのクラス数(19行目)、input size(20, 21行目)を適宜変更する（TensorRT化したときと同じ値にする）。

```nvdsinfer_custom_impl_Yolo/yololayer.h
#ifndef _YOLO_LAYER_H
#define _YOLO_LAYER_H

#include <vector>
#include <string>
#include "NvInfer.h"

namespace Yolo
{
    static constexpr int CHECK_COUNT = 3;
    static constexpr float IGNORE_THRESH = 0.1f;
    struct YoloKernel
    {
        int width;
        int height;
        float anchors[CHECK_COUNT * 2];
    };
    static constexpr int MAX_OUTPUT_BBOX_COUNT = 1000;
    static constexpr int CLASS_NUM = 1;
    static constexpr int INPUT_H = 416;  // yolov5's input height and width must be divisible by 32.
    static constexpr int INPUT_W = 416;

```

以下のコマンドでコンパイルする。

```
CUDA_VER=10.2 make -C nvdsinfer_custom_imple_Yolo
```
nvdsinfer_custom_impl_Yolo下に生成した.soファイルがパーサーとなる。

## 4. アプリ実行

Jetsonの/opt/nvidia/deepstream/deepstream-5.1/sources/parserに移動する。　　
deepstream_app_config.txtのuriに読み込ませたい動画のパスを設定する。  
```deepstream_app_config.txt
[source0]
enable=1
type=3
uri=file://../../samples/streams/sample_1080p_h264.mp4
num-sources=1
gpu-id=0
cudadec-memtype=0
```
以下のコマンドを実行し、アプリを実行する。

```
deepstream-app -c deepstream_app_config.txt
```

# アプリ実行結果
35 FPSでリアルタイムに推論をかけることができた！

![yolov7-tiny.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1319419/ea5dcefc-0a29-8a3a-5530-6d1ee604c71c.gif)



# おわりに
この記事では、Jetsonにyolov7-tinyを組込んでリアルタイムに物体検出を行う方法を紹介しました。無事にDeepstreamのパイプライン上で動かすことができました。yolov5sよりもアプリ実行速度が速い(同じ条件で実行するとyolov5sの場合は30 FPS未満)ので、リアルタイムに推論をかけたい場合はおすすめです。組込みの手法は何通りかあると思うので、別の方法を知っている方いましたら教えていただきたいです！

# 参考ページ
- https://docs.nvidia.com/metropolis/deepstream/dev-guide/text/DS_plugin_gst-nvinfer.html