授業項目 並列計算の概念(プロセスとスレッド) 9. GPUのアーキテクチャ 10. GPGPUのプログラム構造 11. GPUでの並列プログラミング(ベクトル和) 12. GPUによる画像処理 13. GPUのメモリ階層 14. GPGPU組込開発環境 15. GPGPU開発環境(OpenCL) 8. 506 GPGPU実践基礎工学 2015/11/25 第12回 GPUによる画像処理 長岡技術科学大学 電気電子情報工学専攻 出川智啓 今回の内容 画像処理 ネガティブ処理,画像反転,空間フィルタ,モザイク処理 画像処理を簡略化した1次元的な処理 2次元的な並列処理 2次元配列の表現 ベクトル和 モノクロ画像処理 508 GPGPU実践基礎工学 2015/11/25 画像処理 デジタル画像を別の画像に変換 処理の種類 画像の拡大・縮小 画像の反転 フィルタ 509 2値化,ぼかしなど GPGPU実践基礎工学 2015/11/25 画像処理を行うプログラムの流れ 読み込む画像を準備 対象ファイルをオープン 画像の読込 画像ファイルの種類(bmp,jpg,png等)に応じて読み込む方 法を選択 画像ファイルからヘッダ情報を読み込み,様々な情報を取得 メモリを確保し,画素情報を読込 定められたアルゴリズムで画像を処理 処理された画像の出力 510 GPGPU実践基礎工学 2015/11/25 画像処理 画像を読むプログラムを作るだけで一苦労 画像処理が主目的ではなく,GPUの使い方を学習するこ とが目的 画像をモノクロに限定 プログラム中で画像に相当するデータを生成 ターミナルにテキスト出力(横の位置,縦の位置,色情報) 画像の表示にはgnuplotを利用 モノクロ画像に対して簡単な処理を実行 511 GPGPU実践基礎工学 2015/11/25 取り扱うモノクロ画像 型はunsigned char 1バイトの整数型 取り扱える範囲は0~255 0を黒,255を白とみなす 1次元配列で取り扱う 512 画像は2次元なので,2次元配列で取り扱う方が自然 mallocやcudaMallocでは2次元配列を確保できない GPGPU実践基礎工学 2015/11/25 取り扱うモノクロ画像の内容 x unsigned char picture[] = { 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, y 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255, 0,255,255,255,255,255,255, 255,255,255,255,255,255,255,255,255, 0, 0,255,255,255,255,255, 255,255,255,255,255,255,255,255,255, 0, 0, 0,255,255,255,255, 255,255,255,255,255,255,255,255,255, 0, 0, 0, 0,255,255,255, 255,255,255,255,255,255,255,255,255, 0, 0, 0, 0, 0,255,255, 255,255,255,255,255,255,255,255,255, 0, 0, 0, 0, 0, 0,255, 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255}; 513 GPGPU実践基礎工学 2015/11/25 取り扱うモノクロ画像の内容 gnuplotで表示 左下を原点として表示 画像処理では左上が原点 y x 514 GPGPU実践基礎工学 2015/11/25 画像処理 ネガティブ処理 反転 空間フィルタ 水平反転,垂直反転 ぼかし,輪郭抽出 モザイク処理 515 GPGPU実践基礎工学 2015/11/25 画像処理の簡略化(1次元化) いきなり画像処理は難しい 1次元の配列に対して,画像処理と同じような処理を実行 どのような処理を行うかを確認 516 ネガティブ処理 水平反転 空間フィルタ(ぼかし,輪郭抽出) モザイク処理 GPGPU実践基礎工学 原画像もどき 幅 WIDTH=64 (0~63) [3*WIDTH/16,9*WIDTH/16)の範囲を白(255),それ 以外を黒(0) 変数名 原画像pのピクセル値 画素値 原画像 p[] 処理画像 filtered[] 横位置 517 GPGPU実践基礎工学 2015/11/25 ネガティブ処理 色の反転処理 白(255)を黒(0)に 原画像p[] 黒(0)を白(255)に 処理画像 filtered[] 画素値 画素値 黒(0)を白(255),白(255)を黒(0)に反転 (白−各画素の値)を新しい画素の値とする 横位置 518 GPGPU実践基礎工学 2015/11/25 ネガティブ処理 色の反転処理 黒(0)を白(255),白(255)を黒(0)に反転 (白−各画素の値)を新しい画素の値とする void negative(unsigned char *p, unsigned char *filtered){ int i; for (i = 0; i<WIDTH; i++){ filtered[i] = WHITE ‐ p[i]; } } 519 GPGPU実践基礎工学 2015/11/25 水平反転 画像の幾何的位置を反転 画素値 画素値 水平反転 i→(WIDTH‐1)‐i p[i]→filtered[(WIDTH‐1)‐i] 横位置1の画素値を62番目の画素値に 横位置35の画素値を28番目の画素値に 原画像p[] 処理画像 filtered[] 横位置 520 GPGPU実践基礎工学 2015/11/25 水平反転 画像の幾何的位置を反転 水平反転 i→(WIDTH‐1)‐i p[i]→filtered[(WIDTH‐1)‐i] void hreflect(unsigned char *p, unsigned char *filtered){ int i; for (i = 0; i<WIDTH; i++){ //(WIDTH‐1)‐iは反転後の横位置 filtered[(WIDTH ‐ 1) ‐ i] = p[i]; } } 521 GPGPU実践基礎工学 2015/11/25 空間フィルタ ある画素とその周囲の画素を使って処理 処理の仕方を規定したフィルタカーネルを定義 フィルタカーネルは1次元配列で表現,引数として関数に渡す ぼかし(平均)フィルタ 1/3 1/3 1/3 float blur[3] ={1.0f/3.0f,1.0f/3.0f,1.0f/3.0f}; 輪郭抽出(ラプラシアン)フィルタ 1 522 ‐2 1 float laplacian[9] ={ 1.0f,‐2.0f, 1.0f}; GPGPU実践基礎工学 2015/11/25 空間フィルタ ある画素とその周囲の画素を使って処理 画素値 ある位置iの左右(i‐1,i+1)の計3画素の値を読み,それぞ れフィルタカーネルの各値との積を計算し,合計を取った値を 処理画像のi番目の画素とする 原画像p[] ぼかしフィルタカーネル 1/3 1/3 1/3 255/3+255/3+255/3=255 画素値 処理画像 filtered[] 横位置 523 GPGPU実践基礎工学 2015/11/25 空間フィルタ ある画素とその周囲の画素を使って処理 画素値 ある位置iの左右(i‐1,i+1)の計3画素の値を読み,それぞ れフィルタカーネルの各値との積を計算し,合計を取った値を 処理画像のi番目の画素とする 原画像p[] ぼかしフィルタカーネル 1/3 1/3 1/3 255/3+255/3+255/3=255 画素値 処理画像 filtered[] 横位置 524 GPGPU実践基礎工学 2015/11/25 空間フィルタ ある画素とその周囲の画素を使って処理 画素値 ある位置iの左右(i‐1,i+1)の計3画素の値を読み,それぞ れフィルタカーネルの各値との積を計算し,合計を取った値を 処理画像のi番目の画素とする 原画像p[] ぼかしフィルタカーネル 1/3 1/3 1/3 255/3+255/3+255/3=255 画素値 処理画像 filtered[] 横位置 525 GPGPU実践基礎工学 2015/11/25 空間フィルタ ある画素とその周囲の画素を使って処理 ある位置iの左右(i‐1,i+1)の計3画素の値を読み,それぞ れフィルタカーネルの各値との積を計算し,合計を取った値を 処理画像のi番目の画素とする 画素値 原画像p[] ぼかしフィルタカーネル 1/3 1/3 1/3 0/3+255/3+255/3=170 画素値 処理画像 filtered[] 横位置 526 GPGPU実践基礎工学 2015/11/25 空間フィルタ ある画素とその周囲の画素を使って処理 forループでiの値を変化させ,p[i‐1],p[i],p[i+1]の値 から処理後の画素値filtered[i]を計算 void boxfilter (unsigned char *p, unsigned char *filtered, float *filter){ int i, result=BLACK; for (i = 1; i<WIDTH ‐ 1; i++){//端の画素は処理をしない result = filter[0]*p[i‐1]+filter[1]*p[i]+filter[2]*p[i+1]; //フィルタ後の値が負になれば0に切り上げ,255を超えたら255に収める if (result<BLACK) result = 0; if (result>WHITE) result = WHITE; filtered[i] = (unsigned char)result; } } 527 GPGPU実践基礎工学 2015/11/25 空間フィルタ 輪郭抽出フィルタの結果 画素値 画素値 白と黒の境界でのみ値が255になり,それ以外では0 両端はフィルタ処理できないため,初期値がそのまま使われる 原画像p[] 白と黒の境界でのみ 画素値が0より大きく なる 処理画像 filtered[] 横位置 528 GPGPU実践基礎工学 2015/11/25 モザイク処理 画像を小領域に分け,その領域を全て同じ色に置換 画素値 原画像p[] 画素値 小領域内の全画素を,小領域内の画素の平均値に置換 小領域を移動するループ内に,小領域内の画素の平均値を 計算するループを設ける 処理画像 filtered[] 小領域 横位置 529 GPGPU実践基礎工学 2015/11/25 モザイク処理 画像を小領域に分け,その領域を全て同じ色に置換 画素値 原画像p[] 画素値 小領域内の全画素を,小領域内の画素の平均値に置換 小領域を移動するループ内に,小領域内の画素の平均値を 計算するループを設ける 処理画像 filtered[] 横位置 530 GPGPU実践基礎工学 2015/11/25 モザイク処理 画像を小領域に分け,その領域を全て同じ色に置換 画素値 原画像p[] 画素値 小領域内の全画素を,小領域内の画素の平均値に置換 小領域を移動するループ内に,小領域内の画素の平均値を 計算するループを設ける 処理画像 filtered[] 小領域 横位置 531 GPGPU実践基礎工学 2015/11/25 モザイク処理 画像を小領域に分け,その領域を全て同じ色に置換 画素値 原画像p[] 画素値 小領域内の全画素を,小領域内の画素の平均値に置換 小領域を移動するループ内に,小領域内の画素の平均値を 計算するループを設ける 処理画像 filtered[] 横位置 532 GPGPU実践基礎工学 2015/11/25 モザイク処理 画像を小さな領域に分け,その領域を全て同じ色に置換 小領域内で画素の平均値を計算し,処理画像の小領域内の 全画素をその平均値とする void mosaic(unsigned char *p,unsigned char *filtered,int mosaicSize){ int i,isub,average; for (i = 0; i<WIDTH; i += mosaicSize){//小領域を移動するループ //小領域内の画素の平均値を計算 average = 0; for(isub = 0; isub<mosaicSize; isub++) average += p[(i+isub)]; average /= mosaicSize; //小領域内の全画素を平均値で塗りつぶす for(isub = 0; isub<mosaicSize; isub++) filtered[(i+isub)] = (unsigned char)average; } } 533 GPGPU実践基礎工学 2015/11/25 処理の流れ 変数を宣言し,mallocでメモリを確保 原画像pを作成 pをfilteredにコピーしておく 処理を実行 原画像を格納する変数 p 処理後の画像を格納する変数 filtered 関数を作成し,pとfilteredを渡す filteredの内容を画面に出力 534 GPGPU実践基礎工学 2015/11/25 メイン関数(CPU版) #include<stdio.h> #include<stdlib.h> #define WHITE (255) #define BLACK (0) #define WIDTH (64) #define Nbytes (WIDTH*sizeof(unsigned char)) void create(unsigned char *); void copy(unsigned char *,unsigned char *); void print(unsigned char *); void negative(unsigned char *, unsigned char *); void hreflect(unsigned char *, unsigned char *); void boxfilter(unsigned char *, unsigned char *, float *); void mosaic(unsigned char *, unsigned char *, int); int main(void){ unsigned char *p= (unsigned char *)malloc(Nbytes); unsigned char *filtered= (unsigned char *)malloc(Nbytes); //ここで空間フィルタのカーネルを宣言 535 imageproc1d.c GPGPU実践基礎工学 2015/11/25 メイン関数(CPU版) create(p); copy(p,filtered); //ここで処理を行い,結果をfilteredに格納 //画面に各画素の値を表示 print(filtered); return 0; } //画像の内容をコピー void copy(unsigned char *src, unsigned char *dst){ int i; for (i = 0; i<WIDTH; i++) dst[i] = src[i]; } //画像の内容を画面に出力 void print(unsigned char *p){ int i; for (i = 0; i<WIDTH; i++) printf("%d %d¥n", i, p[i]); } 536 GPGPU実践基礎工学 imageproc1d.c 2015/11/25 画像の作成 [3*WIDTH/16,9*WIDTH/16)の範囲を白(255),それ 以外を黒(0) void create(unsigned char *p){ int i, x_origin; for (i = 0; i<WIDTH; i++) p[i] = WHITE; x_origin = 3 * WIDTH / 16; for (i = 0; i<6 * WIDTH / 16; i++) p[i + x_origin] = BLACK; 画素値 } 3WIDTH/16 537 6WIDTH/16 GPGPU実践基礎工学 横位置 2015/11/25 結果の画面表示とファイル保存 print関数が画面に各画素の座標と値を表示 0 255 1 255 : 62 255 63 255 画面出力のファイル表示(出力のリダイレクトを利用) >を利用して保存,結果は表計算等で確認 $ コマンド > ファイル名 $ ./a.out > pic.txt $ cat pic.txt 0 255 1 255 : 62 255 63 255 538 GPGPU実践基礎工学 2015/11/25 GPUへの移植の方針 現画像の作成はCPUで行い,GPUへ転送 1スレッドが1画素の処理を実行 空間フィルタ forループを排除し,配列添字とスレッド番号を対応 端の画素の処理ができないので今回は処理しない モザイク 領域の大きさは1ブロックと同じとする 領域内の画素の平均値の計算は非常に難易度が高い 539 各ブロックにおけるthreadIdx.x=0のスレッドが平均を計算して処 理画像に書き込む GPGPU実践基礎工学 2015/11/25 メイン関数(GPU版) #include<stdio.h> #include<stdlib.h> #define WHITE (255) #define BLACK (0) #define WIDTH (64) #define Nbytes (WIDTH*sizeof(unsigned char)) #define THREAD 16 #define BLOCK (WIDTH/THREAD) void create(unsigned char *); void print(unsigned char *); int main(void){ unsigned char *p= (unsigned char *)malloc(Nbytes); unsigned char *dev_p, *dev_filtered; cudaMalloc( (void **)&dev_p, Nbytes); cudaMalloc( (void **)&dev_filtered, Nbytes); imageproc1d.cu 540 GPGPU実践基礎工学 2015/11/25 メイン関数(GPU版) //ここで空間フィルタのカーネルを宣言 float *filter; cudaMalloc( (void **)&filter, sizeof(float)*3); cudaMemcpy(filter, フィルタのカーネル, sizeof(float)*3, cudaMemcpyHostToDevice); create(p); //CPUで画像を生成してGPUへ送った後,変数dev_filteredにコピーしておく cudaMemcpy(dev_p, p, Nbytes, cudaMemcpyHostToDevice); cudaMemcpy(dev_filtered, dev_p, Nbytes, cudaMemcpyDeviceToDevice); //ここで処理を行い,結果をdev_filteredに格納 //dev_filteredの内容をCPUへ送る unsigned char *filtered = (unsigned char *)malloc(Nbytes); cudaMemcpy(filtered, dev_filtered, Nbytes, cudaMemcpyDeviceToHost); free(p); free(filtered); cudaFree(dev_p); cudaFree(dev_filtered); cudaFree(filter); imageproc1d.cu 541 GPGPU実践基礎工学 2015/11/25 メイン関数(GPU版) return 0; } //原画像の生成 void create(unsigned char *p){ int i, x_origin; for (i = 0; i<WIDTH; i++) p[i] = WHITE; x_origin = 3 * WIDTH / 16; for (i = 0; i<6 * WIDTH / 16; i++) p[i + x_origin] = BLACK; } //画像の内容を画面に出力 void print(unsigned char *p){ int i; for (i = 0; i<WIDTH; i++) printf("%d %d¥n", i, p[i]); } imageproc1d.cu 542 GPGPU実践基礎工学 2015/11/25 ネガティブ処理 1スレッドが1画素の値を計算 ベクトル和のようにスレッド番号と配列添字の対応を計算 画素値 原画像p[] 画素値 スレッド番号 0 1 2 3 ・・・ 処理画像 filtered[] 横位置 543 GPGPU実践基礎工学 2015/11/25 ネガティブ処理 void negative(unsigned char *p, unsigned char *filtered){ int i; for (i = 0; i<WIDTH; i++) filtered[i] = WHITE ‐ p[i]; } imageproc1d.c 544 GPGPU実践基礎工学 2015/11/25 ネガティブ処理 __global__ void negative(unsigned char *p, unsigned char *filtered){ int i; i = blockIdx.x*blockDim.x + threadIdx.x; //スレッド数と配列添字の対応 filtered[i] = WHITE ‐ p[i]; } imageproc1d.cu 545 GPGPU実践基礎工学 2015/11/25 水平反転 1スレッドが1画素の値を反転 ベクトル和のようにスレッド番号と配列添字の対応を計算 書込先が変わる 画素値 原画像p[] 画素値 スレッド番号 0 1 2 3 ・・・ 処理画像 filtered[] 横位置35の画素値を28番目の画素値に 横位置 546 GPGPU実践基礎工学 2015/11/25 水平反転 void hreflect(unsigned char *p, unsigned char *filtered){ int i; for (i = 0; i<WIDTH; i++) //(WIDTH‐1)‐iは反転後の横位置 filtered[(WIDTH ‐ 1) ‐ i] = p[i]; } imageproc1d.c 547 GPGPU実践基礎工学 2015/11/25 水平反転 __global__ void hreflect(unsigned char *p, unsigned char *filtered){ int i; i = blockIdx.x*blockDim.x + threadIdx.x; //(WIDTH‐1)‐iは反転後の横位置 filtered[(WIDTH‐1)‐i] = p[i]; } imageproc1d.cu 548 GPGPU実践基礎工学 2015/11/25 空間フィルタ 1スレッドが1画素の値を計算 ある位置iの画素だけではなく左右(i‐1,i+1)の画素も参照 全スレッドが同じフィルタカーネルを参照 原画像p[] 1/3 1/3 1/3 ぼかしフィルタカーネル 処理画像 filtered[] 画素値 画素値 スレッド番号 0 1 2 3 ・・・ 横位置 549 GPGPU実践基礎工学 2015/11/25 空間フィルタ void boxfilter (unsigned char *p, unsigned char *filtered, float *filter){ int i, result=BLACK; for (i = 1; i<WIDTH ‐ 1; i++){//端の画素は処理をしない result = filter[0]*p[i‐1]+filter[1]*p[i]+filter[2]*p[i+1]; //フィルタ後の値が負になれば0に切り上げ,255を超えたら255に収める if (result<BLACK) result = 0; if (result>WHITE) result = WHITE; filtered[i] = (unsigned char)result; } } imageproc1d.c 550 GPGPU実践基礎工学 2015/11/25 空間フィルタ __global__ void boxfilter (unsigned char *p, unsigned char *filtered, float *filter){ int i, result = BLACK; i = blockIdx.x*blockDim.x + threadIdx.x; if(0<i && i<WIDTH‐1) //端の画素は処理をしないため,ifで処理を分岐 result = filter[0]*p[i‐1]+filter[1]*p[i]+filter[2]*p[i+1]; //フィルタ後の値が負になれば0に切り上げ,255を超えたら255に収める if(result<BLACK) result = 0; if(result>WHITE) result = WHITE; filtered[i] = (unsigned char)result; } imageproc1d.cu 551 GPGPU実践基礎工学 2015/11/25 モザイク処理 1ブロック内の1スレッドのみが処理 1スレッドがブロック内の全画素の平均を計算 平均の画素値を処理画像に書込 画素値 原画像p[] 画素値 スレッド番号 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 ・・・ 処理画像 filtered[] 横位置 552 GPGPU実践基礎工学 2015/11/25 モザイク処理 void mosaic(unsigned char *p,unsigned char *filtered,int mosaicSize){ int i,isub,average; for (i = 0; i<WIDTH; i += mosaicSize){//小領域を移動するループ //小領域内の画素の平均値を計算 average = 0; for(isub = 0; isub<mosaicSize; isub++) average += p[(i+isub)]; average /= mosaicSize; //小領域内の全画素を平均値で塗りつぶす for(isub = 0; isub<mosaicSize; isub++) filtered[(i+isub)] = (unsigned char)average; } } imageproc1d.c 553 GPGPU実践基礎工学 2015/11/25 モザイク処理 __global__ void mosaic (unsigned char *p, unsigned char *filtered, int mosaicSize){ int i,isub,average; i = blockIdx.x*blockDim.x + threadIdx.x; if(threadIdx.x == 0){//ブロック内の1スレッドのみが処理 //小領域内の画素の平均値を計算 average = 0; for(isub = 0; isub<mosaicSize; isub++) average += p[(i+isub)]; average /= mosaicSize; //小領域内の全画素を平均値で塗りつぶす for(isub = 0; isub<mosaicSize; isub++) filtered[(i+isub)] = (unsigned char)average; } } imageproc1d.cu 554 GPGPU実践基礎工学 2015/11/25 ネガティブ処理 色の反転処理 黒を白,白を黒に反転 原画像 555 処理画像 GPGPU実践基礎工学 2015/11/25 画像反転 画像の幾何的位置を反転 画像(画素)の配列添字を変えて出力 原画像 556 処理画像 GPGPU実践基礎工学 2015/11/25 空間フィルタ ある画素とその周囲の画素を使って処理 処理の仕方を規定したカーネルを定義 カーネルを変更することで様々な処理が可能 原画像 557 輪郭抽出 GPGPU実践基礎工学 2015/11/25 フィルタ処理 2次元的な処理 あるピクセルの周囲情報を利用して効果を計算 画像を矩形領域で分割した2次元的な並列処理が自然 原画像 558 輪郭抽出 フィルタ (カーネル) a b c d e f g h i 0 1 0 1 ‐4 1 0 1 0 GPGPU実践基礎工学 = b+d‐4e+f+h 2015/11/25 モザイク処理 画像を小さな領域に分け,その領域を全て同じ色に置換 領域内のある1画素の値か,領域内の画素の平均値にするこ とが多い 原画像 559 処理画像 GPGPU実践基礎工学 2015/11/25 2次元配列 変数名の後ろに[]を2個付けて宣言 unsigned char picture[Nx][Ny]; 1次元配列はベクトル,2次元配列は行列や画像 i i matrix[Nx][Ny] vector[Nx] Ny=8 Nx=8 j Nx=8 560 GPGPU実践基礎工学 2015/11/25 2次元配列の1次元配列表現 mallocやcudaMallocは1次元配列を宣言 2次元配列を宣言できない 1次元配列を宣言し,2次元配列的に読み書き アドレス空間は1次元 2次元配列でも1次元のアドレスで表現 1次元目が連続か,2次元目が連続かが言語によって異なる 1次元目が連続 2次元目が連続 i i 561 matrix[Nx][Ny] matrix[Nx][Ny] j j GPGPU実践基礎工学 2015/11/25 2次元配列の1次元配列表現 C言語は2次元目が連続 picture[Nx][Ny] i 2次元配列の場合 picutre[1][2] = 11 1次元配列の場合 picture[_______] = 11 1*8 + 2 9 10 11 12 Ny=8 1 2 3 4 5 6 7 j 8 Nx=8 picture[i][j] picture[i*Ny+j] 562 GPGPU実践基礎工学 2015/11/25 ベクトル和C=A+B(2次元版) 1次元のベクトル和とほぼ同じ 並列化が容易 for文の書く順番で実行速度が変化 c[][] 563 a[][] GPGPU実践基礎工学 b[][] 2015/11/25 ベクトル和(2次元版) #define Nx (1024) #define Ny (1024) //iとjのループを入れ替えるとどうなるか? for(i=0;i<Nx;i++){ for(j=0;j<Nx;j++){ c[i][j] = a[i][j]+b[i][j]; } } void init(float a[Nx][Ny], float b[Nx][Ny], float c[Nx][Ny]){ } int i,j; //iとjのループを入れ替えるとどうなるか? for(i=0;i<Nx;i++){ for(j=0;j<Nx;j++){ a[i][j] = 1.0; b[i][j] = 2.0; c[i][j] = 0.0; } } int main(void){ float a[Nx][Ny],b[Nx][Ny],c[Nx][Ny]; init(a, b, c); add(a, b, c); return 0; } } void add(float a[Nx][Ny], float b[Nx][Ny], float c[Nx][Ny]){ int i,j; 564 vectoradd2d.c GPGPU実践基礎工学 2015/11/25 ベクトル和(2次元→1次元版) for(j=0;j<Ny;j++){ ij = i*Ny+j; //ij = i+Nx*j;にするとどうなるか? c[ij] = a[ij]+b[ij]; } #include<stdlib.h> #define Nx (1024) #define Ny (1024) #define Nbytes (Nx*Ny*sizeof(float)) } void init(float *a, float *b, float *c){ } int i,j,ij; for(i=0;i<Nx;i++){ for(j=0;j<Ny;j++){ ij = i*Ny+j; //ij = i+Nx*j;にするとどうなるか? a[ij] = 1.0; b[ij] = 2.0; c[ij] = 0.0; } } int main(void){ float *a,*b,*c; a = (float *)malloc(Nbytes); b = (float *)malloc(Nbytes); c = (float *)malloc(Nbytes); init(a, b, c); add(a, b, c); } void add(float *a, float *b, float *c){ int i,j,ij; for(i=0;i<Nx;i++){ 565 free(a); free(b); free(c); return 0; } GPGPU実践基礎工学 vectoradd2d_1darray.c 2015/11/25 GPUによる2次元的な並列処理 ブロックとスレッドを2次元的に設定 566 1スレッドが配列の1要素(画像の1ピクセル)を処理 スレッドを2次元的に配置してブロックを構成 GPGPU実践基礎工学 2015/11/25 GPUの並列化の階層 グリッド-ブロック-スレッドの3階層 各階層の情報を参照できる変数 グリッド(Grid) gridDim グリッド内にあるブロックの数 ブロック(Block) x,y,zをメンバにもつdim3型構造体 blockIdx blockDim ブロックに割り当てられた番号 ブロック内にあるスレッドの数 スレッド(Thread) 567 threadIdx スレッドに割り当てられた番号 GPGPU実践基礎工学 2015/11/25 Hello Threads(2次元版) <<< , >>>の中にどのように数字を書くか #include<stdio.h> __global__ void hello(){ printf("gridDim.x=%d,blockIdx.x=%d,blockDim.x=%d,threadIdx.x=%d¥n", gridDim.x, blockIdx.x, blockDim.x, threadIdx.x); printf("gridDim.y=%d,blockIdx.y=%d,blockDim.y=%d,threadIdx.y=%d¥n", gridDim.y, blockIdx.y, blockDim.y, threadIdx.y); } int main(void){ hello<<<?,?>>>(); cudaDeviceSynchronize(); return 0; } hellothreads2d.cu 568 GPGPU実践基礎工学 2015/11/25 Hello Threads(2次元版) 1次元と同様<<<2,4>>>等として実行 実行結果(画面出力) gridDim.x=2, blockIdx.x=0, blockDim.x=4, threadIdx.x=0 gridDim.x=2, blockIdx.x=0, blockDim.x=4, threadIdx.x=1 gridDim.x=2, blockIdx.x=0, blockDim.x=4, threadIdx.x=2 gridDim.x=2, blockIdx.x=0, blockDim.x=4, threadIdx.x=3 gridDim.x=2, blockIdx.x=1, blockDim.x=4, threadIdx.x=0 gridDim.x=2, blockIdx.x=1, blockDim.x=4, threadIdx.x=1 gridDim.x=2, blockIdx.x=1, blockDim.x=4, threadIdx.x=2 gridDim.x=2, blockIdx.x=1, blockDim.x=4, threadIdx.x=3 gridDim.y=1, blockIdx.y=0, blockDim.y=1, threadIdx.y=0 gridDim.y=1, blockIdx.y=0, blockDim.y=1, threadIdx.y=0 gridDim.y=1, blockIdx.y=0, blockDim.y=1, threadIdx.y=0 gridDim.y=1, blockIdx.y=0, blockDim.y=1, threadIdx.y=0 gridDim.y=1, blockIdx.y=0, blockDim.y=1, threadIdx.y=0 gridDim.y=1, blockIdx.y=0, blockDim.y=1, threadIdx.y=0 gridDim.y=1, blockIdx.y=0, blockDim.y=1, threadIdx.y=0 gridDim.y=1, blockIdx.y=0, blockDim.y=1, threadIdx.y=0 569 GPGPU実践基礎工学 並列度の指定が できていない 2015/11/25 2次元的な並列度の指定 <<<,>>>の中にどのように数字を書くか 1次元の場合は数字を書くことができた 2次元,3次元は数字を並べて書くことができない dim3型構造体を利用 int main(void){ dim3 block(2,4,1), thread(4,2,1); ・・・ dim3型変数block, hello<<<block, thread>>>(); threadを利用 hello<<<dim3(2,4,1), dim3(4,2,1)>>>(...); ・・・ あるいは直接dim3型として記述 } 570 GPGPU実践基礎工学 2015/11/25 Hello Threads(2次元版) 実行結果(画面出力) gridDim.x=2, blockIdx.x=0, blockDim.x=4, threadIdx.x=0 gridDim.x=2, blockIdx.x=0, blockDim.x=4, threadIdx.x=1 gridDim.x=2, blockIdx.x=0, blockDim.x=4, threadIdx.x=2 gridDim.x=2, blockIdx.x=0, blockDim.x=4, threadIdx.x=3 gridDim.x=2, blockIdx.x=0, blockDim.x=4, threadIdx.x=0 gridDim.x=2, blockIdx.x=0, blockDim.x=4, threadIdx.x=1 gridDim.x=2, blockIdx.x=0, blockDim.x=4, threadIdx.x=2 gridDim.x=2, blockIdx.x=0, blockDim.x=4, threadIdx.x=3 :(中略) gridDim.y=4, blockIdx.y=0, blockDim.y=2, threadIdx.y=0 gridDim.y=4, blockIdx.y=0, blockDim.y=2, threadIdx.y=0 gridDim.y=4, blockIdx.y=0, blockDim.y=2, threadIdx.y=0 gridDim.y=4, blockIdx.y=0, blockDim.y=2, threadIdx.y=0 gridDim.y=4, blockIdx.y=0, blockDim.y=2, threadIdx.y=1 gridDim.y=4, blockIdx.y=0, blockDim.y=2, threadIdx.y=1 gridDim.y=4, blockIdx.y=0, blockDim.y=2, threadIdx.y=1 gridDim.y=4, blockIdx.y=0, blockDim.y=2, threadIdx.y=1 : 571 GPGPU実践基礎工学 対応 対応 2015/11/25 各スレッドが異なるi,jを参照するには CUDAでは並列化に階層がある 全体の領域(グリッド)をブロックに分割 ブロックの中をスレッドに分割 <<<dim3(2,4,1), dim3(4,2,1)>>> ブロックの数 1ブロックあたりの スレッドの数 x方向ブロック数×1ブロックあたりのスレッドの数 ×y方向ブロック数×1ブロックあたりのスレッドの数=総スレッド数 総スレッド数=2×4×4×2=64 572 GPGPU実践基礎工学 2015/11/25 各スレッドが異なるi,jを参照するには Nx=8, Ny=8, x,y方向スレッド数4,ブロック数2 gridDim.x=2, gridDim.y=2 blockDim.x=4,blockDim.y=4 threadIdx.x,threadIdx.y 2 3 4 (0,0)(1,0)(2,0)(3,0)(0,0) 5 6 7 ・・・ (0,1)(1,1)(2,1)(3,1) (0,3)(1,3)(2,3)(3,3) ・・・ (0,0) ・・・ 5 6 ・・・ ・・・ GPGPU実践基礎工学 (3,3) 7 (3,3) 573 4 (0,0) (3,3) 2 3 ・・・ (0,2)(1,2)(2,2)(3,2) j= 0 1 i= 0 1 2015/11/25 各スレッドが異なるi,jを参照するには Nx=8, Ny=8, x,y方向スレッド数4,ブロック数2 gridDim.x=2 574 blockIdx.y=1 gridDim.y=2 threadIdx.x,threadIdx.y (0,0)(1,0)(2,0)(3,0)(0,0) (0,1)(1,1)(2,1)(3,1) (0,2)(1,2)(2,2)(3,2) (0,3)(1,3)(2,3)(3,3) (0,0) (3,3) blockDim.y=4 blockIdx.y=0 blockIdx.x=0 blockIdx.x=1 (0,0) (3,3) (3,3) blockDim.x=4 GPGPU実践基礎工学 2015/11/25 各スレッドが異なるi,jを参照するには Nx=8, Ny=8, x,y方向スレッド数4,ブロック数2 i = blockIdx.x*blockDim.x + threadIdx.x j = blockIdx.y*blockDim.y + threadIdx.y threadIdx.x,threadIdx.y block(1,0) 4 5 6 7 (0,0)(1,0)(2,0)(3,0)(0,0) (0,1)(1,1)(2,1)(3,1) (0,3)(1,3)(2,3)(3,3) 4 (0,0) (3,3) 2 3 (0,2)(1,2)(2,2)(3,2) j= 0 1 block(0,0) i= 0 1 2 3 (0,0) 5 6 block(0,1) 575 (3,3) 7 (3,3) block(1,1) GPGPU実践基礎工学 2015/11/25 ベクトル和(1次元配列版) ij = i*Ny+j; c[ij] = a[ij]+b[ij]; #include<stdlib.h> #define Nx (1024) #define Ny (1024) #define Nbytes (Nx*Ny*sizeof(float)) } } } void init(float *a, float *b, float *c){ int main(void){ float *a,*b,*c; a = (float *)malloc(Nbytes); b = (float *)malloc(Nbytes); c = (float *)malloc(Nbytes); int i,j,ij; for(i=0;i<Nx;i++){ for(j=0;j<Ny;j++){ ij = i*Ny+j; a[ij] = 1.0; b[ij] = 2.0; c[ij] = 0.0; } } init(a, b, c); add(a, b, c); free(a); free(b); free(c); return 0; } void add(float *a, float *b, float *c){ int i,j,ij; for(i=0;i<Nx;i++){ for(j=0;j<Ny;j++){ 576 } vectoradd2d_1darray.c GPGPU実践基礎工学 2015/11/25 ベクトル和(2次元並列版) #define Nx (1024) #define Ny (1024) #define Nbytes (Nx*Ny*sizeof(float)) #define NTx (16) #define NTy (16) #define NBx (Nx/NTx) #define NBy (Ny/NTy) int ij = i*Ny + j; c[ij] = a[ij] + b[ij]; } __global__ void init(float *a, float *b, float *c){ int main(void){ float *a,*b,*c; dim3 thread(NTx, NTy, 1); dim3 block(NBx, NBy, 1); int i = blockIdx.x*blockDim.x + threadIdx.x; int j = blockIdx.y*blockDim.y + threadIdx.y; int ij = i*Ny + j; cudaMalloc( (void **)&a, Nbytes); cudaMalloc( (void **)&b, Nbytes); cudaMalloc( (void **)&c, Nbytes); a[ij] = 1.0; b[ij] = 2.0; c[ij] = 0.0; init<<< block, thread >>>(a,b,c); add<<< block, thread >>>(a,b,c); cudaFree(a); cudaFree(b); cudaFree(c); return 0; } __global__ void add(float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; int j = blockIdx.y*blockDim.y + threadIdx.y; 577 } GPGPU実践基礎工学 vectoradd2d.cu 2015/11/25 実行結果 2次元版 # CUDA_PROFILE_LOG_VERSION 2.0 # CUDA_DEVICE 0 Tesla M2050 # TIMESTAMPFACTOR fffff5f7a904f250 method,gputime,cputime,occupancy method=[ _Z4initPfS_S_ ] gputime=[ 735.072 ] cputime=[ 16.000 ] occupancy=[ 1.000 ] method=[ _Z3addPfS_S_ ] gputime=[ 298.080 ] cputime=[ 7.000 ] occupancy=[ 1.000 ] カーネル init add 578 スレッド数 16×16 16×16 GPGPU実践基礎工学 実行時間[s] 735 298 2015/11/25 実行結果 1次元版(vectoradd.cu) # CUDA_PROFILE_LOG_VERSION 2.0 # CUDA_DEVICE 0 Tesla M2050 # TIMESTAMPFACTOR fffff5f7dad0bb38 method,gputime,cputime,occupancy method=[ _Z4initPfS_S_ ] gputime=[ 108.928 ] cputime=[ 15.000 ] occupancy=[ 1.000 ] method=[ _Z3addPfS_S_ ] gputime=[ 112.992 ] cputime=[ 7.000 ] occupancy=[ 1.000 ] カーネル init add 579 スレッド数 実行時間[s] 109 256(=16×16) 113 256(=16×16) GPGPU実践基礎工学 2015/11/25 ベクトル和(2次元並列版)の実行結果 実行結果 1次元版と比較して実行時間がかかる initで7倍 addで3倍 遅くなる要因は? 580 2次元は添字iだけでなくjも計算 添字の計算負荷は軽い 実行時の並列度の指定 GPGPU実践基礎工学 2015/11/25 ベクトル和(2次元並列版) #define Nx (1024) #define Ny (1024) #define Nbytes (Nx*Ny*sizeof(float)) #define NTx (16) #define NTy (16) #define NBx (Nx/NTx) #define NBy (Ny/NTy) int ij = i+ Nx*j; c[ij] = a[ij] + b[ij]; } __global__ void init(float *a, float *b, float *c){ int main(void){ float *a,*b,*c; dim3 thread(NTx, NTy, 1); dim3 block(NBx, NBy, 1); int i = blockIdx.x*blockDim.x + threadIdx.x; int j = blockIdx.y*blockDim.y + threadIdx.y; int ij = i+ Nx*j; cudaMalloc( (void **)&a, Nbytes); cudaMalloc( (void **)&b, Nbytes); cudaMalloc( (void **)&c, Nbytes); a[ij] = 1.0; b[ij] = 2.0; c[ij] = 0.0; init<<< block, thread >>>(a,b,c); add<<< block, thread >>>(a,b,c); cudaFree(a); cudaFree(b); cudaFree(c); return 0; } __global__ void add(float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; int j = blockIdx.y*blockDim.y + threadIdx.y; 581 } GPGPU実践基礎工学 vectoradd2d.cu 2015/11/25 実行結果 2次元版 # CUDA_PROFILE_LOG_VERSION 2.0 # CUDA_DEVICE 0 Tesla M2050 # TIMESTAMPFACTOR fffff5f8098b61e0 method,gputime,cputime,occupancy method=[ _Z4initPfS_S_ ] gputime=[ 230.976 ] cputime=[ 15.000 ] occupancy=[ 1.000 ] method=[ _Z3addPfS_S_ ] gputime=[ 152.960 ] cputime=[ 6.000 ] occupancy=[ 1.000 ] カーネル init add 582 スレッド数 16×16 16×16 GPGPU実践基礎工学 実行時間[s] 230 153 2015/11/25 2次元的な配列アクセスの優先方向(C言語) 2次元配列の場合 in[][],out[][] for(i=0;i<Nx;i++) for(j=0;j<Ny;j++) out[i][j]=in[i][j]; for(j=0;j<Ny;j++) for(i=0;i<Nx;i++) out[i][j]=in[i][j]; i Ny j Nx 583 GPGPU実践基礎工学 2015/11/25 2次元的な配列アクセスの優先方向(C言語) 2次元配列の場合 in[][],out[][] for(i=0;i<Nx;i++) for(j=0;j<Ny;j++) out[i][j]=in[i][j]; for(j=0;j<Ny;j++) for(i=0;i<Nx;i++) out[i][j]=in[i][j]; i Ny j Nx 584 GPGPU実践基礎工学 2015/11/25 2次元的な配列アクセスの優先方向(C言語) 2次元配列の1次元配列的表現 in[],out[] for(i=0;i<Nx;i++) for(j=0;j<Ny;j++) out[i][j]=in[i][j]; for(i=0;i<Nx;i++) for(j=0;j<Ny;j++) out[i*Ny+j]= in[i*Ny+j]; i Ny j Nx 585 GPGPU実践基礎工学 2015/11/25 2次元的な配列アクセスの優先方向 CUDAで2次元的に並列化してアクセスする場合 for(i=0;i<Nx;i++) for(j=0;j<Ny;j++) out[i*Ny+j]= in[i*Ny+j]; in[],out[] i threadIdx.x threadIdx.y i = blockIdx.x*blockDim.x + threadIdx.x; j = blockIdx.y*blockDim.y + threadIdx.y; j out[i+Nx*j]=in[i+Nx*j]; Ny Nx 586 GPGPU実践基礎工学 2015/11/25 2次元的な配列アクセスの優先方向 C言語 CUDA C メモリアドレスは2次元目が連続 その関係を維持するように配列を1次元化 メモリアクセスの際はthreadIdx.xが連続なアドレスにアク セスする方が高速 CUDA Cで2次元的な処理を行うとき 587 1次元配列は1次元目のメモリアドレスが連続となるように扱っ た方がよい GPGPU実践基礎工学 2015/11/25 2次元配列の1次元表現(C言語) C言語は2次元目が連続 その関係を維持するように1次元化 picture[Nx*Ny] 2次元配列の場合 picutre[1][2] = 11 1次元配列の場合 picture[_______] = 11 1*8 + 2 9 10 11 12 Ny=8 1 2 3 4 5 6 7 j 8 メモリが連続な方向 i Nx=8 picture[i][j] picture[i*Ny+j] 588 GPGPU実践基礎工学 2015/11/25 2次元配列の1次元表現(CUDA C) メモリアクセスの際はthreadIdx.xが連続なアドレスに アクセスする方が高速 picture[Nx*Ny] i 2次元配列の場合 picutre[1][2] = 11 1次元配列の場合 9 10 11 12 Ny=8 1 2 3 4 5 6 7 j 8 Nx=8 589 GPGPU実践基礎工学 2015/11/25 2次元配列の1次元表現(CUDA C) threadIdx.xが連続なアドレスにアクセスする方がメモ リアクセスが高速 block(0,0) block(1,0) threadIdx.x 2次元配列の場合 picutre[1][2] = 11 1次元配列の場合 9 10 11 12 threadIdx.y 1 2 3 4 5 6 7 8 メモリが連続な方向 picture[_______] = 11 1 + 8*2 block(0,1) block(1,1) picture[i][j] picture[i+Nx*j] 590 GPGPU実践基礎工学 2015/11/25 想定されるカーネル 1スレッドが2次元配列の1要素を計算 添字 i,j の要素を担当 #define Nx (1920) //水平方向ピクセル数 #define Ny (1080) //垂直方向ピクセル数 __global__ void filter(...){ int i = blockIdx.x*blockDim.x + threadIdx.x; int j = blockIdx.y*blockDim.y + threadIdx.y; picture[i + Nx*j] = ...; } 591 GPGPU実践基礎工学 2015/11/25 画像処理の流れ 変数を宣言し,mallocでメモリを確保 画像pを作成 pをfilteredにコピーしておく 処理を実行 画像を格納する変数 p 処理後の画像を格納する変数 filtered 関数を作成し,pとfilteredを渡す filteredの内容を画面に出力 592 GPGPU実践基礎工学 2015/11/25 メイン関数(CPU版) #include<stdio.h> #include<stdlib.h> #define WHITE (255) #define BLACK (0) #define WIDTH 128 #define HEIGHT WIDTH #define Nbytes (WIDTH*HEIGHT*sizeof(unsigned char)) void create(unsigned char *); void copy(unsigned char *,unsigned char *); void print(unsigned char *); int main(void){ unsigned char *p= (unsigned char *)malloc(Nbytes); unsigned char *filtered= (unsigned char *)malloc(Nbytes); //ここで空間フィルタのカーネルを宣言 create(p); copy(p,filtered); //ここで処理を行い,結果をfilteredに格納 593 GPGPU実践基礎工学 imageproc.c 2015/11/25 メイン関数(CPU版) //画面に各画素の値を表示 print(filtered); return 0; } //画像の内容をコピー void copy(unsigned char *src, unsigned char *dst){ int i; for(i=0;i<WIDTH*HEIGHT;i++) dst[i] = src[i]; } //画像の内容を画面に出力(gnuplotで表示する用) void print(unsigned char *p){ int i,j; for(j=0;j<HEIGHT;j++){ for(i=0;i<WIDTH;i++){ printf("%d %d %d¥n", i, j, p[i+WIDTH*j]);//横位置,縦位置,画像の色情報 } printf("¥n");//1行分表示したら改行を入れる(gnuplotで必要) } imageproc.c } 594 GPGPU実践基礎工学 2015/11/25 画像の作成 void create(unsigned char *p){ 6WIDTH/16 x_origin = 9*WIDTH/16; y_origin = 9*HEIGHT/16; for(i=0;i<6*WIDTH/16;i++) for(j=i;j<6*HEIGHT/16;j++) p[i+x_origin + WIDTH*(j+y_origin)] = BLACK; } HEIGHT 9HEIGHT/16 for(j=0;j<HEIGHT;j++){ for(i=0;i<WIDTH;i++){ p[i+WIDTH*j] = WHITE; } } 6HEIGHT/16 int i,j; int x_origin,y_origin; 9WIDTH/16 WIDTH 595 GPGPU実践基礎工学 2015/11/25 結果の画面表示とファイル保存 print関数が画面に各画素の座標と値を表示 0 0 255 1 0 255 : 126 127 255 127 127 255 画面出力のファイル表示(出力のリダイレクトを利用) >を利用して保存 $ コマンド > ファイル名 $ ./a.out > pic.txt $ cat pic.txt 0 0 255 1 0 255 : 126 127 255 127 127 255 596 GPGPU実践基礎工学 2015/11/25 gnuplotによる結果の表示 2次元,3次元データをプロットするアプリケーション コマンドラインで命令を実行してグラフを描画 関数の描画,ファイルから読み込んだデータの表示が可能 tesla??では正しく動作しないため,grouseで実行する こと 597 GPGPU実践基礎工学 2015/11/25 gnuplotによる結果の表示 表示可能なファイルフォーマット データはスペース区切り 3次元表示では列(または行)の区切りとして空白行が必要 0 0 255 1 0 255 ... 126 0 255 127 0 255 <‐改行 0 1 255 1 1 255 ... 126 1 255 127 1 255 <‐改行 0 2 255 1 2 255 598 GPGPU実践基礎工学 2015/11/25 gnuplotによる結果の表示 実行する命令 #表示設定(画像っぽく表示するための設定) set xrange[0:127] #x軸の表示範囲を0~127(画像サイズ)に固定 set yrange[0:127] #y軸の表示範囲を0~127(画像サイズ)に固定 set cbrange[0:255] #カラーバーの範囲を0~255に固定 set size square #画像を正方形で表示 set pm3d map #カラー表示して真上から表示 set palette define(0"black",255"white") #0を黒,255を白 #設定を反映してファイルを読み込み #(ファイル名はシングルクオートで囲む) splot 'pic.txt' plot.txt 599 GPGPU実践基礎工学 2015/11/25 画像処理 比較的簡単な4種類の処理を実装 ネガティブ処理 画像反転 空間フィルタ 水平反転,垂直反転 ぼかし,輪郭抽出 モザイク処理 600 GPGPU実践基礎工学 2015/11/25 ネガティブ処理 色の反転処理 黒(0)を白(255),白(255)を黒(0)に反転 (255−各画素の値)を新しい画素の値とする 原画像 601 処理画像 GPGPU実践基礎工学 2015/11/25 ネガティブ処理 void negative(unsigned char *p, unsigned char *filtered){ int i,j; for(j=0;j<HEIGHT;j++){ for(i=0;i<WIDTH;i++){ filtered[i+WIDTH*j] = WHITE ‐ p[i + WIDTH*j]; } } } imageproc.c 602 GPGPU実践基礎工学 2015/11/25 水平反転 画像の幾何的位置を反転 HEIGHT‐1 水平反転 i→(WIDTH‐1)‐i 垂直反転 j→(HEIGHT‐1)‐j 原画像 処理画像 j 0 603 i WIDTH‐1 GPGPU実践基礎工学 2015/11/25 垂直反転 画像の幾何的位置を反転 HEIGHT‐1 水平反転 i→(WIDTH‐1)‐i 垂直反転 j→(HEIGHT‐1)‐j 原画像 処理画像 j 0 604 i WIDTH‐1 GPGPU実践基礎工学 2015/11/25 水平反転 void hreflect(unsigned char *p, unsigned char *reflected){ int i,j,iref; for(j=0;j<HEIGHT;j++){ for(i=0;i<WIDTH;i++){ iref = (WIDTH‐1)‐i; //反転後のx座標値 reflected[iref+WIDTH*j] = p[i + WIDTH*j]; } } } imageproc.c 605 GPGPU実践基礎工学 2015/11/25 垂直反転 void vreflect(unsigned char *p, unsigned char *reflected){ int i,j,jref; for(j=0;j<HEIGHT;j++){ for(i=0;i<WIDTH;i++){ jref = (HEIGHT‐1)‐j; //反転後のy座標値 reflected[i+WIDTH*jref] = p[i + WIDTH*j]; } } } imageproc.c 606 GPGPU実践基礎工学 2015/11/25 空間フィルタ ある画素とその周囲の画素を使って処理 処理の仕方を規定したカーネルを定義 カーネルは1次元配列で表現 原画像 607 輪郭抽出 フィルタ (カーネル) a b c d e f g h i 0 1 0 1 ‐4 1 0 1 0 GPGPU実践基礎工学 = b+d‐4e+f+h 2015/11/25 空間フィルタ カーネルは1次元配列で表現し,引数で渡す ぼかし(平均フィルタ) 1/9 1/9 1/9 1/9 1/9 1/9 1/9 1/9 1/9 float blur[9] ={1.0f/9.0f,1.0f/9.0f,1.0f/9.0f, 1.0f/9.0f,1.0f/9.0f,1.0f/9.0f, 1.0f/9.0f,1.0f/9.0f,1.0f/9.0f}; 輪郭抽出(ラプラシアンフィルタ) 0 608 1 0 1 ‐4 1 0 1 0 float laplacian[9] ={ 0.0f, 1.0f, 0.0f, 1.0f,‐4.0f, 1.0f, 0.0f, 1.0f, 0.0f}; GPGPU実践基礎工学 2015/11/25 空間フィルタ void boxfilter(unsigned char *p, unsigned char *filtered, float *filter){ int i,j int result = BLACK; //端では何もしない for(j=1;j<HEIGHT‐1;j++){ for(i=1;i<WIDTH‐1;i++){ result = filter[0]*p[(i‐1) + WIDTH*(j‐1)] +filter[1]*p[(i ) + WIDTH*(j‐1)] +filter[2]*p[(i+1) + WIDTH*(j‐1)] +filter[3]*p[(i‐1) + WIDTH*(j )] +filter[4]*p[(i ) + WIDTH*(j )] +filter[5]*p[(i+1) + WIDTH*(j )] +filter[6]*p[(i‐1) + WIDTH*(j+1)] +filter[7]*p[(i ) + WIDTH*(j+1)] +filter[8]*p[(i+1) + WIDTH*(j+1)]; if(result<BLACK) result = ‐result; //数値が負になっていれば‐1をかける if(result>WHITE) result = WHITE; //数値が255を超えていれば255に収める filtered[i+WIDTH*j] = (unsigned char)result; } } imageproc.c } 609 GPGPU実践基礎工学 2015/11/25 モザイク処理 画像を小さな領域に分け,その領域を全て同じ色にする 領域内の全画素を,領域内の画素の平均値に置き換える 原画像 610 処理画像 GPGPU実践基礎工学 2015/11/25 モザイク void mosaic(unsigned char *f,unsigned char *filtered, int mosaicSize){ int i,j, isub,jsub; int average; for(j=0;j<HEIGHT;j+=mosaicSize){ for(i=0;i<WIDTH;i+=mosaicSize){ //領域内の画素の平均値を計算 average = 0; for(jsub = 0; jsub<mosaicSize; jsub++){ for(isub = 0; isub<mosaicSize; isub++){ average += f[(i+isub) + WIDTH*(j+jsub)]; } } average /= (mosaicSize*mosaicSize); //領域内の画素を計算した平均値で塗りつぶす for(jsub = 0; jsub<mosaicSize; jsub++){ for(isub = 0; isub<mosaicSize; isub++){ filtered[(i+isub) + WIDTH*(j+jsub)] = (unsigned char)average; } } } } imageproc.c } 611 GPGPU実践基礎工学 2015/11/25 GPUへの移植の方針 画像の作成はCPUで行い,GPUへ転送 1スレッドが1画素の処理を実行 空間フィルタ 2重のforループを排除し,配列添字とスレッド番号を対応 端の画素の処理ができないので今回は処理しない モザイク 領域の大きさ(横×縦)は1ブロックと同じとする 領域内の画素の平均値の計算は非常に難易度が高い 612 threadIdx.x=0,threadIdx.y=0のスレッドが平均を計算してメモ リに書き出す GPGPU実践基礎工学 2015/11/25 メイン関数(GPU版) #include<stdio.h> #include<stdlib.h> #define WHITE (255) #define BLACK (0) #define WIDTH 4096 #define HEIGHT WIDTH #define Nbytes (WIDTH*HEIGHT*sizeof(unsigned char)) #define THREADX 16 #define THREADY 16 #define BLOCKX (WIDTH/THREADX) #define BLOCKY (HEIGHT/THREADY) void create(unsigned char *); void print(unsigned char *); int main(void){ unsigned char *p= (unsigned char *)malloc(Nbytes); unsigned char *dev_p, *dev_filtered; cudaMalloc( (void **)&dev_p, Nbytes); cudaMalloc( (void **)&dev_filtered, Nbytes); 613 GPGPU実践基礎工学 imageproc.cu 2015/11/25 メイン関数(GPU版) //ここで空間フィルタのカーネルを宣言 float *filter; cudaMalloc( (void **)&filter, sizeof(float)*9); cudaMemcpy(filter, フィルタのカーネル, sizeof(float)*9, cudaMemcpyHostToDevice); create(p); //CPUで画像を生成してGPUへ送った後,変数dev_filteredにコピーしておく cudaMemcpy(dev_p, p, Nbytes, cudaMemcpyHostToDevice); cudaMemcpy(dev_filtered, dev_p, Nbytes, cudaMemcpyDeviceToDevice); dim3 block( BLOCKX, BLOCKY, 1), thread(THREADX, THREADY, 1); //ここで処理を行い,結果をdev_filteredに格納 //dev_filteredの内容をCPUへ送る unsigned char *filtered = (unsigned char *)malloc(Nbytes); cudaMemcpy(filtered, dev_filtered, Nbytes, cudaMemcpyDeviceToHost); free(p); free(filtered); cudaFree(dev_p); cudaFree(dev_filtered); cudaFree(filter); return 0; imageproc.cu } 614 GPGPU実践基礎工学 2015/11/25 メイン関数(GPU版) void create(unsigned char *p){ int i,j, x_origin,y_origin; for(j=0;j<HEIGHT;j++){ for(i=0;i<WIDTH;i++){ p[i+WIDTH*j] = WHITE; } } x_origin = 9*WIDTH/16; y_origin = 9*HEIGHT/16; for(i=0;i<6*WIDTH/16;i++){ for(j=i;j<6*HEIGHT/16;j++){ p[i+x_origin + WIDTH*(j+y_origin)] = BLACK; } } } void print(unsigned char *p){ int i,j; for(j=0;j<HEIGHT;j++){ for(i=0;i<WIDTH;i++){ printf("%d %d %d¥n", i, j, p[i+WIDTH*j]); } printf("¥n"); } } 615 GPGPU実践基礎工学 imageproc.cu 2015/11/25 ネガティブ処理 __global__ void negative(unsigned char *p, unsigned char *filtered){ int i,j; i = blockIdx.x*blockDim.x + threadIdx.x; //スレッド数と配列添字の対応 j = blockIdx.y*blockDim.y + threadIdx.y; // filtered[i+WIDTH*j] = WHITE ‐ p[i + WIDTH*j]; } imageproc.cu 616 GPGPU実践基礎工学 2015/11/25 水平反転 __global__ void hreflect(unsigned char *p, unsigned char *reflected){ int i,j; int iref; i = blockIdx.x*blockDim.x + threadIdx.x; j = blockIdx.y*blockDim.y + threadIdx.y; iref = (WIDTH‐1)‐i; //反転後のx座標値 reflected[iref+WIDTH*j] = p[i + WIDTH*j]; } imageproc.cu 617 GPGPU実践基礎工学 2015/11/25 垂直反転 __global__ void vreflect(unsigned char *p, unsigned char *reflected){ int i,j; int jref; i = blockIdx.x*blockDim.x + threadIdx.x; j = blockIdx.y*blockDim.y + threadIdx.y; jref = (HEIGHT‐1)‐j; //反転後のy座標値 reflected[i+WIDTH*jref] = p[i + WIDTH*j]; } imageproc.cu 618 GPGPU実践基礎工学 2015/11/25 空間フィルタ __global__ void boxfilter(unsigned char *p, unsigned char *filtered, float *filter){ int i,j; int result = BLACK; i = blockIdx.x*blockDim.x + threadIdx.x; j = blockIdx.y*blockDim.y + threadIdx.y; if(0<i && i<WIDTH‐1 && 0<j && j<HEIGHT‐1){ //端の画素は処理をしないため,ifで処理を分岐 result = filter[0]*p[(i‐1) + WIDTH*(j‐1)] +filter[1]*p[(i ) + WIDTH*(j‐1)] +filter[2]*p[(i+1) + WIDTH*(j‐1)] +filter[3]*p[(i‐1) + WIDTH*(j )] +filter[4]*p[(i ) + WIDTH*(j )] +filter[5]*p[(i+1) + WIDTH*(j )] +filter[6]*p[(i‐1) + WIDTH*(j+1)] +filter[7]*p[(i ) + WIDTH*(j+1)] +filter[8]*p[(i+1) + WIDTH*(j+1)]; } if(result<BLACK) result = ‐result; //数値が負になっていれば‐1をかける if(result>WHITE) result = WHITE; //数値が255を超えていれば255に収める filtered[i+WIDTH*j] = (unsigned char)result; } imageproc.cu 619 GPGPU実践基礎工学 2015/11/25 モザイク処理 __global__ void mosaic(unsigned char *p, unsigned char *filtered, int mosaicSize){ int i,j, isub,jsub; int average; i = blockIdx.x*blockDim.x + threadIdx.x; j = blockIdx.y*blockDim.y + threadIdx.y; if(threadIdx.x == 0 && threadIdx.y == 0){//ブロック内の1スレッドのみが処理 //領域内の画素の平均値を計算 average = 0; for(jsub = 0; jsub<mosaicSize; jsub++){ for(isub = 0; isub<mosaicSize; isub++){ average += p[(i+isub) + WIDTH*(j+jsub)]; } } average /= (mosaicSize*mosaicSize); //領域内の画素を計算した平均値で塗りつぶす for(jsub = 0; jsub<mosaicSize; jsub++){ for(isub = 0; isub<mosaicSize; isub++){ filtered[(i+isub) + WIDTH*(j+jsub)] = (unsigned char)average; } } } imageproc.cu } 620 GPGPU実践基礎工学 2015/11/25 処理時間の比較 画像サイズ 4096×4096 1ブロックあたりのスレッド数 16×16 モザイクのサイズはスレッド数と同じ(16×16) 処理 ネガティブ処理 水平反転 垂直反転 空間フィルタ モザイク処理 621 処理時間[ms] CPU 175 187 185 553 260 GPGPU実践基礎工学 GPU 1.17 1.18 1.18 4.13 38.5 補足 ビットマップ画像のフォーマット ビットマップ画像 画像を画素(pixel)の集合として捉え,各画素の色を3 原色(赤緑青,RGB)で表す 1画素あたり1バイト~4バイト jpg画像のように画像圧縮を施さないのでデータ量が画 像サイズに比例して増大 624 圧縮形式もあるがあまり利用されない zip形式等のファイル圧縮を使うと効果的に圧縮可能 GPGPU実践基礎工学 2015/11/25 ビットマップファイルフォーマット ファイルの情報を表すヘッダ+画像の各画素情報 ヘッダ ファイルヘッダ ファイルの種類,ファイルサイズ等 情報ヘッダ ファイルヘッダ+情報ヘッダの合計54バイト 画像の幅,高さ,1画素あたりのデータサイズなど 画像の各画素の情報 625 GPGPU実践基礎工学 2015/11/25 ビットマップファイルフォーマット 24bitビットマップファイルのバイナリダンプ ファイルヘッダ 情報ヘッダ 1画素の情報 画像情報 626 GPGPU実践基礎工学 2015/11/25 ビットマップファイルフォーマット ファイルヘッダ 内容 サイズ ファイルの種類 備考 u2 ビットマップの場合はBM u4 16進数表記(リトルエンディアン,下位 ビットが先に書かれる) 予約領域1 u2 必ず0 予約領域2 u2 必ず0 ファイル先頭から画像までの オフセット u4 windowsビットマップでは54 (16進数表記で36) ファイルサイズ(byte) エンディアン(endian, 多バイトデータの記述・格納形式) 36 00 03 00 リトルエンディアン 00 03 00 3616=196,662 byte ビッグエンディアン 36 00 03 0016=905,970,432 byte 627 GPGPU実践基礎工学 2015/11/25 ビットマップファイルフォーマット 情報ヘッダ 内容 備考 byte数 情報ヘッダのサイズ u4 必ず40(byte, 16進数表記で28) 画像の幅(pixel) 4 16進数表記 画像の高さ(pixel) 4 16進数表記 プレーン数 u2 必ず1 色ビット数(bit/pixel) u2 1,4,8,24,32(16進数表記) 圧縮形式 u4 0,1,2,3のいずれか 画像データサイズ u4 16進数表記 水平解像度(dot/m) 4 0の場合もある 垂直解像度(dot/m) 4 0の場合もある パレット数(使用色数) u4 0の場合もある 重要な色の数 u4 0の場合もある 628 GPGPU実践基礎工学 2015/11/25 ビットマップファイルフォーマット 画像情報(24bitビットマップ) 3原色の情報をそれぞれ1バイト(unsigned char)で保持 3原色の並び順はBGR 幅は行単位で4バイトの倍数に揃えられる 画素の並びは左下から右方向,下から上方向 BG R 629 GPGPU実践基礎工学 2015/11/25
© Copyright 2024 ExpyDoc