Upload
lyhanh
View
251
Download
10
Embed Size (px)
Citation preview
GPGPUによる高速画像処理 ~リアルタイム画像処理への挑戦~
名古屋大学大学院情報科学研究科
出口 大輔
リアルタイム画像処理
2
発表の流れ
GPGPUを始める前に GPGPUの基礎知識
CUDAの使い方 CUDAを使う前に
プログラミングの予備知識
CUDAを使って“Hello World”
GPGPUにチャレンジ 行列積の計算
テンプレートマッチング
ガウシアンフィルタ
SIFT特徴量の計算
3
~GPGPUって何?~
4
GPGPUって?
GPGPUは何の略?
General-Purpose computation on GPUs
GPUを汎用計算に利用しようという試み
現在は「GPUコンピューティング」とも呼ばれる
どうしてGPGPUが注目されているのか?
5
0
200
400
600
800
1000
1200
1400
1600
1900 1900 1900 1900 1900 1900 1900 1900 1900
CPUとGPUの性能比較
6
2003 2004 2005 2006 2008
※NVIDIA CUDA Programming Guide 4.0より引用
[GFLOP/s]
ピーク性能
NVIDIA GPU
Intel
CPU
Geforece GTX280
Geforece 8800GTX
Quad Core
Xeon 3.2GHz Core2 Duo
3.0GHz
2007 2010 2009
Geforece GTX480
GPGPUって?
GPGPUは何の略?
General-Purpose computation on GPUs
GPUを汎用計算に利用しようという試み
現在は「GPUコンピューティング」とも呼ばれる
どうしてGPGPUが注目されているのか?
GPUの計算性能が飛躍的に向上
最新のGPUは 1.5 TFLOPS 以上の演算性能(CPUの約10倍)
GPUはCPUと比較して並列計算に優れている
GeForce GTX580 では 512 コアによる並列計算が可能
手頃な価格で入手可能
GeForce GTX580 は約5万円で購入可能
7
GPGPUの活用例
動画像処理 CyberLink PowerDirector 7 ビデオエフェクトのレンダリングを高速化
TMPGEnc 4.0 XPress フィルター処理・デコード処理の高速化
画像処理 Adobe Photoshop CS4 / CS5,Adobe Premire CS5 各種フィルタ処理の高速化
OpenCV 各種画像処理の高速化
数値計算 MATLAB FFTの高速化
8
GPUの歴史: 1999年以前
1970年 ~ 1990年 初期開発の時代
ソフトウェアによるグラフィックス処理
プログラム可能なGPUに関する初期研究 Ikonas System [1], Pixel Machine [2]
1990年 ~ 1999年 GPU技術の黎明期
3Dグラフィックス・アクセラレータの開発
グラフィックス向けライブラリの開発 OpenGL(1992年~), DirectX(1995年~)
9
[1] J. N. England, “A system for interactive modeling of physical curved surface
objects,” Proceedings of SIGGRAPH 78, pp.336-340. 1978
[2] M. Potmesil and E. M. Hoffert, “The Pixel Machine: A Parallel Image
Computer,” Proceedings of SIGGRAPH89, pp.69-78, 1989
GPUの歴史: 1999年~
“GPU”の誕生
NVIDIA社の GeForce 256 の登場
ハードウェア T&L※をサポート
CPU負荷を大幅に削減
グラフィックスパイプライン
ハードウェア固定の処理
自由表現な表現は不可
10
頂点処理
(ハードウェア固定)
ジオメトリ処理
(クリッピング等)
ラスタライズ処理
画面出力 ※Transform & Lighting
ピクセル処理
(ハードウェア固定)
GPUの歴史: 2003年~
プログラマブルシェーダの登場
Vertex Shader
頂点座標の変換処理
Pixel Shader
画素の輝度計算処理
シェーダ言語の進化
アセンブラから高級言語へ
Cg, HLSL, GLSL
柔軟な映像表現が可能に
グラフィックス以外への応用
GPGPUへの関心が高まる
11
Vertex Shader
(プログラマブル)
ジオメトリ処理
(クリッピング等)
ラスタライズ処理
画面出力
Pixel Shader
(プログラマブル)
GPUの歴史: 2007年~
GPGPUの開発環境の整備 CUDA (NVIDIA)
ATI Stream (AMD)
OpenCL
DirectCompute
GPGPU時代の到来 物理シミュレーション
数値計算
信号解析
画像処理・認識
・・・
12
Vertex Shader
(プログラマブル)
Geometry Shader
(プログラマブル)
ラスタライズ処理
画面出力
Pixel Shader
(プログラマブル)
統合型シェーダ
GPUの歴史: 2009年~
Fermiアーキテクチャの登場
汎用計算向けのアーキテクチャ※
L1/L2キャッシュの搭載
複数カーネルの同時実行のサポート
倍精度浮動小数点演算の高速化
ECCメモリのサポート
アトミックなメモリ操作の高速化
C++のフルサポート
GPGPU関連のライブラリ&ツールの充実
CUBLAS, CUFFT, Thrust, NPP
Parallel Nsight, Visual Profiler
13
※ビデオカードによって
サポート状況は異なる
~CUDAを使う前に~
14
CUDAって何?
CUDA(Compute Unified Device Architecture)
発音は「クーダ」もしくは「キューダ」
NVIDIA社が提供するGPUを利用する ための統合開発環境
GeForce 8以降のハードウェアで利用可能
グラフィックス処理APIの知識は不要
CPUでプログラムを実行する感覚
C/C++を用いてプログラムの開発が可能
既存のアルゴリズムの移植も比較的容易
15
CUDAが利用可能な環境
CUDA対応のグラフィックスカード
OSの対応状況(CUDA 4.0) Windows XP(32bit, 64bit)
Windows Vista(32bit, 64bit)
Windows Server 2008(32bit, 64bit)
Linux(32bit, 64bit)
Mac OS
16
GeForce GTX 580, GTX 480, GTX 260, 8800シリーズ,他
Quadro Plex 2200 D2, FX 5800, FX 5600, 5000, 6000, 他
Tesla S2050, C2070, S1070, C1060, S870, D870, C870
※TeslaはHPCに特化した製品であり映像出力を持たない
CUDAを使うための準備(Windows編)
「CUDA ZONE」から次をダウンロード
NVIDIA Driver
CUDA対応のビデオドライバー
CUDA Toolkit 4.0 ※
コンパイラ(nvcc)
CUBLASやCUFFT
ライブラリ
ドキュメント
CUDA SDK 4.0
サンプル
※ 2011年5月25日にリリース
17
http://www.nvidia.com/object/cuda_home.html
CUDAを動かしてみよう!!
18
Volumetric Particle Shadows
※「CUDA SDK」内のサンプルの実行結果
Image Denoising
~プログラミングの予備知識~
19
CUDA対応のGPU(G80, GT200)
ストリーミング・マルチプロセッサ(SM)
8 個のスカラープロセッサ(SP)
16KBの共有メモリ
スレッド間の同期機構
マルチプロセッサ内でのみ可能
マルチプロセッサ間での同期には CPU処理が必要
GeForce GTX280 の場合
30 基のマルチプロセッサを搭載
1GByte以上のグローバルメモリ
20
SP SP SP SP
SP SP SP SP
マルチプロセッサ #1
SP SP SP SP
SP SP SP SP
マルチプロセッサ #2
CUDA対応のGPU(Fermi)
ストリーミング・マルチプロセッサ(SM)
32 個のスカラープロセッサ(SP)
48KBの共有メモリ
スレッド間の同期機構
マルチプロセッサ内でのみ可能
マルチプロセッサ間での同期には CPU処理が必要
GeForce GTX480 の場合
15 基のマルチプロセッサを搭載
1GByte以上のグローバルメモリ
21
SM #1 SP SP SP SP SP SP SP SP
SP SP SP SP SP SP SP SP
SP SP SP SP SP SP SP SP
SP SP SP SP SP SP SP SP
SM #2 SP SP SP SP SP SP SP SP
SP SP SP SP SP SP SP SP
SP SP SP SP SP SP SP SP
SP SP SP SP SP SP SP SP
CUDAのプログラミングモデル
GPUの特徴
多数のスレッドが高い並列性をもって処理を実行
GPU のみでプログラムを実行できない
CPUとの連携が不可欠
CUDAにおける計算の流れ
GPUを並列演算可能なデバイスとして扱う
複数のスレッドを同時に実行できる外部CPU
階層的にスレッドを管理
スレッドのまとまり = ブロック
ブロックのまとまり = グリッド
問題を分割して計算する際に便利
22
スレッドを3次元的に配置
各スレッドの ID を X, Y, Z の3要素で表現
スレッドのまとまりをブロックとして管理
グリッド
ブロック(0,1,1)
ブロック(0,0,1)
スレッド
(0,0,1) スレッド
(0,1,1) スレッド
(0,2,1)
スレッド
(1,0,1) スレッド
(1,1,1) スレッド
(1,2,1)
ブロック(1,0,1)
スレッド
(0,0,1) スレッド
(0,1,1) スレッド
(0,2,1)
スレッド
(1,0,1) スレッド
(1,1,1) スレッド
(1,2,1)
ブロック(1,1,1) ブロック(0,1,0) ブロック(1,1,0)
ブロック(1,0,0)
スレッド
(0,0,0) スレッド
(0,1,0) スレッド
(0,2,0)
スレッド
(1,0,0) スレッド
(1,1,0) スレッド
(1,2,0)
スレッド
(0,0,0) スレッド
(1,0,0) スレッド
(2,0,0)
スレッド
(0,1,0) スレッド
(1,1,0) スレッド
(2,1,0)
CUDAにおけるスレッド管理
23
ブロック(0,0,0)
スレッド
(0,0,0) スレッド
(0,1,0) スレッド
(0,2,0)
スレッド
(1,0,0) スレッド
(1,1,0) スレッド
(1,2,0)
スレッド
(0,0,0) スレッド
(1,0,0) スレッド
(2,0,0)
スレッド
(0,1,0) スレッド
(1,1,0) スレッド
(2,1,0)
階層的スレッドの利用方法
一般的な画像処理で利用する場合
ブロック内のスレッド数を決定
16×16 = 256 スレッド
画像内にブロックを配置
ブロック内のスレッド が各画素を処理
計算範囲の求め方
ブロックID
スレッドID
ブロック内スレッド数
24
T00 T10 TN0
T01
ブロック#1
T0M TNM
T00 T10 TN0
T01
T0M TNM
スレッド
(0,0,0) スレッド
(1,0,0) スレッド
(2,0,0)
スレッド
(0,1,0) スレッド
(1,1,0) スレッド
(2,1,0)
ブロック(0,0,0)
CUDAにおける計算の流れ
25
ブロック(1,0,0)
ブロック(0,1,0) ブロック(1,1,0)
処理1
グリッド 1
ブロック (0,0,0) ブロック (1,0,0)
グリッド 2 処理2
スレッド
(0,0,0) スレッド
(1,0,0) スレッド
(2,0,0)
スレッド
(0,1,0) スレッド
(1,1,0) スレッド
(2,1,0)
CUDAのメモリモデル
26
共有メモリ
スレッド (0,0,0)
ローカルメモリ
レジスタ
スレッド(1,0,0)
ブロック (0,0,0)
共有メモリ
スレッド(0,0,0) スレッド(1,0,0)
ブロック(1,0,0)
コンスタントメモリ
テクスチャメモリ
グローバルメモリ
ローカルメモリ
レジスタ
ローカルメモリ
レジスタ
ローカルメモリ
レジスタ
CUDAで利用可能なメモリ
グローバルメモリ 大量のメモリを利用可能
低速なメモリアクセス(400~600クロック必要) Fermiアーキテクチャではキャッシュ機構(L1/L2)を搭載
共有メモリ レジスタと同じ速度でアクセス可能
マルチプロセッサ1基あたり16KB(Fermiは最大48KB) Fermiの場合は一部をL1キャッシュとして利用可能
テクスチャメモリ キャッシュ機構による高速なアクセスが可能
ハードウェア線形補間や正規化テクスチャ座標が利用可能
コンスタントメモリ キャッシュ機構による高速なアクセスが可能
マルチプロセッサ1基あたり64KB
27
各GPUで使用できる機能の違い
使用できる機能を Compute Capability で区別
現在 1.0 ~ 2.1 のGPUが存在
Compute Capability による機能の違い
1.0:初期リリース
GeForce 8800GTX, Quadro FX 5600, 他
1.1:アトミックなメモリ操作のサポート
GeForce 9800 GTX, Quadro FX 3700, 他
1.3:倍精度浮動小数点演算のサポート
GeForce GTX 280, Quadro FX 5800, 他
2.x :グローバルメモリのキャッシュをサポート
GeForce GTX480,Quadro 5000,他
28
Compute Capabilityによる機能の違い
29
Compute Capability
1.0 1.1 1.2 1.3 2.x
3次元グリッド × ○
倍精度浮動小数 × ○
32ビット整数の
アトミック演算 × ○
64ビット整数の
アトミック演算 × ○
単精度浮動小数の
アトミック演算 × ○
CUDAにおける制限事項
30
Compute Capability
1.0 1.1 1.2 1.3 2.x
1ブロックあたりのスレッド数
512 1024
1マルチプロセッサあたりのスレッド数
768 1024 1536
1マルチプロセッサあたりのレジスタ数
8192 16384 32768
コンスタントメモリ 64KB
共有メモリ 16KB 48KB
2Dテクスチャ 216×215 216×216
~ CUDAを使って“Hello World”~
31
CUDAを使って“Hello World!!”
Hello World!! を表示するサンプル(main.cu)
32
__global__ void hello( char *data ) { char *text = "Hello World!!¥n"; data[ threadIdx.x ] = text[ threadIdx.x ]; } int main( int argc, char *argv[] ) { char *dData, hData[ 15 ]; cudaMalloc( ( void ** )&dData, sizeof( char ) * 15 ); dim3 nThreads( 15, 1 ); dim3 nBlocks( 1, 1 ); hello<<< nBlocks, nThreads >>>( dData ); cudaMemcpy( hData, dData, sizeof( char ) * 15, cudaMemcpyDeviceToHost ); printf( "%s", hData ); cudaFree( dData ); return( 0 ); }
CUDAを使って“Hello World!!”
Hello World!! を表示するサンプル(main.cu)
33
__global__ void hello( char *data ) { char *text = "Hello World!!¥n"; data[ threadIdx.x ] = text[ threadIdx.x ]; } int main( int argc, char *argv[] ) { char *dData, hData[ 15 ]; cudaMalloc( ( void ** )&dData, sizeof( char ) * 15 ); dim3 nThreads( 15, 1 ); dim3 nBlocks( 1, 1 ); hello<<< nBlocks, nThreads >>>( dData ); cudaMemcpy( hData, dData, sizeof( char ) * 15, cudaMemcpyDeviceToHost ); printf( "%s", hData ); cudaFree( dData ); return( 0 ); }
_ _global_ _, threadIdx
dim3, <<<…>>>
CUDAで拡張された部分
CUDAを使って“Hello World!!”
Hello World!! を表示するサンプル(main.cu)
34
__global__ void hello( char *data ) { char *text = "Hello World!!¥n"; data[ threadIdx.x ] = text[ threadIdx.x ]; } int main( int argc, char *argv[] ) { char *dData, hData[ 15 ]; cudaMalloc( ( void ** )&dData, sizeof( char ) * 15 ); dim3 nThreads( 15, 1 ); dim3 nBlocks( 1, 1 ); hello<<< nBlocks, nThreads >>>( dData ); cudaMemcpy( hData, dData, sizeof( char ) * 15, cudaMemcpyDeviceToHost ); printf( "%s", hData ); cudaFree( dData ); return( 0 ); }
CUDAの言語拡張(1)
言語拡張により追加された修飾子
関数に対する修飾子
変数に対する修飾子
35
_ _global_ _ CPU から呼び出され,GPU で実行される関数
_ _device_ _ GPU から呼び出され,GPU で実行される関数
_ _host_ _ CPU から呼び出され,CPU で実行される関数
_ _constant_ _ GPU 上のコンスタントメモリに存在する変数
_ _shared_ _ GPU 上の共有メモリに存在する変数
_ _device_ _ GPU 上のメモリに存在する変数
CUDAを使って“Hello World!!”
Hello World!! を表示するサンプル(main.cu)
36
__global__ void hello( char *data ) { char *text = "Hello World!!¥n"; data[ threadIdx.x ] = text[ threadIdx.x ]; } int main( int argc, char *argv[] ) { char *dData, hData[ 15 ]; cudaMalloc( ( void ** )&dData, sizeof( char ) * 15 ); dim3 nThreads( 15, 1 ); dim3 nBlocks( 1, 1 ); hello<<< nBlocks, nThreads >>>( dData ); cudaMemcpy( hData, dData, sizeof( char ) * 15, cudaMemcpyDeviceToHost ); printf( "%s", hData ); cudaFree( dData ); return( 0 ); }
CUDAの言語拡張(2)
CUDAで利用可能な組み込み型
GPU内のスレッドを識別する組み込み変数
37
dim3 整数 x, y, z からなる 3 次元ベクトル
(スレッド数やブロック数の指定に利用)
uchar2, int2, float2, … x, y からなる 2 次元ベクトル
uchar3, int3, float3, … x, y, z からなる 3 次元ベクトル
uchar4, int4, float4, … x, y, z, w からなる 4 次元ベクトル
gridDim グリッドの次数
blockIdx スレッドが属するブロックのインデックス
blockDim スレッドが属するブロックの次数
threadIdx ブロック内のスレッドのインデックス
gridDim
= 2×2×2
スレッドを識別する組み込み変数
38
グリッド
ブロック(0,1,1)
ブロック(1,0,1)
スレッド
(0,0,1) スレッド
(0,1,1) スレッド
(0,2,1)
スレッド
(1,0,1) スレッド
(1,1,1) スレッド
(1,2,1)
ブロック(1,1,1) ブロック(0,1,0) ブロック(1,1,0)
ブロック(1,0,0)
スレッド
(0,0,0) スレッド
(0,1,0) スレッド
(0,2,0)
スレッド
(1,0,0) スレッド
(1,1,0) スレッド
(1,2,0)
スレッド
(0,0,0) スレッド
(1,0,0) スレッド
(2,0,0)
スレッド
(0,1,0) スレッド
(1,1,0) スレッド
(2,1,0)
ブロック(0,0,1)
スレッド
(0,0,1) スレッド
(0,1,1) スレッド
(0,2,1)
スレッド
(1,0,1) スレッド
(1,1,1) スレッド
(1,2,1)
ブロック(0,0,0)
blockIdx
blockDim
= 3×2×2 スレッド
(0,0,0) スレッド
(0,1,0) スレッド
(0,2,0)
スレッド
(1,0,0) スレッド
(1,1,0) スレッド
(1,2,0)
スレッド
(1,0,0) スレッド
(2,0,0)
スレッド
(0,1,0) スレッド
(1,1,0) スレッド
(2,1,0)
スレッド
(0,0,0)
threadIdx
C/C++プログラミングとの相違点
CPUとGPUで実行するコードを明確に区別
CPUで実行する場合は “__host__”(省略可)を付与
GPUで実行する場合は “__global__” を付与
CPUとGPUで処理可能なメモリ空間の違い
CPUとGPU間でメモリ転送が必要
スレッド数を指定してGPU上の関数を呼び出し
関数名<<< ブロック数, スレッド数 >>>( … );
ブロック数とスレッド数は実行時に指定可能
問題サイズに合わせて調整可能
39
CUDAを使って“Hello World!!”
Hello World!! を表示するサンプル(main.cu)
40
__global__ void hello( char *data ) { char *text = "Hello World!!¥n"; data[ threadIdx.x ] = text[ threadIdx.x ]; } int main( int argc, char *argv[] ) { char *dData, hData[ 15 ]; cudaMalloc( ( void ** )&dData, sizeof( char ) * 15 ); dim3 nThreads( 15, 1 ); dim3 nBlocks( 1, 1 ); hello<<< nBlocks, nThreads >>>( dData ); cudaMemcpy( hData, dData, sizeof( char ) * 15, cudaMemcpyDeviceToHost ); printf( "%s", hData ); cudaFree( dData ); return( 0 ); }
GPU上で実行される関数
GPU上のメモリを解放
CPUからGPUへメモリ転送
GPU上にメモリを確保
ブロック内に配置する スレッド数 15×1 = 15
総ブロック数 1×1 = 1
スレッドのレジスタ数を調査
Visual Studio 2008 コマンドプロンプトを起動
コマンドプロンプト上で main.cu をコンパイル
合計レジスタ数をチェック
41
C:¥Your¥Path>nvcc main.cu --ptxas-options=-v --compile
main.cu
ptxas info : Compiling entry function '_Z5helloPc' for 'sm_10'
ptxas info : Used 2 registers, 8+16 bytes smem, 15 bytes cmem[0]
レジスタ数
2 × 15 = 30
レジスタ数 生成スレッド数 合計
~Thrustライブラリの使い方~
42
Thrustライブラリって?
CUDAで利用できるテンプレートライブラリ
CUDAとOpenMPに対応したC++ STLの並列版
URL: http://thrust.googlecode.com/
Thrustライブラリの特徴
コンテナ
CPUとGPUのデータをコンテナとして管理
アルゴリズム
コンテナに対してアルゴリズムを適用可能
直感的なインターフェース
CUDAの複雑なAPIに関する知識は不要
メモリの確保/解放/転送がとても簡単
43
Thrustライブラリの使い方
GPU上で「1,2,3,…,100」の総和を計算
44
#include <thrust/host_vector.h>
#include <thrust/device_vector.h>
#include <thrust/sequence.h>
#include <thrust/reduce.h>
int main( int argc, char *argv[] )
{
// CPU上のメモリを確保
thrust::host_vector< int > hvec( 100 );
// データを初期化 1, 2, 3, ...
thrust::sequence( hvec.begin( ), hvec.end( ), 1 );
// GPU上のメモリを確保
thrust::device_vector< int > dvec( 100 );
// CPU -> GPU のメモリ転送
dvec = hvec;
int val = thrust::reduce( dvec.begin( ), dvec.end( ), 0, thrust::plus< int >( ) );
printf( "%d¥n", val );
return( 0 );
}
コンテナの使い方(1)
メモリの確保
thrust::host_vector< int > hvec( 20 );
CPU上のメモリに要素数20の「int」配列を確保
thrust::device_vector< float > dvec( 100 );
GPU上のメモリに要素数100の「float」配列を確保
メモリの解放
コンテナオブジェクトの消滅時に自動解放
CPUの場合:free( )
GPUの場合:cudaFree( )
明示的なメモリ解放:dvec.clear( )
45
コンテナの使い方(2)
メモリの転送
代入操作でメモリ転送が可能
コンテナの各要素へのアクセス
配列と同様にアクセス可能
46
thrust::host_vector< float > hvec( 20 );
thrust::device_vector< float > dvec( 20 );
dvec = hvec;
hvec[ 5 ] = 100.0f;
dvec[ 1 ] = 23.4f; 内部でcudaMemcpyが呼ばれるので注意
イテレータ(1)
コンテナの要素を指すポインタ
STLのイテレータと同じように使用可能
thrust::device_vector< int > dvec( 4 );
thrust::device_vector< int >::iterator ite = dvec.begin( );
47
0 1 2 3
dvec.begin( ) dvec.end( )
dvec.begin( ) + 3
dvec
イテレータ(2)
コンテナの各要素へのアクセス
thrust::device_vector< int > dvec( 4 );
thrust::device_vector< int >::iterator ite = dvec.begin( );
*ite = 10;
++ite;
*ite = 25;
48
dvec[ 0 ] = 10 と等価
dvec[ 1 ] = 25 と等価
アルゴリズムと使用方法
CPUとGPUのデータに対して同じ様に適用可能
GPUの場合はGPUを使用して並列処理される
アルゴリズムの使用例
コンテナのデータすべてに1を代入する
thrust::fill( dvec.begin( ), dvec.end( ), 1 );
コンテナのデータに 1, 2, 3, … の値で初期化する
thrust::sequence( dvec.begin( ), dvec.end( ) );
49
1 1 1 1 1 1 1 1
1 2 3 4 5 6 7 N
Parallel Reduction(1)
コンテナ内の要素を1つの値に集約する処理
GPUのスレッドが並列に処理を実行
例:総和計算の場合
50
36 1 2 3 4 5 6 7 8
GPU上のデータ
3 7 11 15
10 26
36
CUDAスレッドの同期
CUDAスレッドの同期
Parallel Reduction(2)
Thrustライブラリを用いた集約処理
総和の計算
int val = thrust::reduce( dvec.begin( ), dvec.end( ) );
最大値の計算
int val = thrust::reduce( dvec.begin( ), dvec.end( ),
0, thrust::maximum< int >( ) );
51
1 2 3 4 5 6 7 10 55
1 10 3 8 5 9 7 6 10 2
CUDAとThrustを組み合わせる
Hello World!! を表示するサンプル
52
#include <thrust/host_vector.h> #include <thrust/device_vector.h> __global__ void hello( char *data ) { char *text = "Hello World!!¥n"; data[ threadIdx.x ] = text[ threadIdx.x ]; } int main( int argc, char *argv[] ) { thrust::host_vector< char > hvec( 15 ); thrust::device_vector< char > dvec( 15 ); dim3 nThreads( 15, 1 ); dim3 nBlocks( 1, 1 ); hello<<< nBlocks, nThreads >>>( thrust::raw_pointer_cast( &dvec[ 0 ] ) ); hvec = dvec; printf( "%s", &hvec[ 0 ] ); return( 0 ); }
CPUからGPUへメモリ転送
GPU上のメモリを指すポインタを取得
53
GPGPUにチャレンジ
行列積の計算 CUDAにおける基本的な実装方法
共有メモリ
テンプレートマッチング 2次元テクスチャメモリ
ハードウェア線形補間
正規化テクスチャ座標
Parallel Reduction アルゴリズム
ガウシアンフィルタ 1次元テクスチャメモリ
コンスタントメモリ
SIFT特徴量の計算 1・2次元テクスチャメモリ
コンスタントメモリ
54
~行列積の計算~
55
GPUによる行列積の計算
行列積計算の特徴
各要素はすべて独立に計算可能
GPUによる並列計算が可能
同じメモリ領域へ頻繁にアクセス
メモリアクセス速度がボトルネック
GPU上での実装方法
1. 行列積の計算式に忠実な実装(GPU#1)
2. 共有メモリを利用した高速化(GPU#2)
ブロック単位(部分行列)で行列積を計算
共有メモリをキャッシュとして利用
56
行列積C=A×Bの流れ
1. 行列A, B, CのメモリをGPU上に確保
2. 行列A, BのデータをGPUに転送
3. 行列Cの各要素cmnを計算
N 次正方行列を仮定
ただし,N は16の倍数
4. 計算結果をCPUに転送
57
N
k
knmkmn bac1
行列A 行列C
行列B
mnc
mka
knb
m行
n列
N
N
処理の流れ
CPU側の処理
行列の初期化
GPU側の処理
行列Cの各要素を計算
58
行列の初期化
CPU
メモリ確保
GPU
行列積の計算
GPU
データ転送
CPU GPU
データ転送
GPU CPU
int main( int argc, char *argv[] )
{
int N = 512; // 行列A, B, Cのサイズ
float *hA, *hB, *hC; // CPU(host)側で利用するメモリへのポインタ
float *dA, *dB, *dC; // GPU(device)側で利用するメモリへのポインタ
/* CPU側のメモリを確保*/
/* GPU側のメモリを確保*/
/* CPU側のメモリをGPU側へ転送 */
/* 実行するGPUのスレッド数,ブロック数を設定 */
/* GPUのカーネルを実行し,C=A×B の結果を dC に格納 */
/* GPUの計算結果をCPU側へ転送 */
/* CPUとGPUそれぞれのメモリを解放 */
return( 0 );
}
行列積計算のCPU処理(ソース)
59
行列積計算のCPU処理(1)
行列積の計算で使用する変数
CPUとGPUでメモリを確保
CPU:malloc, new, cudaMallocHost でメモリを確保
GPU:cudaMalloc によりグローバルメモリを確保
60
int N = 512; // 行列A, B, Cのサイズ
float *hA, *hB, *hC; // CPU(host)側で利用するメモリへのポインタ
float *dA, *dB, *dC; // GPU(device)側で利用するメモリへのポインタ
/* CPU側のメモリを確保 */
hA = ( float * )malloc( N * N * sizeof( float ) );
hB = ( float * )malloc( N * N * sizeof( float ) );
hC = ( float * )malloc( N * N * sizeof( float ) );
/* GPU側のメモリを確保 */
cudaMalloc( ( void ** )&dA, N * N * sizeof( float ) );
cudaMalloc( ( void ** )&dB, N * N * sizeof( float ) );
cudaMalloc( ( void ** )&dC, N * N * sizeof( float ) );
行列積計算のCPU処理(2)
CPUとGPU間のメモリ転送
CPU→GPU
cudaMemcpy( dst, src, size, cudaMemcpyHostToDevice );
GPU→CPU
cudaMemcpy( dst, src, size, cudaMemcpyDeviceToHost );
CPUとGPUで確保したメモリの解放
CPU:free, delete, cudaFreeHost でメモリを解放
GPU:cudaFree でグローバルメモリを解放
61
転送先 転送バイト数
転送元 転送方向
この部分を変更
行列積計算のCPU処理(3)
GPU上で処理を実行
計算範囲の設定
N 次正方行列を仮定
ただし N は16の倍数
行列Cをサイズ16×16のブロックに分割
ブロック内の各要素を各スレッドが計算
合計M×M 個のブロックを配置
各スレッドが cmn を計算
n = threadIdx.x + blockIdx.x × blockDim.x;
m = threadIdx.y + blockIdx.y × blockDim.y;
GPU上で関数を実行
multiply<<< ブロック数, スレッド数 >>>( … );
62
行列C
16
16
(threadIdx.y, threadIdx.x)
(blockIdx.y, blockIdx.x)
行方向のブロック数 (=16)
int main( int argc, char *argv[] )
{
int N = 512; // 行列A, B, Cのサイズ
float *hA, *hB, *hC; // CPU(host)側で利用するメモリへのポインタ
float *dA, *dB, *dC; // GPU(device)側で利用するメモリへのポインタ
/* CPU側のメモリを確保*/
/* GPU側のメモリを確保*/
/* CPU側のメモリをGPU側へ転送 */
/* 実行するGPUのスレッド数,ブロック数を設定 */
dim3 nThreads( 16, 16 );
dim3 nBlocks( N / nThreads.x, N / nThreads.y );
/* GPUのカーネルを実行し,C=A×B の結果を dC に格納 */
multiply<<< nBlocks, nThreads >>>( dA, dB, dC, N );
/* GPUの計算結果をCPU側へ転送 */
/* CPUとGPUそれぞれのメモリを解放 */
return( 0 );
}
行列積計算のCPU処理(まとめ)
63
GPU上で処理を実行
GPU上のメモリを指定
実装方法(GPU#1)
行列積C=A×Bの各要素cmnを計算
要素cmnをGPU上で並列計算
64
__global__ void multiply1( float *dA, float *dB, float *dC, int N )
{
int n = threadIdx.x + blockIdx.x * blockDim.x;
int m = threadIdx.y + blockIdx.y * blockDim.y;
float sum = 0.0f;
for( int k = 0 ; k < N ; k++ )
{
sum += dA[ m + k * N ] * dB[ k + n * N ];
}
dC[ m + n * N ] = sum;
}
N
k
knmkmn bac1
行列A 行列C
行列B
mnc
mka
knb
m行
n列
N
N
各要素cmnを部分行列(ブロック)の積で計算
ブロックサイズが16×16の場合
ブロック内で同じメモリを参照
高速な共有メモリを利用
グローバルメモリへの アクセスを削減
2×N×162 回
◦
2×N×16 回
実装方法(GPU#2)
65
16
1
)1(16
16
N
t
t
tk
knmkmn bac
行列A 行列C
行列B
mnc
mka
knbN
N
16
16
16
16
16分の1
実装方法(GPU#2)
66
__global__ void multiply2( float *dA, float *dB, float *dC, int N )
{
int n = threadIdx.x + blockIdx.x * blockDim.x;
int m = threadIdx.y + blockIdx.y * blockDim.y;
float sum = 0.0f;
for( int k = 0 ; k < N ; k += 16 )
{
__shared__ float tA[ 16 ][ 16 ];
__shared__ float tB[ 16 ][ 16 ];
tA[ threadIdx.y ][ threadIdx.x ] = dA[ m + ( k + threadIdx.x ) * N ];
tB[ threadIdx.x ][ threadIdx.y ] = dB[ ( k + threadIdx.y ) + n * N ];
__syncthreads( );
for( int t = 0 ; t < 16 ; t++ )
{
sum += tA[ threadIdx.y ][ t ] * tB[ threadIdx.x ][ t ];
}
__syncthreads( );
}
dC[ m + n * N ] = sum;
}
共有メモリの宣言
ブロック内のスレッドを同期
ブロック内のスレッドを同期
各スレッドが独立して行列積を計算
各スレッドが独立してメモリアクセス
行列A 行列C
行列B
mnc
mka
knbN
N
16
16
16
16
計算時間の比較(1)
CPU, GPU#1, GPU#2 の計算時間を比較
使用計算機
CPU: Intel Core i7-X980(3.33GHz)
OpenMPを使用して6スレッドで並列計算
GPU: NVIDIA Geforce GTX480
マルチプロセッサ数: 15(SP数 = 15×32 = 480)
Memory:1.5 GB
OS:Windows 7 SP1
67
0
50
100
150
200
250
300
350
400
450
500
16
32
48
64
80
96
112
128
144
160
176
192
208
224
240
272
288
304
320
336
352
368
400
416
432
448
464
480
496
528
544
560
592
608
624
656
672
688
720
736
752
768
784
800
計算時間の比較(2)
68
行列のサイズ (N 次正方行列)
[msec.]
CPU
GPU#1
GPU#2
N = 560
計算時間(N = 560 の場合)
・CPU#1: 91.7 msec.
・GPU#1: 18.3 msec. (×5.0)
・GPU#2: 06.7 msec. (×13.7)
まとめ(行列積)
GPU上での実装方法
1. 行列積の計算式に忠実な実装(GPU#1)
2. 共有メモリを利用した高速化(GPU#2)
大きさ16×16の部分行列積に分割
共有メモリをキャッシュとして利用
CPUとGPUそれぞれでの計算速度を比較
CPU < GPU#1 < GPU#2
メモリアクセスの工夫により約34倍の高速化
69
×5.0 ×13.7
~テンプレートマッチング~
70
テンプレートマッチング(1)
画像処理の分野で広く用いられている手法
基板の品質検査
画像中の特定物体(人物など)の検出
入力画像中からテンプレートに類似する部分を探索
入力画像中に窓を設定
窓を移動させながら 類似度を評価
71
テンプレート 窓
比較
テンプレートマッチング(2)
基本的なテンプレートマッチングの戦略 入力画像中の部分画像とテンプレートの類似度を評価
窓を移動させながら類似度計算 位置(X軸方向, Y軸方向)
拡大/縮小(スケール変化)
回転
類似度の例
SAD(Sum of Absolute Difference)
SSD(Sum of Squared Difference)
NCC(Normalized Cross Correlation)
窓の大きさに比例して計算コストが増加
膨大な回数の類似度評価が必要
計算コスト大
72
デモ(テンプレートマッチング)
73
テンプレートマッチングの実装方法
テンプレートマッチングの特徴
類似度は各位置で独立に計算可能
GPUによる並列計算が可能
頻繁に画像メモリへアクセス
テクスチャメモリのキャッシュ機能を利用
複数スケールでテンプレートと入力画像を比較
正規化テクスチャ座標とハードウェア線形補間の利用
解像度に依存しないメモリアクセス
74
・・・
スケール
処理の流れ
CPU側の処理
入力画像とテンプレートの読み込み
GPUスレッドの同期
GPU側の処理
類似度計算
75
スケールの変更
画像読み込み
CPU
メモリ確保
GPU
類似度計算
GPU
スレッド同期
CPU
データ転送
CPU GPU
データ転送
GPU CPU
テクスチャメモリとは?
GPU上の読み取り専用の特殊なメモリ領域 キャッシュを利用した高速なアクセスが可能
2次元アクセスに対して効率的
ハードウェアを利用した高速な線形補間が可能
テクスチャメモリの定義 texture<DataType, Type, ReadMode> texRef;
DataType: データの型(int,float 等)
Type: テクスチャの種類(1次元テクスチャなど) cudaTextureType1D,cudaTextureType2D,…
ReadMode: 値の範囲 cudaReadModeElementType: 各データ型の値を使用
cudaReadModeNormalizedFloat: 0 ~ 1に正規化(符号付きは-1~1)
76
メモリ確保と転送(1)
入力画像のメモリをGPU上に確保
テクスチャの定義(画素表現:RGBA)
cudaArrayを利用して2次元テクスチャを確保
CPUからGPUへメモリ転送
テクスチャメモリへの対応付け
77
cudaArray *imgArray;
cudaChannelFormatDesc c1 = cudaCreateChannelDesc< uchar4 >( );
cudaMallocArray( &imgArray, &c1, width, height );
cudaBindTextureToArray( imgTex, imgArray, c1 );
画素表現の指定
cudaMemcpyToArray( imgArray, 0, 0, pSrc, nBytes, cudaMemcpyHostToDevice );
転送データ量
転送元ポインタ CPUからGPUへ転送
texture< uchar4, cudaTextureType2D, cudaReadModeElementType > imgTex;
メモリ確保と転送(2)
テンプレートのメモリをGPU上に確保
テクスチャの定義
ハードウェア線形補間の有効化
正規化テクスチャ座標の有効化
78
ハードウェア線形補間の利用(1)
texture< uchar4, cudaTextureType2D, cudaReadModeNormalizedFloat > refTex;
refTex.normalized = 1;
refTex.filterMode = cudaFilterModeLinear;
ハードウェア線形補間の利用(2)
(0, 0) (W-1, 0)
(0, H-1) (W-1, H-1)
(0, 0) (1, 0)
(0, 1) (1, 1)
(0, 0) (1, 0)
(0, 1)
(0, 0) (1, 0)
(0, 1)
GPU上で類似度計算
79
__global__ void kernel( float *error, float *scale, int imgW, int imgH,
int areaW, int areaH, int maskW, int maskH, float s )
{
int i = threadIdx.x + blockDim.x * blockIdx.x;
int j = threadIdx.y + blockDim.y * blockIdx.y;
float err = 0.0f;
float _1_w = 1.0f / maskW; // テンプレートの幅に対するスケーリング係数
float _1_h = 1.0f / maskH; // テンプレートの高さに対するスケーリング係数
for( int n = 0 ; n < maskH ; n++ )
{
for( int m = 0 ; m < maskW ; m++ )
{
uchar4 p1 = tex2D( imgTex, i + m, j + n );
float4 p2 = tex2D( refTex, _1_w * m, _1_h * n ) * 255.0f;
err += ( p1.x - p2.x ) * ( p1.x - p2.x );
err += ( p1.y - p2.y ) * ( p1.y - p2.y );
err += ( p1.z - p2.z ) * ( p1.z - p2.z );
}
}
err *= _1_w * _1_h;
if( error[ i + j * imgW ] > err )
{
error[ i + j * imgW ] = err;
scale[ i + j * imgW ] = s;
}
}
テクスチャからデータを読み取り
SSDを計算
SSDが最小のものを記録
CPUによる類似度計算の制御
80
// スケール&誤差を保持するGPU側のメモリ領域
thrust::device_vector< float > derror( img.size( ), 1.0e10f );
thrust::device_vector< float > dscale( img.size( ), 0 );
float s = MIN_SCALE;
while( s <= MSCALE )
{
// 実行するGPUのスレッド数を指定
int W = ( int )img.width( ), H = ( int )img.height( );
int w = ( int )( ref.width( ) * s ), h = ( int )( ref.height( ) * s );
int threadNumX = 16, threadNumY = 16;
int blockNumX = ( W - w ) / threadNumX + ( ( ( W - w ) % threadNumX ) == 0 ? 0 : 1 );
int blockNumY = ( H - h ) / threadNumY + ( ( ( H - h ) % threadNumY ) == 0 ? 0 : 1 );
dim3 nThreads( threadNumX, threadNumY, 1 );
dim3 nBlocks( blockNumX, blockNumY, 1 );
// GPU側で類似度を計算する
kernel<<< nBlocks, nThreads >>>( thrust::raw_pointer_cast( &derror[ 0 ] ),
thrust::raw_pointer_cast( &dscale[ 0 ] ),
W, H, W - w, H - h, w, h, s );
cudaDeviceSynchronize( ); // 処理が完了するまで待機する
s *= SCALE_FACTOR; // スケールを変更する
}
// GPUの処理結果を転送する
thrust::host_vector< float > herror = derror;
thrust::host_vector< float > hscale = dscale;
GPUを用いて類似度を計算
スケールを変更して再探索
計算時間の比較(1)
CPUとGPUで計算時間を評価
使用計算機
CPU: Intel Core i7-X980(3.33GHz)
OpenMPを使用して6スレッドで並列計算
GPU: NVIDIA GeForce GTX480
マルチプロセッサ数: 15(SP数= 15×32 = 480)
Memory:1.5 GB
OS:Windows 7 SP1
81
計算時間の比較(2)
実験パラメータ
入力画像 :800×600画素
テンプレート:105×135画素
スケール :0.3~1.9倍(拡大率1.2)
実験結果
CPU: 77.3 sec.
GPU: 2.2 sec.
約 35 倍の高速化
82
テンプレート
結果画像
まとめ(テンプレートマッチング)
テクスチャメモリを利用した高速化
キャッシュを利用した効率的なメモリアクセス
解像度に依存しないメモリアクセス
ハードウェア線形補間
正規化テクスチャ座標
CPUとGPUで計算時間を比較
CPU: 77.3 sec.
GPU: 2.2 sec.
83
約 35 倍の高速化を実現
~テンプレートマッチングを高速化~
84
類似度計算の打ち切り処理を導入
各スケールで計算した類似度の最大値をGPUで計算
Parallel Reduction アルゴリズムを利用
スケールの変更
画像読み込み
CPU
メモリ確保
GPU
類似度計算
GPU
スレッド同期
CPU
データ転送
CPU GPU
データ転送
GPU CPU
最大類似度計算
GPU
処理の流れ
85
GPU上で類似度計算
86
__global__ void kernel( float *error, float *scale, int imgW, int imgH,
int areaW, int areaH, int maskW, int maskH, float s, float maxerr )
{
int i = threadIdx.x + blockDim.x * blockIdx.x;
int j = threadIdx.y + blockDim.y * blockIdx.y;
float err = 0.0f;
float _1_w = 1.0f / maskW; // テンプレートの幅に対するスケーリング係数
float _1_h = 1.0f / maskH; // テンプレートの高さに対するスケーリング係数
for( int n = 0 ; n < maskH ; n++ )
{
for( int m = 0 ; m < maskW ; m++ )
{
uchar4 p1 = tex2D( imgTex, i + m, j + n );
float4 p2 = tex2D( refTex, _1_w * m, _1_h * n ) * 255.0f;
err += ( p1.x - p2.x ) * ( p1.x - p2.x );
err += ( p1.y - p2.y ) * ( p1.y - p2.y );
err += ( p1.z - p2.z ) * ( p1.z - p2.z );
}
if( maxerr < err *_1_w * _1_h )
{
break;
}
}
... 以下同じ ...
}
前スケールの探索時におけるSSDが最小値より大きい場合は計算を打ち切り
Parallel Reductionによる最小値探索
GPU上のメモリから最小値を計算
GPUのスレッドが並列に値の比較処理を実行
87
2
GPU上のデータ
同期
11 4 27 25 5 13 6 9 20 12 14 7 2 19 3 15
4 25 5 6 12 7 2 3
4 5 7 2
4 2
2
同期
同期
Thrustライブラリの reduce アルゴリズムを利用
thrust::reduce( 先頭, 末尾, 初期値, thrust::minimum<T>( ) );
Parallel Reductionによる最小値探索
GPU上のメモリから最小値を計算
GPUのスレッドが並列に値の比較処理を実行
88
2
GPU上のデータ
同期
11 4 27 25 5 13 6 9 20 12 14 7 2 19 3 15
4 25 5 6 12 7 2 3
4 5 7 2
4 2
2
同期
同期
先頭を指すイテレータ
末尾を指すイテレータ
CPUによる類似度計算の制御
89
// スケール&誤差を保持するGPU側のメモリ領域
thrust::device_vector< float > derror( img.size( ), 1.0e10f );
thrust::device_vector< float > dscale( img.size( ), 0 );
float s = MIN_SCALE, maxerr = 1.0e10f;
while( s <= MSCALE )
{
// 実行するGPUのスレッド数を指定
int W = ( int )img.width( ), H = ( int )img.height( );
int w = ( int )( ref.width( ) * s ), h = ( int )( ref.height( ) * s );
int threadNumX = 16, threadNumY = 16;
int blockNumX = ( W - w ) / threadNumX + ( ( ( W - w ) % threadNumX ) == 0 ? 0 : 1 );
int blockNumY = ( H - h ) / threadNumY + ( ( ( H - h ) % threadNumY ) == 0 ? 0 : 1 );
dim3 nThreads( threadNumX, threadNumY, 1 );
dim3 nBlocks( blockNumX, blockNumY, 1 );
// GPU側で類似度を計算する
kernel<<< nBlocks, nThreads >>>( thrust::raw_pointer_cast( &derror[ 0 ] ),
thrust::raw_pointer_cast( &dscale[ 0 ] ),
W, H, W - w, H - h, w, h, s, maxerr );
cudaDeviceSynchronize( ); // 処理が完了するまで待機する
// 誤差の最大値を取得する
maxerr = thrust::reduce( derror.begin( ), derror.end( ), 1.0e10f, thrust::minimum< float >( ) );
s *= SCALE_FACTOR; // スケールを変更する
}
// GPUの処理結果を転送する
...
Trustライブラリを用いてGPUを活用して誤差の最小値を計算
計算時間の比較(1)
CPUとGPUで計算時間を評価
使用計算機
CPU: Intel Core i7-X980(3.33GHz)
OpenMPを使用して6スレッドで並列計算
GPU: NVIDIA GeForce GTX480
マルチプロセッサ数: 15(SP数= 15×32 = 480)
Memory:1.5 GB
OS:Windows 7 SP1
90
計算時間の比較(2)
実験パラメータ
入力画像 :800×600画素
テンプレート:105×135画素
スケール :0.3~1.9倍(拡大率1.2)
実験結果
CPU: 8.700 sec.
GPU: 0.258 sec.
約 33.7 倍の高速化
91
テンプレート
結果画像
まとめ(テンプレートマッチング)
類似度計算の打ち切り処理により高速化
Parallel Reductionアルゴリズムを利用
CPUとGPUで計算時間を比較
92
類似度の打ち切り
なし あり
CPU 77.3 秒 8.7 秒
GPU 2.2 秒 0.258 秒
~ガウシアンフィルタ~
93
ガウシアンフィルタに挑戦
94
線形フィルタとは?
画像処理の基本的な処理
ガウシアンフィルタ
LoGフィルタ
ガボールフィルタ
他
空間フィルタリング(線形フィルタ)
95
h
hy
w
wx
yjxifyxhjig ,,,
入力画像
フィルタ 出力画像 注目画素 f(i,j)
フィルタh(i,j)
積和
周辺領域
入力画像 出力g(i,j)
ガウシアンフィルタ
フィルタ係数に2次元ガウス分布を利用
問題点
フィルタ半径が大きくなるにつれ計算コスト大
フィルタを1次元ガウス分布の積に分解
96
2
22
2exp
2
1,
yxyxh
2
2
2
2
2
22
2exp
2exp
2
1
2exp
2
1,
yxyxyxh
h
hy
w
wx
yjxifxy
jig ,2
exp2
exp2
1,
2
2
2
2
X軸方向への1次元ガウシアンフィルタ Y軸方向への1次元ガウシアンフィルタ
ガウシアンフィルタの特徴
フィルタ出力は各画素で独立に計算可能
GPUによる並列計算が可能
フィルタ係数の算出
フィルタ適用前に事前計算が可能
フィルタ適用中は常に同じ値を参照
コンスタントメモリを利用
注目画素の周辺領域(1次元)にアクセス
1次元テクスチャのキャッシュ機能を利用
97
コンスタントメモリとは?
GPU上に実装されている特殊なメモリ領域
読み取り専用
マルチプロセッサ1基あたり 64 KB
キャッシュを利用した高速なアクセスが可能
レジスタとほぼ同じ速度でアクセス可能
コンスタントメモリの定義
__constant__ float coeff[ 512 ];
C言語の配列のようにアクセス可能
98
処理の流れ
GPU上に2つの1次元配列を確保
X,Y軸方向の処理時に入力と出力を入れ替え
GPU内でのメモリ転送コストを削減
99
画像読み込み
CPU
メモリ確保
GPU
(X軸)
ガウシアンフィルタ
GPU
スレッド同期
CPU
データ転送
CPU GPU
データ転送
GPU CPU
(Y軸)
ガウシアンフィルタ
GPU
メモリ1 メモリ2 メモリ2 入力画像
メモリ確保と転送(1)
入力画像のメモリをGPU上に確保
テクスチャの定義
GPU上に1次元配列を確保
CPUからGPUへメモリ転送
1次元テクスチャにマッピング
100
cudaMalloc( ( void ** )&iData, nBytes );
cudaMalloc( ( void ** )&oData, nBytes );
cudaBindTexture( 0, imgTexX, oData );
cudaBindTexture( 0, imgTexY, iData );
cudaMemcpy( oData, pSrc, nBytes, cudaMemcpyHostToDevice );
texture< float4, 1 > imgTexX;
texture< float4, 1 > imgTexY;
メモリ確保と転送(2)
フィルタ係数をコンスタントメモリに確保
コンスタントメモリの定義
CPUからGPUへメモリ転送
101
コンスタントメモリを表す修飾子
__constant__ float coeff[ 512 ];
cudaMemcpyToSymbol( coeff, pSrc, 512 * sizeof( float ) );
サイズ指定が必要
計算時間の比較(1)
CPUとGPUで計算時間を評価
使用計算機 CPU: Intel Core i7-X980(3.33GHz)
OpenMPを使用して6スレッドで並列計算
GPU: NVIDIA Geforce GTX480
マルチプロセッサ数: 15(SP数 = 15×32 = 480)
Memory:1.5 GB
OS:Windows 7 SP1
実験パラメータ 画像サイズ:1002 ~ 30002 画素
σ:5.0,10.0
102
0
50
100
150
200
250
300
350
400
200 400 600 800 1000 1200 1400 1600 1800 2000 2200 2400 2600 2800 3000
計算時間の比較(2)
103
画像サイズ N
2
[msec.]
CPU(σ=5)
GPU(σ=10)
GPU(σ=5)
計算時間(N 2= 20002 の場合)
・CPU(σ=5) : 105.2 msec.
・GPU(σ=5) : 20.9 msec. (×5.0)
・CPU(σ=10) : 167.7 msec.
・GPU(σ=10) : 30.7 msec. (×5.5)
CPU(σ=10)
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
まとめ(ガウシアンフィルタ)
2次元ガウス分布を1次元ガウス分布の積で表現
ガウシアンフィルタを1次元フィルタに分解
コンスタントメモリを利用した高速化
キャッシュを利用したメモリアクセスの高速化
同じ値をスレッド間で共有する場合に有効
CPUとGPUで計算時間を比較
画像サイズが 20002 の場合(σ=5)
CPU: 105.2 msec.
GPU: 20.9 msec.
104
約 5 倍の高速化を実現
~SIFT特徴量の計算~
105
SIFT(Scale Invariant Feature Transform)
回転・スケール変化等に頑健な特徴点の検出
画像間のマッチングや物体認識・検出に利用
106
1. David G. Lowe, “Distinctive image features from scale-invariant keypoints,”
International Journal of Computer Vision, 60, 2, pp. 91-110, 2004.
2. 藤吉弘亘. "Gradientベースの特徴抽出 - SIFTとHOG - ", 情報処理学会 研究報告
CVIM 160, pp. 211-224, 2007.
1,2
SIFTのアルゴリズム(1)
107
DoG画像の作成 (Difference-of-Gaussian)
特徴点の検出
エッジ上の点を削除
サブピクセル位置推定
異なるスケールの平滑化画像の差分(DoG)を計算
周辺26画素に対して極値をとる位置を
特徴点として検出
コントラストの
小さい点を削除 DoG1
DoG2
DoG3
DoG1
DoG2
DoG3
3k
k
2k
SIFTのアルゴリズム(2)
108
DoG画像の作成 (Difference-of-Gaussian)
特徴点の検出
エッジ上の点を削除
サブピクセル位置推定
コントラストの
小さい点を削除
SIFTのアルゴリズム(3)
109
オリエンテーションの算出
特徴ベクトルを算出
周辺領域の勾配方向と強度から
オリエンテーションを算出
デモ( SIFT )
110
SIFTの実装方法
さまざまなスケールにおけるDoG計算 高速なガウシアンフィルタを利用
キーポイント検出 各画素で独立に判定可能
GPUによる並列計算が可能
判定に26近傍の画素値が必要
3次元テクスチャを利用できるか? テクスチャサイズの制限から利用は困難
2次元テクスチャで代用
オリエンテーションの算出 各キーポイントで独立に計算可能
111
処理の流れ
112
画像読み込み
CPU
DoG計算
GPU
スレッド同期
CPU
データ転送
CPU GPU
データ転送
GPU CPU
キーポイント検出
GPU
キーポイント DoG画像 オリエンテーション 入力画像
キーポイントリスト作成
CPU
オリエンテーション算出
GPU
データ転送
GPU CPU
SIFT計算におけるメモリ配置
複数スケールのDoG画像をテクスチャ1枚に配置
ミップマップ※を構築
DoG画像の出力先をソフトウェア的に調整
113
11
21
41
81
・・・
・・・
※CUDAは未サポート
計算時間の比較(1)
CPUとGPUで計算時間を評価 SIFT特徴量としてオリエンテーションを計算
使用計算機 CPU: Intel Core2 Quad Q9550(2.83 GHz)
4スレッドで並列計算
GPU: NVIDIA GeForce GTX280
マルチプロセッサ数: 30(SP数= 30×8 = 240)
Memory:1.0 GB
OS:Windows Vista SP1
実験パラメータ 画像サイズ:502 ~ 10002 画素
σ0:1.6,分割数:3
114
0
100
200
300
400
500
600
700
800
900
1000
1100
1200
1300
1400
1500
1600
1700
1800
1900
2000
2100
50 100 150 200 250 300 350 400 450 500 550 600 650 700 750 800 850 900 950 1000
計算時間の比較(2)
115
画像サイズ N
2
[msec.]
計算時間(N 2 = 6002 の場合)
・CPU : 458.0 msec.
・GPU : 10.2 msec. (×44.9)
2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
CPU
GPU
デモ(SIFT)
116
まとめ(SIFT)
テクスチャメモリとコンスタントメモリの利用
キャッシュを利用したメモリアクセスの高速化
ミップマップを構築
複数スケールのDoG画像を1枚のテクスチャに格納
CPUとGPUで計算時間を比較
N 2 = 6002 の場合
CPU: 458.0 msec.
GPU: 10.2 msec.
117
約 44 倍の高速化を実現
~GPGPUによる高速画像処理に挑戦して~
118
GPGPUへの挑戦を終えて
CUDAを利用することで容易にGPGPUが可能
スレッドプログラミングの経験があれば非常に簡単
既存プログラムの移植も比較的容易
Thrustライブラリによる簡単なGPGPU
GPUを意識せずにプログラミングが可能
GPUを使うと10倍以上の高速化が可能?
多くの画像処理アルゴリズムは高速化が可能
空間フィルタリング,局所特徴量計算,他
逐次型の画像処理アルゴリズムは高速化が困難
ラベリング,細線化,他
119
GPGPUの問題点と今後の展望
複数GPUの利用
各GPU上で別々に処理を実行
CUDA 4.0 は単一CPUスレッドから複数GPUを利用可能
CPUとの連携
GPUの苦手な処理をCPUで計算
CPUとGPUの役割分担が重要
高性能なGPUが登場
リアルタイム画像処理(大規模計算)への挑戦
GPGPU開発環境の標準化(OpenCL)
120
参考文献
121
[1] M. J. Harris, G. Coombe, T. Scheuermann, and A. Lastra, “Physically-
Based Visual Simulation on Graphics Hardware,” Proceedings of
SIGGRAPH 2002 / Eurographics Workshop on Graphics Hardware 2002,
pp.1-10, 2002.(GPGPUの起源が書かれている論文)
[2] J. D. Owens, D. Luebke, N. Govindaraju, M. Harris, J. Krüger, A. E.
Lefohn, and T. J. Purcell, “A Survey of General-Purpose Computation on
Graphics Hardware,” Computer Graphics Forum, Vol.26, No.1, pp.80-
113, 2007.(最近のGPGPUが詳しく述べられている論文)
[3] “GPGPU,” http://gpgpu.org/
[4] “CUDA ZONE,” http://www.nvidia.com/object/cuda_home.html
[5] “CUDA Programming Guide,”
http://www.nvidia.com/object/cuda_develop.html
[6] “OpenCL,” http://www.khronos.org/opencl/
MIST(Media Integration Standard Toolkit)
複数メディアを扱うためのライブラリ
音声・画像処理のアルゴリズムを多数実装
C/C++を用いた高速な処理を実現
C++のテンプレートを用いた汎用的な実装
複数のプラットフォームで動作
充実した日本語チュートリアルを用意
オープンソースとして公開中
BSDスタイルのライセンス
商用の製品開発でも利用可能
122
http://mist.murase.m.is.nagoya-u.ac.jp/
~Visual Studio 2010 の詳細設定~
※CUDA Toolkit 4.0 以降
123
Visual Studio 2010 の簡易設定(1)
“.cu” ファイルの簡易コンパイル設定
プロジェクトメニューのビルドのカスタマイズを表示
CUDA 4.0を選択
124
2.これを選択
1.これを表示
※ .cu ファイルの追加前に行う
Visual Studio 2010 の簡易設定(2)
“.cu” ファイルのコンパイルオプション
“.cu” ファイルのプロパティを表示
NVCCのコンパイルオプションをGUIで調整可能
125
これを表示
Visual Studio 2010 の簡易設定(3)
「プロジェクト」 「プロパティ」を選択
「構成プロパティ」 「リンカー」を選択
追加の依存ファイル
“cudart.lib” を指定
126
ここに入力
Visual Studio 2010 の簡易設定(3)
プログラムのコンパイルと実行
Visual Studio の「ビルド」を実行
実行ファイルが作成されることを確認
「デバッグなしで開始」
コマンドラインに “Hello World!!” が表示される
127
Hello World!!
続行するには何かキーを押してください . . .
※本講演のプログラムを実行した場合
~補足資料~
128
FLOPS
FLoating point number Operations Per Second
FLOPSやFLOP/s と表記される
1秒あたりに実行可能な浮動小数点演算回数
スーパーコンピュータ等の性能を表す指標
代表的なCPU/GPUのFLOPS
Core i7-965
51.20 GFLOPS
GeforceGTX580
1.58 TFLOPS
RadeonHD5870
2.72 TFLOPS
129
※ GPUは積和演算の性能
CUDAにおけるエラー処理
API関数(メモリ確保,他)の場合
各API関数の戻り値を評価
cudaSuccess → 実行に成功
GPUで実行する関数の場合
cudaThreadSynchronize( )により同期
cudaGetLastError( ) の戻り値を評価
cudaSuccess → 実行に成功
130
~OpenCV + CUDA~
131
デモ(特徴点検出&対応付け)
132
OpenCVをダウンロード
Subversion経由で最新版を入手
https://code.ros.org/svn/opencv/trunk/opencv
Cmakeをダウンロード
http://www.cmake.org/
133
OpenCVライブラリのビルド
CUDAを有効にする
Cmakeの設定で「WITH_CUDA」にチェック
Configureを実行
ビルド設定を反映
Generateを実行
ビルドファイルを生成
134
Cmakeの設定画面
1.ここにチェック
2.設定を反映
3.ビルドファイルの生成
CPU/GPUのコードの違い
SURFを用いた特徴点検出&マッチング(CPU)
135
#include <opencv2/opencv.hpp>
...
cv::Mat_< float > desc2;
cv::SurfFeatureDetector detector2( th2 );
// 特徴点を検出
detector2.detect( frame_gray, keys2 );
// 特徴量を計算
cv::SurfDescriptorExtractor extractor;
extractor.compute( frame_gray, keys2, desc2 );
// 特徴点を対応付け
cv::BruteForceMatcher< cv::L2< float > > matcher;
matcher.match( desc1, desc2, matches );
CPU/GPUのコードの違い
SURFを用いた特徴点検出&マッチング(GPU)
136
#include <opencv2/gpu/gpu.hpp>
...
cv::gpu::GpuMat desc_gpu2;
cv::gpu::SURF_GPU detector2;
detector2.hessianThreshold = th2;
// データをGPUへ転送
cv::gpu::GpuMat frame_gpu;
frame_gpu.upload( frame_gray );
// 特徴点検出&特徴量計算
detector2( frame_gpu, cv::gpu::GpuMat(), keys2, desc_gpu2 );
// 特徴点を対応付け
cv::gpu::BruteForceMatcher_GPU< cv::L2< float > > matcher;
matcher.match( desc_gpu1, desc_gpu2, matches );
OpenCVでGPUを利用できる機能
行列演算
テンプレートマッチング
歩行者検出(HOG)
特徴点検出&特徴量(SURF)
特徴点対応付け
画像フィルタ
ラプラシアン,ソーベル,ガウシアン,他
カメラキャリブレーション
・・・
137