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 2024 Paperzz