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