Upload
yasuhiro-yoshimura
View
1.006
Download
3
Embed Size (px)
Citation preview
GTX 1080でComputer Visionアルゴリズムを色々動かしてみる
関東GPGPU勉強会#42016/8/21
@dandelion1124
自己紹介(1/2)
Twitter ID:@dandelion1124
• 学生時代はコンピュータビジョン、VRの研究に従事
• 大学院の研究室でOpenCVのTipsサイトを作っていたらOpenCV関連書籍(OpenCVプログラミングブック)を書くことに
• 現在は都内勤務エンジニア
Web: http://atinfinity.github.io
Wiki: https://github.com/atinfinity/lab/wiki
連載記事: http://www.buildinsider.net/small/opencv/
自己紹介(2/2)
• メインで参加している勉強会
–関東コンピュータビジョン勉強会 #cvsaisentan
http://sites.google.com/site/cvsaisentan/
本日のアジェンダ
• OpenCVとは
• OpenCVのデータ構造
• GpuMatとCUDAカーネルの連携
• CUDAカーネル実装
– フィルタ処理
– テンプレートマッチング
• 速度計測いろいろ
– OpenCV
OpenCVとは
Intelが開発したOpen SourceのComputer Vision
ライブラリ。現在はItseezIntelが開発を行っている。
http://opencv.org/
• 公式サポートOS
– Windows/Linux/Mac OS/Android/iOS
• 公式サポート言語
– C/C++/Python/Java
※有志による非公式ラッパーの一覧を以下のサイトにまとめています。https://github.com/atinfinity/lab/wiki/OpenCV-binding%E3%81%BE%E3%81%A8%E3%82%81
先日ItseezがIntelに買収されるという発表がありました
OpenCVのデータ構造
OpenCVで画像を格納するために使うデータ構造は
おおまかに以下の4つ。
• Mat 画像データの入れ物(CPU版)
• UMat 画像データの入れ物(CPU/OpenCL版)
• GpuMat 画像データの入れ物(CUDA版)
UMatについては前回勉強会の発表資料を参照ください。
http://www.slideshare.net/YasuhiroYoshimura/gpgpu-dandelion1124-201301130
※上記資料は2013年11月時点のものなので最新版ではAPI仕様が少し変わっています。
GpuMatとCUDAカーネルの連携
GpuMatとCUDAカーネルを連携する場合は、
cudevモジュールのGlobPtrSz、PtrStepSzを活用すると
便利です。
• GlobPtrSz:cols(幅)、rows (高さ) 、step等が参照可
• PtrStepSz:cols (幅)、rows (高さ)、step、ptr等が参照可
– Ptrメソッドを使うとポインタのアドレス計算を簡略化できる
– 以降のコードではこちらを使います
参考:http://proc-cpuinfo.fixstars.com/2016/08/gpumat.html
サンプルコード(PtrStepSz)
// PtrStepSz型の変数を生成
cv::cudev::PtrStepSz<uchar> pSrc =
cv::cudev::PtrStepSz<uchar>(src.rows, src.cols * src.channels(), src.ptr<uchar>(), src.step);
const dim3 block(64, 2);
const dim3 grid(cv::cudev::divUp(dst.cols, block.x), cv::cudev::divUp(dst.rows, block.y));
// CUDAカーネルを呼び出す
kernel<<<grid, block>>>(pSrc); //カーネル引数としてPtrStepSz型の変数を渡す
CV_CUDEV_SAFE_CALL(cudaGetLastError());
CV_CUDEV_SAFE_CALL(cudaDeviceSynchronize());
カーネル呼び出し部
サンプルコード(PtrStepSz)
// カーネル引数としてPtrStepSz型の変数を受け取る
__global__ void kernel(cv::cudev::PtrStepSz<uchar> src)
{
int x = blockDim.x * blockIdx.x + threadIdx.x;
int y = blockDim.y * blockIdx.y + threadIdx.y;
if(x < src.cols && y < src.rows) {
uchar val = src.ptr(y)[x]; // 座標(x, y)の画素値を参照する
}
}
CUDAカーネル
【追記】GpuMatとCUDAカーネルの連携
GpuMatとCUDAカーネルを連携する場合は、
cudevモジュールのGlobPtrSz、PtrStepSzを活用すると
便利です。
発表時に「CUDAカーネルの引数としてGpuMatクラスの
インスタンスを直接渡せる」というご指摘を受けたため、
確認したところ、正しく動作することがわかりました。
実装する際にはGpuMatクラスのインスタンスを渡した方が
わかりやすそうです。
【追記】サンプルコード(GpuMat)
cv::Mat src = cv::imread("lena.jpg", cv::IMREAD_GRAYSCALE);
cv::cuda::GpuMat d_src(src);
const dim3 block(64, 2);
const dim3 grid(cv::cudev::divUp(dst.cols, block.x), cv::cudev::divUp(dst.rows, block.y));
// CUDAカーネルを呼び出す
kernel<<<grid, block>>>(d_src); //カーネル引数としてGpuMatクラスのインスタンスを渡す
CV_CUDEV_SAFE_CALL(cudaGetLastError());
CV_CUDEV_SAFE_CALL(cudaDeviceSynchronize());
カーネル呼び出し部
【追記】サンプルコード(GpuMat)
// カーネル引数としてGpuMatクラスのインスタンスを受け取る
__global__ void kernel(cv::cuda::GpuMat src)
{
int x = blockDim.x * blockIdx.x + threadIdx.x;
int y = blockDim.y * blockIdx.y + threadIdx.y;
if(x < src.cols && y < src.rows) {
uchar val = src.ptr(y)[x]; // 座標(x, y)の画素値を参照する
}
}
CUDAカーネル
CUDAカーネル実装
よーし、CUDAカーネル書くぞー!
→まずはCUDA8のドキュメントを読もう
→Pascal Tuning Guideはまだない?
※CUDA 8 RCのドキュメント
ご清聴ありがとうございました
CUDAカーネル実装
よーし、CUDAカーネル書くぞー!
→まずはCUDA8のドキュメントを読もう
→Pascal Tuning Guideはまだない?
→今回は手探りでやってみましょう
※CUDA 8 RCのドキュメント
CUDAカーネル実装
• 計測環境– OS:Ubuntu 16.04 LTS(64bit)
– CUDA:CUDA Toolkit v8.0
– GCC:5.4.0
– CPU:Intel Xeon CPU E5-2623 v3 @ 3.00GHz
– メモリ:128GB
– GPU:GeForce GTX 1080• Pascalアーキテクチャ
CUDAカーネル実装
• 計測環境(比較用)– OS:Ubuntu 16.04 LTS(64bit)
– CUDA:CUDA Toolkit v8.0
– GCC:5.4.0
– CPU:Intel Xeon CPU E5-2623 v3 @ 3.00GHz
– メモリ:128GB
– GPU:GeForce GTX TITAN X• Maxwellアーキテクチャ
CUDAカーネル実装
• 計測環境(比較用)– OS:Windows 10 Pro(64bit)
– CUDA:CUDA Toolkit v7.5
– Visual Studio:Visual Studio 2013 Update5
– CPU:Intel Core i7-3930K @ 3.20GHz
– メモリ:32GB
– GPU:GeForce GTX 680• Keplerアーキテクチャ
CUDAカーネル実装
GeForce GTX 1080
GeForce GTX Titan X
GeForce GTX 680
アーキテクチャ Pascal Maxwell Kepler
CUDAコア数 2560基 3072基 1536基
定格クロック 1607MHz 1000MHz 1006MHz
ブーストクロック 1733MHz 1075MHz 1058MHz
メモリバス帯域幅 320GB/s 336.5GB/s 192.2GB/s
メモリ容量 8GB 12GB 2GB
動作クロックが大きく向上 メモリバス帯域幅はGeForce
Titan Xとほぼ同等
CUDAコア、メモリ容量がGTX 1080より多い
フィルタ処理
• 着目座標および周辺の画素値にカーネル係数で重み付けした値を計算する処理
• 以下の例だと・・・(1/9)*11 + (1/9)*14 + (1/9)*11 +
(1/9)*13 + (1/9)*10 + (1/9)*13 +
(1/9)*11 + (1/9)*14 + (1/9)*11 = 12
カーネル(3x3)
11
13
11
14
13
14 11
10
11
入力画像
1/9
1/9 1/9
1/9 1/9
1/9
1/91/91/9
注目座標
12
出力画像
積和演算が多い!!この例だと積:9回、和:9回
フィルタ処理(CPU実装)
ソースコード:https://github.com/atinfinity/imageFilteringGpu
for(int y = border_size; y < (dst.rows - border_size); y++){
uchar* pdst = dst.ptr<uchar>(y);
for(int x = border_size; x < (dst.cols - border_size); x++){
double sum = 0.0;
for(int yy = 0; yy < kernel.rows; yy++){
for(int xx = 0; xx < kernel.cols; xx++){
sum +=
(kernel.ptr<float>(yy)[xx] * src.ptr<uchar>(y+yy-border_size)[x+xx-border_size]);
}
}
pdst[x] = sum;
}
}
計算結果をストア
カーネル係数で重み付けして加算
フィルタ処理
• Naive実装
– CPU実装の類似度計算部分をそのままCUDAに持ってくる
• __ldg関数利用
– __ldg関数を使ってRead-Onlyキャッシュ経由でデータ読み込み
• テクスチャメモリ利用
– 画像データをテクスチャメモリに格納し、カーネルで参照
ソースコード:https://github.com/atinfinity/imageFilteringGpu
フィルタ処理(Naive実装)
ソースコード:https://github.com/atinfinity/imageFilteringGpu
if((y >= border_size) && y < (dst.rows-border_size)){
if((x >= border_size) && (x < (dst.cols-border_size))){
double sum = 0.0;
for(int yy = 0; yy < kernel.rows; yy++){
for(int xx = 0; xx < kernel.cols; xx++){
sum += (kernel.ptr(yy)[xx] * src.ptr(y+yy-border_size)[x+xx-border_size]);
}
}
dst.ptr(y)[x] = sum;
}
}
範囲チェック
カーネル係数で重み付けして加算
計算結果をストア
フィルタ処理(__ldg関数利用)
ソースコード:https://github.com/atinfinity/imageFilteringGpu
if((y >= border_size) && y < (dst.rows-border_size)){
if((x >= border_size) && (x < (dst.cols-border_size))){
double sum = 0.0;
for(int yy = 0; yy < kernel.rows; yy++){
const uchar* psrc = src.ptr(y+yy-border_size) + (x-border_size);
const float* pkernel = kernel.ptr(yy);
for(int xx = 0; xx < kernel.cols; xx++){
sum += (__ldg(&pkernel[xx]) * __ldg(&psrc[xx]));
}
}
dst.ptr(y)[x] = sum;
}
}
__ldg関数を利用してRead-Onlyキャッシュ経由で読み込み
フィルタ処理(テクスチャメモリ利用)
ソースコード:https://github.com/atinfinity/imageFilteringGpu
// bind texture
cv::cuda::device::bindTexture<uchar>(&srcTex, pSrc);
const dim3 block(64, 2);
const dim3 grid(cv::cudev::divUp(dst.cols, block.x), cv::cudev::divUp(dst.rows, block.y));
imageFilteringGpu_tex<<<grid, block>>>(pSrc, pDst, pKernel, border_size);
CV_CUDEV_SAFE_CALL(cudaGetLastError());
CV_CUDEV_SAFE_CALL(cudaDeviceSynchronize());
// unbind texture
CV_CUDEV_SAFE_CALL(cudaUnbindTexture(srcTex));
カーネル呼び出し部
OpenCVに便利関数があるので活用!
なぜかunbind用の便利関数が見当たらなかったので直接呼ぶ・・・
フィルタ処理(テクスチャメモリ利用)
ソースコード:https://github.com/atinfinity/imageFilteringGpu
if((y >= border_size) && (y < (dst.rows-border_size))){
if((x >= border_size) && (x < (dst.cols-border_size))){
double sum = 0.0;
for(int yy = 0; yy < kernel.rows; yy++){
for(int xx = 0; xx < kernel.cols; xx++){
sum += (kernel.ptr(yy)[xx] * tex2D(srcTex, x + xx - border_size, y + yy - border_size));
}
}
dst.ptr(y)[x] = sum;
}
}
Tex2D関数を使ってテクスチャメモリを参照
CUDAカーネル
フィルタ処理
入力画像はw=800、h=640のグレースケール画像 単位はms 5回計測して平均をとる
GTX 1080(Pascal)
GTX TITAN X(Maxwell)
GTX680(Kepler)
CPU実装(Naive) 57.521 44.158 39.255
OpenCV(Mat) 4.956 4.980 5.212
CUDA実装(Naive) 0.426 0.606 1.138
CUDA実装(__ldg関数使用)
0.417 0.560 -
CUDA実装(テクスチャメモリ使用)
0.531 0.618 1.168
CC3.0では__ldgが使えない・・・
テンプレートマッチング
• 画像中から特定のパターンを見付ける処理
• 類似度の指標はいくつかある
– SSD:差の2乗の総和
– SAD:差の絶対値の総和
etc…
• 今回はSSDという指標について実装します
参考:http://imagingsolution.blog107.fc2.com/blog-entry-186.html
テンプレートマッチング(CPU実装)
ソースコード:https://github.com/atinfinity/matchTemplateGpu
for(int y = 0; y < result.rows; y++){
float* presult = result.ptr<float>(y);
for(int x = 0; x < result.cols; x++){
long sum = 0;
for(int yy = 0; yy < templ.rows; yy++){
const uchar* pimg = img.ptr<uchar>(y + yy);
const uchar* ptempl = templ.ptr<uchar>(yy);
for(int xx = 0; xx < templ.cols; xx++){
int diff = pimg[x + xx] - ptempl[xx];
sum += (diff*diff);
}
}
presult[x] = sum;
}
}
画素値の差を計算
差の二乗和を加算
テンプレートマッチング
1. Naive実装 CPU実装の類似度計算部分をそのままCUDAに持ってくる
2. shared memory利用 テンプレート画像は頻繁に参照するため、shared memoryに格納
テンプレートサイズは任意なので動的にshared memoryを確保
3. 2. + __ldg利用 __ldg関数を使ってRead-Onlyキャッシュ経由でデータ読み込み
ソースコード:https://github.com/atinfinity/matchTemplateGpu
テンプレートマッチング(Naive実装)
ソースコード:https://github.com/atinfinity/matchTemplateGpu
if((x < result.cols) && (y < result.rows)){
long sum = 0;
for(int yy = 0; yy < templ.rows; yy++){
for(int xx = 0; xx < templ.cols; xx++){
int diff = (img.ptr((y+yy))[x+xx] - templ.ptr(yy)[xx]);
sum += (diff*diff);
}
}
result.ptr(y)[x] = sum;
}
差の二乗和を加算
画素値の差を計算
テンプレートマッチング(32x32)
入力画像はw=1920、h=1080のグレースケール画像 単位はms 5回計測して平均をとる
GTX 1080(Pascal)
GTX TITAN X(Maxwell)
GTX680(Kepler)
CPU実装(Naive) 1696.070 1728.700 1107.840
OpenCV(Mat) 36.876 36.782 38.723
CUDA実装(Naive) 13.845 19.483 46.181
CUDA実装(shared memory)
7.851 11.834 49.417
CUDA実装(shared memory + __ldg)
6.931 9.641 -
CC3.0では__ldgが使えない・・・
テンプレートマッチング(64x64)
入力画像はw=1920、h=1080のグレースケール画像 単位はms 5回計測して平均をとる
GTX 1080(Pascal)
GTX TITAN X(Maxwell)
GTX680(Kepler)
CPU実装(Naive) 5527.08 5519.98
OpenCV(Mat) 65.84 65.32
CUDA実装(Naive) 36.64 46.87
CUDA実装(shared memory)
27.50 38.87
CUDA実装(shared memory + __ldg)
25.15 33.02 -
速度計測いろいろ(OpenCV)
• cudafilters– GaussianBlur
– BoxFilter
• cudafeatures2d– ORB
– FAST
• cudaobjdetect– HOG
速度計測いろいろ(OpenCV)
入力画像はw=800、h=640のグレースケール画像 単位はms 5回計測して平均をとる
GTX 1080(Pascal)
GTX TITAN X(Maxwell)
GTX680(Kepler)
GaussianBlur 0.065 0.237 0.757
BoxFilter 0.265 0.564 1.671
ORB 4.295 6.688 11.665
FAST 0.443 0.474 1.252
HOG 9.884 13.099 40.300
やり残したこと
• テンプレートマッチング(CUDA版)のチューニング– 演算部分の最適化– 【追記】long型を使用している箇所を見直す– アルゴリズムそのものの見直し
• CUDA8から追加された機能を試す– cudaMemPrefetchAsyncなるAPIが増えているのに気付いたけど使い方がよくわからず・・・• 【追記】Unified Memoryのprefetchに関するAPIのようです。
• OpenCVベンチマーク– 詳細なプロファイリングおよび分析
• NPPベンチマーク– CUDA8のNPPはPascal世代にも最適化されている?
まとめ
• GpuMatとCUDAカーネルの連携は簡単– cudevモジュールのGlobPtrSz、PtrStepSzが便利
– 【追記】CUDAカーネルにGpuMatクラスのインスタンスを直接渡せる
• GTX 1080では動作クロックが大きく向上– 演算ネックとなる問題に対しては恩恵が得られている
• OpenCVのcudaモジュールも(少なくとも今回検証した関数については)GTX 1080上で高速に動作– ただし、OpenCVのcudaモジュールが最新アーキテクチャに対して十分な最適化がなされているかは要検証
• Pascal Tuning Guide欲しい!!
おわり