Help us understand the problem. What is going on with this article?

OpenCVとNPPの連携

More than 3 years have passed since last update.

この記事はOpenCV Advent Calendar 2016の11日目の記事です.

はじめに

この記事ではOpenCVとNVIDIA Performance Primitives library (NPP)の連携方法について紹介します.

NPPとは

NVIDIA Performance Primitives library (NPP)とは画像処理,信号処理の各種アルゴリズムをCUDA実装したライブラリです.雑に紹介するとIPPのCUDA版みたいなものです.CUDAオンラインドキュメントにNPPドキュメント(PDFファイル)へのリンク一覧があるので興味がある方は読んでみるとよいでしょう.
※対応するデータ型,チャンネル数などによってAPIが分かれているのでページ数がそれなりにあります.

また,NPPは機能毎にモジュール化されており,NPPで用意されているモジュールは以下の通りです.

  • nppc:core
  • nppi:image processing
  • npps:signal processing

そのため,使いたい機能が入ったモジュールに対応したライブラリをリンクしてください(※nppcは必須).

NPPの機能

NPPではどういったことができるのか気になる方もいらっしゃると思いますが,NPP公式サイトには以下のように紹介されています.

  • Eliminates unnecessary copying of data to/from CPU memory
    • Process data that is already in GPU memory
    • Leave results in GPU memory so they are ready for subsequent processing
  • Data Exchange and Initialization
    • Set, Convert, Copy, CopyConstBorder, Transpose, SwapChannels
  • Arithmetic and Logical Operations
    • Add, Sub, Mul, Div, AbsDiff, Threshold, Compare
  • Color Conversion
    • GBToYCbCr, YcbCrToRGB, YCbCrToYCbCr, ColorTwist, LUT_Linear
  • Filter Functions
    • FilterBox, Filter, FilterRow, FilterColumn, FilterMax, FilterMin, Dilate, Erode, SumWindowColumn, SumWindowRow
  • JPEG
    • DCTQuantInv, DCTQuantFwd, QuantizationTableJPEG
  • Geometry Transforms
    • Mirror, WarpAffine, WarpAffineBack, WarpAffineQuad, WarpPerspective, WarpPerspectiveBack, WarpPerspectiveQuad, Resize
  • Statistics Functions
    • Mean_StdDev, NormDiff, Sum, MinMax, HistogramEven, RectStdDev

この説明はあくまでざっくりとしたものなので,詳細を知りたい方はCUDAオンラインドキュメントにあるPDFドキュメントを読むことをオススメします.

OpenCVとNPPの関係性

OpenCV cudaモジュールの一部関数は内部的にNPP関数を呼んでいます.
以下にいくつかその例を紹介します.

ただし,アルゴリズムは同じでもAPIに与えられるパラメータ,実装がOpenCVと異なるものはNPPを使わず,OpenCV同梱のCUDAカーネルを呼んでいます.特にwarpAffineなどは,

bool useNpp = borderMode == BORDER_CONSTANT && ofs.x == 0 && ofs.y == 0 && useNppTab[src.depth()][src.channels() - 1][interpolation];

というようにNPPで実装されているものかどうかを判定して,NPPとOpenCV内のCUDAカーネルを呼び分けています.

NPPを直接使うメリット

OpenCVのcudaモジュールに同梱されるCUDAカーネルは基本的に特定アーキテクチャに対する最適化が行われているわけではありません.一方でNPPはアーキテクチャ更新に対応して定期的にバージョンアップされていることからNPPを直接叩いたほうがよいケースもあります.

OpenCVと連携するための前準備

OpenCVとNPP連携して使うためには前準備,事前知識が必要なので簡単に説明していきます.

CMakeLists.txtを書く

OpenCVとNPPを使ったプログラムをコンパイルするにはCMakeLists.txtで書いちゃうのが簡単です.
大まかな流れはこんな感じです.

  1. find_packageでCUDAを探す ※詳細はCMakeドキュメント(FindCUDA)を参照ください
  2. NPPのヘッダファイルがあるパス(${CUDA_INCLUDE_DIRS})をインクルードパスに設定する
  3. NPPライブラリをリンクする

この流れに従ったCMakeLists.txtのサンプルを以下に示します.

cmake_minimum_required(VERSION 2.8)
add_executable(npp_test main.cpp)

# OpenCVを見つける
find_package(OpenCV REQUIRED)

# CUDAを見つける
find_package(CUDA REQUIRED)

if(OpenCV_FOUND)
  if(CUDA_FOUND)
    include_directories(${CUDA_INCLUDE_DIRS} ${OpenCV_INCLUDE_DIRS})

    # NPPライブラリをリンクする
    target_link_libraries(
      npp_test 
      ${CUDA_npp_LIBRARY} 
      ${CUDA_nppc_LIBRARY} 
      ${CUDA_nppi_LIBRARY}
      ${CUDA_npps_LIBRARY} 
      ${OpenCV_LIBS})
  endif(CUDA_FOUND)
endif(OpenCV_FOUND)

よく使うNPPのデータ型を知る

NPPを使った実装を行う前によく使うNPPのデータ型について紹介します.

Basic NPP Data Types

NPPで定義される基本型は以下の通りです.
以下の表からもわかるようにNpp<ビット数><データ型>という命名規則になっています.

