GPGPUによる高速画像処理

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
  hx, 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
 
hx, 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)

特徴点対応付け

画像フィルタ

ラプラシアン,ソーベル,ガウシアン,他

カメラキャリブレーション

・・・