NVIDIA Vision Programming Interface (VPI)入門 C 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.3/architecture.html>にあるVPIアーキテクチャの図を以下に引用します。

![](https://docs.nvidia.com/vpi/2.3/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.3/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.3/installation.html>を参照ください。

## バックエンド

<https://docs.nvidia.com/vpi/2.3/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.3/architecture.html#arch_backend>を参照ください。

## データ構造

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

- 2D Images
  - C APIだと`VPIImage`
  - <https://docs.nvidia.com/vpi/2.3/group__VPI__Image.html>
- 1D Arrays
  - C APIだと`VPIArray`
  - <https://docs.nvidia.com/vpi/2.3/group__VPI__Array.html>
- 2D Image Pyramids
  - C APIだと`VPIPyramid`
  - <https://docs.nvidia.com/vpi/2.3/group__VPI__Pyramid.html>

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

### VPIImage

```cpp
// VPIImageを使うために要インクルード
#include <vpi/Image.h>

int main(int argc, char *argv[])
{
    int32_t width = 512;
    int32_t height = 256;
    VPIImageFormat fmt = VPI_IMAGE_FORMAT_U8;

    // VPIImage生成
    VPIImage image;
    vpiImageCreate(width, height, fmt, 0, &image);

    // VPIImage解放
    vpiImageDestroy(image);

    return 0;
}
```

### VPIArray

```cpp
// VPIArrayを使うために要インクルード
#include <vpi/Array.h>

int main(int argc, char *argv[])
{
    int32_t capacity = 10;
    VPIArrayType type = VPI_ARRAY_TYPE_U8;

    // VPIArray生成
    VPIArray arr;
    vpiArrayCreate(capacity, type, 0, &arr);

    // VPIArray解放
    vpiArrayDestroy(arr);

    return 0;
}
```

### VPIPyramid

```cpp
// VPIPyramidを使うために要インクルード
#include <vpi/Pyramid.h>

int main(int argc, char *argv[])
{
    int32_t width = 512;
    int32_t height = 256;
    VPIImageFormat fmt = VPI_IMAGE_FORMAT_U8;
    int32_t numLevels = 3;

    // VPIPyramid生成
    VPIPyramid pyr;
    vpiPyramidCreate(width, height, fmt, numLevels, 0.5f, 0, &pyr);

    // VPIPyramid解放
    vpiPyramidDestroy(pyr);

    return 0;
}
```

## データ変換

VPIには各種データ変換機能が用意されており、他のライブラリと連携ができるようになっています。ここではC APIでよく使うと思われるOpenCVの`cv::Mat`クラスとの相互変換について紹介します。

- Mat->VPIImage
- VPIImage->Mat

### Mat->VPIImage

```cpp
// 要インクルード
#include <vpi/OpenCVInterop.hpp>
```

```cpp
cv::Mat cvSrc = cv::Mat(cv::Size(512, 256), CV_8UC3, cv::Scalar(0, 0, 255));

// Mat->VPIImage
VPIImage src;
vpiImageCreateWrapperOpenCVMat(cvSrc, 0, &src);
```

ここでは`vpiImageCreateWrapperOpenCVMat`の例を示していますが、それ以外の方法もあります。<https://docs.nvidia.com/vpi/2.3/group__VPI__OpenCVInterop.html>を参照ください。

### VPIImage->Mat

```cpp
// 要インクルード
#include <vpi/OpenCVInterop.hpp>
```

```cpp
VPIImage dst;

// dstの初期化はこのコードでは省略している

// VPIImageのロック
//   この例ではREADのみであるためVPI_LOCK_READを指定
//   この例ではCPU backendを使うためVPI_IMAGE_BUFFER_HOST_PITCH_LINEARを指定
//   詳細は https://docs.nvidia.com/vpi/2.3/group__VPI__Image.html#ga6024548d5ee11f88fab7341830262e2d 参照
VPIImageData dstData;
vpiImageLockData(dst, VPI_LOCK_READ, VPI_IMAGE_BUFFER_HOST_PITCH_LINEAR, &dstData);

// VPIImageData->Mat
cv::Mat cvDst;
vpiImageDataExportOpenCVMat(dstData, &cvDst);

// VPIImageのアンロック
vpiImageUnlock(dst);
```

## 対応アルゴリズム

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

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

<https://docs.nvidia.com/vpi/2.3/architecture.html#arch_simple_pipeline>にある画像を入力としてBox Filterを適用し、出力するシンプルなパイプラインの実装例を示します。下図はパイプラインを図示したものです。

![](https://docs.nvidia.com/vpi/2.3/arch_simple_pipeline.svg)


```cpp
#include <vpi/Image.h>
#include <vpi/Stream.h>

// 今回、BoxFilterを使うためインクルード
#include <vpi/algo/BoxFilter.h>

#include <vpi/OpenCVInterop.hpp>
#include <opencv2/core.hpp>
#include <iostream>

int main(int argc, char *argv[])
{
    VPIImage src;
    cv::Mat cvSrc = cv::imread("kodim08.png", cv::IMREAD_GRAYSCALE);
    vpiImageCreateWrapperOpenCVMat(cvSrc, 0, &src);

    int32_t width;
    int32_t height;
    vpiImageGetSize(src, &width, &height);

    VPIImageFormat type;
    vpiImageGetFormat(src, &type);

    // srcと同じ画像サイズ、フォーマットでVPIImageを生成
    VPIImage dst;
    vpiImageCreate(width, height, type, 0, &dst);

    // ストリームの生成
    VPIStream stream;
    vpiStreamCreate(0, &stream);

    // タスクのサブミット（このサンプルではVPI_BACKEND_CUDAを指定している）
    vpiSubmitBoxFilter(stream, VPI_BACKEND_CUDA, src, dst, 5, 5, VPI_BORDER_ZERO);

    // ストリームの同期
    vpiStreamSync(stream);

    // 解放処理
    vpiStreamDestroy(stream);
    vpiImageDestroy(src);
    vpiImageDestroy(dst);

    return 0;
}
```

## CMakeLists.txt

CMakeLists.txtのサンプルを以下に示します。

```cmake
cmake_minimum_required(VERSION 3.5)
project(vpi_sample)

# VPIパッケージを検索
find_package(vpi REQUIRED)

add_executable(${PROJECT_NAME} main.cpp)

# VPIライブラリのリンク設定
target_link_libraries(${PROJECT_NAME} vpi)
```

## エラー処理

### ステータスコード

VPIはVPIStatusでステータスコードを定義しています。詳細は<https://docs.nvidia.com/vpi/2.3/group__VPI__Status.html#ga00590fb7e3bc6c02d207f4d280c27056>を参照ください。

|ステータスコード|意味|
|---|---|
|VPI_SUCCESS|Operation completed successfully.|
|VPI_ERROR_NOT_IMPLEMENTED|Operation isn't implemented.|
|VPI_ERROR_INVALID_ARGUMENT|Invalid argument, either wrong range or value not accepted.|
|VPI_ERROR_INVALID_IMAGE_FORMAT|Image type not accepted.|
|VPI_ERROR_INVALID_ARRAY_TYPE|Array type not accepted.|
|VPI_ERROR_INVALID_PAYLOAD_TYPE|Payload not created for this algorithm.|
|VPI_ERROR_INVALID_OPERATION|Operation isn't valid in this context.|
|VPI_ERROR_INVALID_CONTEXT|Context is invalid or is already destroyed.|
|VPI_ERROR_DEVICE|Device backend error.|
|VPI_ERROR_NOT_READY|Operation not completed yet, try again later.|
|VPI_ERROR_BUFFER_LOCKED|Invalid operation on a locked buffer.
|VPI_ERROR_OUT_OF_MEMORY|Not enough free memory to allocate object.|
|VPI_ERROR_INTERNAL|Internal, non specific error.|

VPI APIによってはステータスコードを返すものがあるので、エラー処理やデバッグの際に活用するとよいでしょう。

```cpp
VPIStatus status = vpiStreamCreate(0, &stream);
```

### ステータスチェックマクロ

公式サンプルコードでは以下のようなステータスチェックマクロを実装しています。

```cpp
#define CHECK_STATUS(STMT)                                    \
    do                                                        \
    {                                                         \
        VPIStatus status = (STMT);                            \
        if (status != VPI_SUCCESS)                            \
        {                                                     \
            char buffer[VPI_MAX_STATUS_MESSAGE_LENGTH];       \
            vpiGetLastStatusMessage(buffer, sizeof(buffer));  \
            std::ostringstream ss;                            \
            ss << vpiStatusGetName(status) << ": " << buffer; \
            throw std::runtime_error(ss.str());               \
        }                                                     \
    } while (0);
```

`CHECK_STATUS`マクロの使用例は以下の通りです。

```cpp
VPIStream stream;
CHECK_STATUS(vpiStreamCreate(0, &stream));
```

## VPIディレクトリ構成

<https://docs.nvidia.com/vpi/2.3/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.2
- VPI 2.3.9