NVIDIA Vision Programming Interface (VPI)入門 Python API編

## VPIとは

VPI(<https://docs.nvidia.com/vpi/>)は、NVIDIA **V**ision **P**rogramming **I**nterfaceのことで、Jetsonデバイス、NVIDIA GPU搭載のx86_64マシン向けにコンピュータビジョン、画像処理アルゴリズムの機能を提供するライブラリです。

また、各種アルゴリズムは、CPUだけでなくNVIDIA GPU、PVAなどの異なるアクセラレータ向けの実装がなされており、特定アクセラレータに処理をオフロードし、連携できるようになっています。<https://docs.nvidia.com/vpi/2.2/architecture.html>にあるVPIアーキテクチャの図を以下に引用します。

![](https://docs.nvidia.com/vpi/2.2/arch_overview.svg)

より詳細な情報を知りたい方は<https://www.youtube.com/watch?v=gCf1wQLfYCM>にあるNVIDIA Japanによる解説動画を観るとよいでしょう。

<iframe width="560" height="315" src="https://www.youtube.com/embed/gCf1wQLfYCM" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>

## 動作環境

VPIがサポートしている動作環境は以下の通りです。詳細は<https://docs.nvidia.com/vpi/2.2/architecture.html#arch_supported_platforms>を参照ください。

- Jetson AGX Xavier、Jetson Xavier NX
- Jetson AGX Orin、Jetson AGX Orin NX
- NVIDIA GPU搭載（Maxwell世代以降）のLinux x86_64マシン

インストール方法は<https://docs.nvidia.com/vpi/2.2/installation.html>を参照ください。

## バックエンド

<https://docs.nvidia.com/vpi/2.2/basic_concepts.html#basic_backend>にある表を以下に示します。この表にはVPIが提供するバックエンドと対応デバイス、アーキテクチャが書かれています。

|Backend|Device/platform|
|---|---|
|CPU|x86_64アーキテクチャ/Ubuntuマシン、Jetsonデバイスで利用可能|
|CUDA|x86_64アーキテクチャのNVIDIA GPU搭載Ubuntuマシン、Jetsonデバイスで利用可能|
|PVA<br>(Programmable Vision Accelerator)|Jetson AGX Xavier、Jetson Xavier NXシリーズ以降のデバイスのみで利用可能（NanoシリーズはPVA非搭載）|
|VIC<br>(Video Image Compositor)|Jetsonデバイスのみ利用可能|
|NVENC<br>(NVIDIA Encoder Engine)|Jetsonデバイスのみ利用可能。ただし、dense optical flowはJetson AGX Xavierシリーズ以降のJetsonデバイスのみ|
|OFA<br>(NVIDIA Optical Flow Accelerator)|Jetson AGX OrinシリーズのJetsonデバイスのみ利用可能|

各バックエンドの詳細は<https://docs.nvidia.com/vpi/2.2/architecture.html#arch_backend>を参照ください。

## データ構造

VPIにおけるプリミティブなデータ構造について紹介します。VPIはC API、Python APIを提供していますが、以降の説明ではPython APIを用いた例を述べます。詳細は<https://docs.nvidia.com/vpi/2.2/basic_concepts.html#basic_buffers>を参照ください。

- 2D Images
  - Python APIだと`vpi.Image`
  - <https://docs.nvidia.com/vpi/2.2/python/build/vpi.Image.html#vpi.Image>
- 1D Arrays
  - Python APIだと`vpi.Array`
  - <https://docs.nvidia.com/vpi/2.2/python/build/vpi.Array.html>
- 2D Image Pyramids
  - Python APIだと`vpi.Pyramid`
  - <https://docs.nvidia.com/vpi/2.2/python/build/vpi.Pyramid.html>

以降、これらのプリミティブなデータ構造の使い方の例を示します。

### vpi.Image

`vpi.Image`の使用例は以下の通りです。ここでは8bit unsigned integer型の画素値を持つ1チャンネル画像を扱っているので画像フォーマットは`vpi.Format.U8`と指定しています。画像フォーマットの詳細は<https://docs.nvidia.com/vpi/2.2/python/build/vpi.Format.html>を参照ください。

```python
import vpi

width = 640
height = 480
format = vpi.Format.U8

# vpi.Image
img = vpi.Image((width, height), format)

print(f"img.format = {img.format}")
print(f"img.width = {img.width}")
print(f"img.height = {img.height}")
print(f"img.size = {img.size}")
```

このスクリプトの実行結果は以下の通りです。

```
img.format = Format.U8
img.width = 640
img.height = 480
img.size = (640, 480)
```

### vpi.Array

`vpi.Array`の使用例は以下の通りです。

```python
import vpi

capacity = 10
format = vpi.Format.U8

# vpi.Array
arr = vpi.Array(capacity, format)

print(f"arr.capacity = {arr.capacity}")
print(f"arr.size = {arr.size}")
```

このスクリプトの実行結果は以下の通りです。

```
arr.capacity = 10
arr.size = 0
```

## 対応アルゴリズム

VPIが提供するコンピュータビジョン、画像処理アルゴリズムは<https://docs.nvidia.com/vpi/2.2/algorithms.html>にあります。ただし、バックエンドによってはサポートしていないアルゴリズムがあったり、利用できる画像サイズ、パラメータに制約があるためアルゴリズムのページを確認ください。

## シンプルなパイプライン

<https://docs.nvidia.com/vpi/2.2/architecture.html#arch_simple_pipeline>にあるシンプルな画像処理パイプラインの例を用いて説明します。この例ではおおまかに以下の処理を行っています。

1. 読み込んだ画像をndarrayに格納
1. ndarrayをvpi.Imageに変換
1. CUDA backendでbox filter適用

```python
import cv2
import vpi

img_cv = cv2.imread("kodim08.png", cv2.IMREAD_GRAYSCALE)

# ndarrayをvpi.Imageに変換
img = vpi.asimage(img_cv)

# CUDA backendでbox filter適用
with vpi.Backend.CUDA:
    output = img.box_filter(ksize=7)
```

前述のコードで

```python
with vpi.Backend.CUDA:
    output = img.box_filter(ksize=7)
```

となっている箇所は、box_filterメソッドにてバックエンドを指定するように

```python
output = img.box_filter(ksize=7, backend=vpi.Backend.CUDA)
```

と書くこともできます。

## データ変換

VPIには各種データ変換機能が用意されており、他のライブラリと連携ができるようになっています。
ここではPython APIでよく使うと思われる以下の変換について紹介します。

- ndarray->vpi.Image
- vpi.Image->ndarray
- torch.Tensor->vpi.Image

また、Jetsonデバイスのみではありますが、EGLImage、NvBufferとの相互変換もサポートしています。詳細は<https://docs.nvidia.com/vpi/2.2/python/interoperabilities.html>を参照ください。

### ndarray->vpi.Image

vpi.asimageでndarrayからvpi.Imageへ変換することができます。

- <https://docs.nvidia.com/vpi/2.2/python/build/vpi.asimage.html#vpi.asimage>

```python
import cv2
import vpi

img_cv = cv2.imread("kodim08.png", cv2.IMREAD_GRAYSCALE)

# ndarrayをvpi.Imageに変換
img = vpi.asimage(img_cv)
```

### vpi.Image->ndarray

以下のメソッドでvpi.Imageからndarrayへ変換することができます。

- cpuメソッド：<https://docs.nvidia.com/vpi/2.2/python/build/vpi.Container.cpu.html>
- toメソッド：<https://docs.nvidia.com/vpi/2.2/python/build/vpi.Container.to.html>

```python
import vpi

width = 640
height = 480
format = vpi.Format.U8
img = vpi.Image((width, height), format)

# cpuメソッド
buffer1 = input.cpu()

# toメソッド
buffer2 = input.to(vpi.MemType.CPU)
```

### torch.Tensor->vpi.Image

`vpi.asimage`でtorch.Tensorからvpi.Imageへ変換することができます。

```python
import cv2
import torch
import vpi

img_cv = cv2.imread("kodim08.png", cv2.IMREAD_GRAYSCALE)
torch_image = torch.asarray(np_image).cuda()
vpi_image = vpi.asimage(torch_image)
```

詳細は<https://developer.nvidia.com/blog/improved-interoperability-between-vpi-and-pytorch/>を参照ください。

## VPIディレクトリ構成

<https://docs.nvidia.com/vpi/2.2/installation.html>に説明がある通り、VPIのディレクトリ構成は下表の通りです。

|ファイルパス||
|---|---|
|`/opt/nvidia/vpi2`|VPIルートディレクトリ|
|`/opt/nvidia/vpi2/bin`|デモプログラムなどの実行バイナリが格納されたディレクトリ|
|`/opt/nvidia/vpi2/include`|インクルードディレクトリ|
|`/opt/nvidia/vpi2/lib/<arch>`|VPIライブラリが格納されたディレクトリ|
|`/opt/nvidia/vpi2/samples`|VPIサンプルプログラム（C++、Python）が格納されたディレクトリ|

## 動作確認環境

筆者は以下の環境で動作確認を行いました。

- reComputer J4012(Jetson Orin NX 16GB)
- JetPack 5.1.1
- Python 3.8.10
- VPI 2.2.7