NPPで定義されるデータ型 実際のデータ型
Npp8u 8-bit unsigned char
Npp8s 8-bit signed char
Npp16u 16-bit unsigned integer
Npp16s 16-bit signed integer
Npp32u 32-bit unsigned integer
Npp32s 32-bit signed integer
Npp64u 64-bit unsigned integer
Npp64s 64-bit signed integer
Npp32f 32-bit (IEEE) floating-point number
Npp64f 64-bit floating-point number

NppiPoint構造体

2次元座標を扱う構造体でデータフィールドは以下の通りです.

  • int x
  • int y

以下にNppiPoint構造体の初期化を行う例を示します.

int x        = 0;
int y        = 0;
NppiPoint pt = {x, y};

NppiRect構造体

矩形領域の情報を扱う構造体でデータフィールドは以下の通りです.

  • int x
  • int y
  • int width
  • int height

以下にNppiRect構造体の初期化を行う例を示します.

int x         = 0;
int y         = 0;
int width     = 320;
int height    = 240;
NppiRect rect = {x, y, width, height};

NppiSize構造体

サイズ情報を扱う構造体でデータフィールドは以下の通りです.

  • int width
  • int height

以下にNppiSize構造体の初期化を行う例を示します.

int width    = 320;
int height   = 240;
NppiSize roi = {width, height};

NppStatus

ステータスコードのenum値です.NPP関数は戻り値としてステータスコードを返すようになっています.
NPP_NO_ERRORNPP_SUCCESS(意味としてはNPP_NO_ERRORと等価)は正常に関数が実行されたことを意味しています.
そのため,実際に使うときにはNPP関数の戻り値がNPP_SUCCESSなのかチェックしておくとよいでしょう.

画像データのアクセス方法

cv::cuda::GpuMatに格納されている画像データをNPPの関数に渡すために(個人的に)よく使う方法は以下の2通りです.

前者だとこんな感じです.

nppiFilterMedian_8u_C1R(d_src.ptr<Npp8u>(0), nSrcStep, d_dst.ptr<Npp8u>(0), nDstStep, roi, mask, anchor, d_median_filter_buffer);

一方後者だとこんな感じです.

nppiFilterMedian_8u_C1R(d_src.datastart, nSrcStep, d_dst.datastart, nDstStep, roi, mask, anchor, d_median_filter_buffer);

OpenCVとNPPを連携したコードを実装してみる

というわけでこれまで長々(?)と前提となる話をして準備が整ったので,OpenCVとNPPを連携したサンプルコードを以下に示します.

// OpenCV header file
#include <opencv2/core.hpp>
#include <opencv2/core/cuda.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/highgui.hpp>

// NPP header file
#include <npp.h>

#include <iostream>

int main(int argc, char *argv[])
{
    const NppLibraryVersion *libVer = nppGetLibVersion();
    std::cout << "NPP Library Version: " << libVer->major << "." << libVer->minor << "." << libVer->build << std::endl;

    // GpuMat
    cv::Mat src = cv::imread("lena.jpg", cv::IMREAD_GRAYSCALE);
    cv::Mat dst = cv::Mat(src.size(), src.type());
    cv::cuda::GpuMat d_src(src);
    cv::cuda::GpuMat d_dst(dst);

    // NPP
    unsigned int width  = d_src.cols;
    unsigned int height = d_src.rows;
    NppiSize roi        = {width, height};
    NppiSize mask       = {7, 7};
    NppiPoint anchor    = {0, 0};

    // create temporary buffer
    Npp32u nBufferSize            = 0;
    Npp8u *d_median_filter_buffer = NULL;
    NppStatus status = nppiFilterMedianGetBufferSize_8u_C1R(roi, mask, &nBufferSize);
    if(status != NPP_SUCCESS)
    {
        std::cout << "[NPP ERROR] status = " << status << std::endl;
        return -1;
    }
    cudaMalloc((void **)(&d_median_filter_buffer), nBufferSize);

    Npp32s nSrcStep = d_src.step;
    Npp32s nDstStep = d_dst.step;
    status = nppiFilterMedian_8u_C1R(d_src.datastart, nSrcStep, d_dst.datastart, nDstStep, roi, mask, anchor, d_median_filter_buffer);
    if(status != NPP_SUCCESS)
    {
        std::cout << "[NPP ERROR] status = " << status << std::endl;
    }

    // free temporary buffer
    cudaFree(d_median_filter_buffer);

    // display result image
    d_dst.download(dst);
    cv::imshow("dst", dst);
    cv::waitKey(0);
    cv::destroyAllWindows();

    return 0;
}

このプログラムの実行結果は以下の通りです.

入力画像
入力画像
出力画像
出力画像

おわりに

この記事ではOpenCVとNVIDIA Performance Primitives library (NPP)の連携方法について紹介しました.
この記事をきっかけにNPP使ってみようかなと思ってくれる方が増えると幸いです.

そして,まだOpenCVのNPP統合できていないところを対応してぜひPull Requestを送りましょう!(これが言いたかった)

備考

筆者は以下の環境で動作確認しました.

  • CPU:Intel Core i7-6700HQ
  • メモリ:64GB
  • GPU:NVIDIA GeForce GTX 1060 / 6GB
  • Ubuntu 16.04 LTS(64bit)
  • OpenCV 3.1.0
  • CUDA 8.0
  • gcc 5.4.0
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした