授業項目
並列計算の概念(プロセスとスレッド)
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 2026 ExpyDoc