2015年度GPGPU実践基礎工学 第12回 GPUによる画像処理

April 26, 2018 | Author: Anonymous | Category: Engineering
Report this link


Description

第12回 GPUによる画像処理 長岡技術科学大学 電気電子情報工学専攻 出川智啓 授業項目 8. 並列計算の概念(プロセスとスレッド) 9. GPUのアーキテクチャ 10. GPGPUのプログラム構造 11. GPUでの並列プログラミング(ベクトル和) 12. GPUによる画像処理 13. GPUのメモリ階層 14. GPGPU組込開発環境 15. GPGPU開発環境(OpenCL) GPGPU実践基礎工学2 2015/11/25 今回の内容 GPGPU実践基礎工学3  画像処理  ネガティブ処理,画像反転,空間フィルタ,モザイク処理  画像処理を簡略化した1次元的な処理  2次元的な並列処理  2次元配列の表現  ベクトル和  モノクロ画像処理 2015/11/25 画像処理 GPGPU実践基礎工学4  デジタル画像を別の画像に変換  処理の種類  画像の拡大・縮小  画像の反転  フィルタ  2値化,ぼかしなど 2015/11/25 画像処理を行うプログラムの流れ GPGPU実践基礎工学5  読み込む画像を準備  対象ファイルをオープン  画像の読込  画像ファイルの種類(bmp,jpg,png等)に応じて読み込む方 法を選択  画像ファイルからヘッダ情報を読み込み,様々な情報を取得  メモリを確保し,画素情報を読込  定められたアルゴリズムで画像を処理  処理された画像の出力 2015/11/25 画像処理 GPGPU実践基礎工学6  画像を読むプログラムを作るだけで一苦労  画像処理が主目的ではなく,GPUの使い方を学習するこ とが目的  画像をモノクロに限定  プログラム中で画像に相当するデータを生成  ターミナルにテキスト出力(横の位置,縦の位置,色情報)  画像の表示にはgnuplotを利用  モノクロ画像に対して簡単な処理を実行 2015/11/25 取り扱うモノクロ画像 GPGPU実践基礎工学7  型はunsigned char  1バイトの整数型  取り扱える範囲は0~255  0を黒,255を白とみなす  1次元配列で取り扱う  画像は2次元なので,2次元配列で取り扱う方が自然  mallocやcudaMallocでは2次元配列を確保できない 2015/11/25 取り扱うモノクロ画像の内容 GPGPU実践基礎工学8 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, 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}; unsigned char picture[] = { x y 2015/11/25 取り扱うモノクロ画像の内容 GPGPU実践基礎工学9  gnuplotで表示  左下を原点として表示  画像処理では左上が原点 x y 2015/11/25 画像処理 GPGPU実践基礎工学10  ネガティブ処理  反転  水平反転,垂直反転  空間フィルタ  ぼかし,輪郭抽出  モザイク処理 2015/11/25 画像処理の簡略化(1次元化) GPGPU実践基礎工学11  いきなり画像処理は難しい  1次元の配列に対して,画像処理と同じような処理を実行  どのような処理を行うかを確認  ネガティブ処理  水平反転  空間フィルタ(ぼかし,輪郭抽出)  モザイク処理 2015/11/25 原画像もどき 2015/11/25GPGPU実践基礎工学12  幅 WIDTH=64 (0~63)  [3*WIDTH/16,9*WIDTH/16)の範囲を白(255),それ 以外を黒(0)  変数名  原画像 p[]  処理画像 filtered[]  原画像pのピクセル値 画 素 値 横位置 ネガティブ処理 2015/11/25GPGPU実践基礎工学13  色の反転処理  黒(0)を白(255),白(255)を黒(0)に反転  (白−各画素の値)を新しい画素の値とする 画 素 値 横位置 画 素 値 原画像p[] 処理画像 filtered[] 白(255)を黒(0)に 黒(0)を白(255)に ネガティブ処理 2015/11/25GPGPU実践基礎工学14  色の反転処理  黒(0)を白(255),白(255)を黒(0)に反転  (白−各画素の値)を新しい画素の値とする void negative(unsigned char *p, unsigned char *filtered){ int i; for (i = 0; i 水平反転 2015/11/25GPGPU実践基礎工学15  画像の幾何的位置を反転  水平反転 i→(WIDTH‐1)‐i  p[i]→filtered[(WIDTH‐1)‐i] 画 素 値 横位置 画 素 値 原画像p[] 処理画像 filtered[] 横位置1の画素値を62番目の画素値に 横位置35の画素値を28番目の画素値に 水平反転 2015/11/25GPGPU実践基礎工学16  画像の幾何的位置を反転  水平反転 i→(WIDTH‐1)‐i  p[i]→filtered[(WIDTH‐1)‐i] void hreflect(unsigned char *p, unsigned char *filtered){ int i; for (i = 0; i 空間フィルタ 2015/11/25GPGPU実践基礎工学17  ある画素とその周囲の画素を使って処理  処理の仕方を規定したフィルタカーネルを定義  フィルタカーネルは1次元配列で表現,引数として関数に渡す  ぼかし(平均)フィルタ  輪郭抽出(ラプラシアン)フィルタ 1/3 1/3 1/3 1 ‐2 1 float blur[3] ={1.0f/3.0f,1.0f/3.0f,1.0f/3.0f}; float laplacian[9] ={ 1.0f,‐2.0f, 1.0f}; 画 素 値 横位置 画 素 値 空間フィルタ 2015/11/25GPGPU実践基礎工学18  ある画素とその周囲の画素を使って処理  ある位置iの左右(i‐1,i+1)の計3画素の値を読み,それぞ れフィルタカーネルの各値との積を計算し,合計を取った値を 処理画像のi番目の画素とする ぼかしフィルタカーネル 1/3 1/3 1/3 255/3+255/3+255/3=255 原画像p[] 処理画像 filtered[] 画 素 値 横位置 画 素 値 空間フィルタ 2015/11/25GPGPU実践基礎工学19  ある画素とその周囲の画素を使って処理  ある位置iの左右(i‐1,i+1)の計3画素の値を読み,それぞ れフィルタカーネルの各値との積を計算し,合計を取った値を 処理画像のi番目の画素とする ぼかしフィルタカーネル 1/3 1/3 1/3 255/3+255/3+255/3=255 原画像p[] 処理画像 filtered[] 画 素 値 横位置 画 素 値 空間フィルタ 2015/11/25GPGPU実践基礎工学20  ある画素とその周囲の画素を使って処理  ある位置iの左右(i‐1,i+1)の計3画素の値を読み,それぞ れフィルタカーネルの各値との積を計算し,合計を取った値を 処理画像のi番目の画素とする ぼかしフィルタカーネル 1/3 1/3 1/3 255/3+255/3+255/3=255 原画像p[] 処理画像 filtered[] 画 素 値 横位置 空間フィルタ 2015/11/25GPGPU実践基礎工学21  ある画素とその周囲の画素を使って処理  ある位置iの左右(i‐1,i+1)の計3画素の値を読み,それぞ れフィルタカーネルの各値との積を計算し,合計を取った値を 処理画像のi番目の画素とする ぼかしフィルタカーネル 1/3 1/3 1/3 0/3+255/3+255/3=170 原画像p[] 処理画像 filtered[] 画 素 値 空間フィルタ 2015/11/25GPGPU実践基礎工学22  ある画素とその周囲の画素を使って処理  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 画 素 値 横位置 画 素 値 空間フィルタ 2015/11/25GPGPU実践基礎工学23  輪郭抽出フィルタの結果  白と黒の境界でのみ値が255になり,それ以外では0  両端はフィルタ処理できないため,初期値がそのまま使われる 原画像p[] 処理画像 filtered[]白と黒の境界でのみ 画素値が0より大きく なる モザイク処理 2015/11/25GPGPU実践基礎工学24  画像を小領域に分け,その領域を全て同じ色に置換  小領域内の全画素を,小領域内の画素の平均値に置換  小領域を移動するループ内に,小領域内の画素の平均値を 計算するループを設ける 画 素 値 横位置 原画像p[] 処理画像 filtered[] 画 素 値 小領域 モザイク処理 2015/11/25GPGPU実践基礎工学25  画像を小領域に分け,その領域を全て同じ色に置換  小領域内の全画素を,小領域内の画素の平均値に置換  小領域を移動するループ内に,小領域内の画素の平均値を 計算するループを設ける 画 素 値 横位置 原画像p[] 処理画像 filtered[] 画 素 値 モザイク処理 2015/11/25GPGPU実践基礎工学26  画像を小領域に分け,その領域を全て同じ色に置換  小領域内の全画素を,小領域内の画素の平均値に置換  小領域を移動するループ内に,小領域内の画素の平均値を 計算するループを設ける 画 素 値 横位置 原画像p[] 処理画像 filtered[] 画 素 値 小領域 モザイク処理 2015/11/25GPGPU実践基礎工学27  画像を小領域に分け,その領域を全て同じ色に置換  小領域内の全画素を,小領域内の画素の平均値に置換  小領域を移動するループ内に,小領域内の画素の平均値を 計算するループを設ける 画 素 値 横位置 原画像p[] 処理画像 filtered[] 画 素 値 モザイク処理 2015/11/25GPGPU実践基礎工学28  画像を小さな領域に分け,その領域を全て同じ色に置換  小領域内で画素の平均値を計算し,処理画像の小領域内の 全画素をその平均値とする void mosaic(unsigned char *p,unsigned char *filtered,int mosaicSize){ int i,isub,average; for (i = 0; i 処理の流れ GPGPU実践基礎工学29  変数を宣言し,mallocでメモリを確保  原画像を格納する変数 p  処理後の画像を格納する変数 filtered  原画像pを作成  pをfilteredにコピーしておく  処理を実行  関数を作成し,pとfilteredを渡す  filteredの内容を画面に出力 2015/11/25 メイン関数(CPU版) GPGPU実践基礎工学30 #include #include #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); //ここで空間フィルタのカーネルを宣言 2015/11/25 imageproc1d.c メイン関数(CPU版) GPGPU実践基礎工学31 create(p); copy(p,filtered); //ここで処理を行い,結果をfilteredに格納 //画面に各画素の値を表示 print(filtered); return 0; } //画像の内容をコピー void copy(unsigned char *src, unsigned char *dst){ int i; for (i = 0; i 画像の作成 2015/11/25GPGPU実践基礎工学32  [3*WIDTH/16,9*WIDTH/16)の範囲を白(255),それ 以外を黒(0) void create(unsigned char *p){ int i, x_origin; for (i = 0; i 結果の画面表示とファイル保存 GPGPU実践基礎工学33  print関数が画面に各画素の座標と値を表示  画面出力のファイル表示(出力のリダイレクトを利用)  >を利用して保存,結果は表計算等で確認  $ コマンド > ファイル名 0 255 1 255 : 62 255 63 255 $ ./a.out > pic.txt $ cat pic.txt 0 255 1 255 : 62 255 63 255 2015/11/25 GPUへの移植の方針 GPGPU実践基礎工学  現画像の作成はCPUで行い,GPUへ転送  1スレッドが1画素の処理を実行  forループを排除し,配列添字とスレッド番号を対応  空間フィルタ  端の画素の処理ができないので今回は処理しない  モザイク  領域の大きさは1ブロックと同じとする  領域内の画素の平均値の計算は非常に難易度が高い  各ブロックにおけるthreadIdx.x=0のスレッドが平均を計算して処 理画像に書き込む 34 2015/11/25 メイン関数(GPU版) GPGPU実践基礎工学35 #include #include #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); 2015/11/25 imageproc1d.cu メイン関数(GPU版) GPGPU実践基礎工学36 //ここで空間フィルタのカーネルを宣言 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); 2015/11/25 imageproc1d.cu メイン関数(GPU版) GPGPU実践基礎工学37 return 0; } //原画像の生成 void create(unsigned char *p){ int i, x_origin; for (i = 0; i ネガティブ処理 2015/11/25GPGPU実践基礎工学38  1スレッドが1画素の値を計算  ベクトル和のようにスレッド番号と配列添字の対応を計算 画 素 値 横位置 画 素 値 原画像p[] 処理画像 filtered[] ・・・0 1 2 3スレッド番号 void negative(unsigned char *p, unsigned char *filtered){ int i; for (i = 0; i __global__ void negative(unsigned char *p, unsigned char *filtered){ int i; i = blockIdx.x*blockDim.x + threadIdx.x; //スレッド数と配列添字の対応 filtered[i] = WHITE ‐ p[i]; } ネガティブ処理 GPGPU実践基礎工学40 2015/11/25 imageproc1d.cu 水平反転 2015/11/25GPGPU実践基礎工学41  1スレッドが1画素の値を反転  ベクトル和のようにスレッド番号と配列添字の対応を計算  書込先が変わる 画 素 値 横位置 画 素 値 原画像p[] 処理画像 filtered[] 横位置35の画素値を28番目の画素値に 0 1 2 3 ・・・スレッド番号 void hreflect(unsigned char *p, unsigned char *filtered){ int i; for (i = 0; i __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]; } 水平反転 GPGPU実践基礎工学43 2015/11/25 imageproc1d.cu 空間フィルタ 2015/11/25GPGPU実践基礎工学44  1スレッドが1画素の値を計算  ある位置iの画素だけではなく左右(i‐1,i+1)の画素も参照  全スレッドが同じフィルタカーネルを参照 画 素 値 横位置 画 素 値 ぼかしフィルタカーネル 原画像p[] 処理画像 filtered[] 0 1 2 3スレッド番号 1/3 1/3 1/3 ・・・ void boxfilter (unsigned char *p, unsigned char *filtered, float *filter){ int i, result=BLACK; for (i = 1; i __global__ void boxfilter (unsigned char *p, unsigned char *filtered, float *filter){ int i, result = BLACK; i = blockIdx.x*blockDim.x + threadIdx.x; if(0 モザイク処理 2015/11/25GPGPU実践基礎工学47  1ブロック内の1スレッドのみが処理  1スレッドがブロック内の全画素の平均を計算  平均の画素値を処理画像に書込 画 素 値 横位置 原画像p[] 処理画像 filtered[] 画 素 値 スレッド番号 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 ・・・ void mosaic(unsigned char *p,unsigned char *filtered,int mosaicSize){ int i,isub,average; for (i = 0; i __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 ネガティブ処理 2015/11/25GPGPU実践基礎工学50  色の反転処理  黒を白,白を黒に反転 原画像 処理画像 画像反転 2015/11/25GPGPU実践基礎工学51  画像の幾何的位置を反転  画像(画素)の配列添字を変えて出力 原画像 処理画像 空間フィルタ 2015/11/25GPGPU実践基礎工学52  ある画素とその周囲の画素を使って処理  処理の仕方を規定したカーネルを定義  カーネルを変更することで様々な処理が可能 原画像 輪郭抽出 原画像 輪郭抽出 フィルタ処理 2015/11/25GPGPU実践基礎工学53  2次元的な処理  あるピクセルの周囲情報を利用して効果を計算  画像を矩形領域で分割した2次元的な並列処理が自然 0 1 0 1 ‐4 1 0 1 0 = b+d‐4e+f+h フィルタ (カーネル) a b c d e f g h i モザイク処理 2015/11/25GPGPU実践基礎工学54  画像を小さな領域に分け,その領域を全て同じ色に置換  領域内のある1画素の値か,領域内の画素の平均値にするこ とが多い 原画像 処理画像 2次元配列 GPGPU実践基礎工学55  変数名の後ろに[]を2個付けて宣言  1次元配列はベクトル,2次元配列は行列や画像 unsigned char picture[Nx][Ny]; vector[Nx] Nx=8 i Nx=8 j i matrix[Nx][Ny] Ny =8 2015/11/25 2次元配列の1次元配列表現 2015/11/25GPGPU実践基礎工学56  mallocやcudaMallocは1次元配列を宣言  2次元配列を宣言できない  1次元配列を宣言し,2次元配列的に読み書き  アドレス空間は1次元  2次元配列でも1次元のアドレスで表現  1次元目が連続か,2次元目が連続かが言語によって異なる j i j i 1次元目が連続 matrix[Nx][Ny] matrix[Nx][Ny] 2次元目が連続 2次元配列の1次元配列表現 2015/11/25GPGPU実践基礎工学57  C言語は2次元目が連続  2次元配列の場合  1次元配列の場合 picutre[1][2] = 11 picture[_______] = 11 Nx=8 1*8 + 2 picture[i][j]  picture[i*Ny+j] j i 1 2 3 4 5 9 10 11 12 6 7 8 picture[Nx][Ny] Ny =8 ベクトル和C=A+B(2次元版) 2015/11/25GPGPU実践基礎工学58  1次元のベクトル和とほぼ同じ  並列化が容易  for文の書く順番で実行速度が変化 c[][] a[][] b[][] #define Nx (1024) #define Ny (1024) void init(float a[Nx][Ny], float b[Nx][Ny], float c[Nx][Ny]){ int i,j; //iとjのループを入れ替えるとどうなるか? for(i=0;i #include #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 GPUによる2次元的な並列処理 2015/11/25GPGPU実践基礎工学61  ブロックとスレッドを2次元的に設定  1スレッドが配列の1要素(画像の1ピクセル)を処理  スレッドを2次元的に配置してブロックを構成 GPUの並列化の階層  グリッド-ブロック-スレッドの3階層  各階層の情報を参照できる変数  x,y,zをメンバにもつdim3型構造体  グリッド(Grid)  gridDim グリッド内にあるブロックの数  ブロック(Block)  blockIdx ブロックに割り当てられた番号  blockDim ブロック内にあるスレッドの数  スレッド(Thread)  threadIdx スレッドに割り当てられた番号 GPGPU実践基礎工学62 2015/11/25 Hello Threads(2次元版)  の中にどのように数字を書くか 2015/11/25GPGPU実践基礎工学63 #include __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 Hello Threads(2次元版)  1次元と同様等として実行  実行結果(画面出力) 2015/11/25GPGPU実践基礎工学64 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 並列度の指定が できていない 2次元的な並列度の指定 2015/11/25GPGPU実践基礎工学65  の中にどのように数字を書くか  1次元の場合は数字を書くことができた  2次元,3次元は数字を並べて書くことができない  dim3型構造体を利用 int main(void){ dim3 block(2,4,1), thread(4,2,1); hello(); hello(...); } dim3型変数block,  threadを利用 ・・・ あるいは直接dim3型として記述・・・ Hello Threads(2次元版)  実行結果(画面出力) 2015/11/25GPGPU実践基礎工学66 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 : 対応 対応 各スレッドが異なるi,jを参照するには  CUDAでは並列化に階層がある  全体の領域(グリッド)をブロックに分割  ブロックの中をスレッドに分割 ブロックの数 1ブロックあたりの スレッドの数 x方向ブロック数×1ブロックあたりのスレッドの数 ×y方向ブロック数×1ブロックあたりのスレッドの数=総スレッド数 GPGPU実践基礎工学67 2015/11/25 総スレッド数=2×4×4×2=64 各スレッドが異なるi,jを参照するには 2015/11/25GPGPU実践基礎工学68  Nx=8, Ny=8, x,y方向スレッド数4,ブロック数2  gridDim.x=2, gridDim.y=2  blockDim.x=4,blockDim.y=4 (0,0)(1,0)(2,0)(3,0)(0,0) (3,3) (3,3) (0,1)(1,1)(2,1)(3,1) (0,2)(1,2)(2,2)(3,2) (0,3)(1,3)(2,3)(3,3) (3,3) (0,0) (0,0) threadIdx.x,threadIdx.y i= 0 1 2 3 4 5 6 7 j= 0 1 2 3 4 5 6 7 ・・・ ・ ・ ・ ・・・ ・ ・ ・ ・・・ ・ ・ ・ (0,0)(1,0)(2,0)(3,0)(0,0) (3,3) (3,3) (0,1)(1,1)(2,1)(3,1) (0,2)(1,2)(2,2)(3,2) (0,3)(1,3)(2,3)(3,3) (3,3) (0,0) (0,0) 各スレッドが異なるi,jを参照するには 2015/11/25GPGPU実践基礎工学69  Nx=8, Ny=8, x,y方向スレッド数4,ブロック数2 blockDim.x=4 bl oc kD im .y =4 threadIdx.x,threadIdx.y blockIdx.x=0 blockIdx.x=1 bl oc kI dx .y =0 bl oc kI dx .y =1 gridDim.x=2 gr id Di m. y= 2 各スレッドが異なるi,jを参照するには 2015/11/25GPGPU実践基礎工学70  Nx=8, Ny=8, x,y方向スレッド数4,ブロック数2  i = blockIdx.x*blockDim.x + threadIdx.x  j = blockIdx.y*blockDim.y + threadIdx.y (0,0)(1,0)(2,0)(3,0)(0,0) (3,3) (3,3) (0,1)(1,1)(2,1)(3,1) (0,2)(1,2)(2,2)(3,2) (0,3)(1,3)(2,3)(3,3) (3,3) (0,0) (0,0) block(0,0) block(1,0) block(0,1) block(1,1) i= 0 1 2 3 4 5 6 7 j= 0 1 2 3 4 5 6 7 threadIdx.x,threadIdx.y #include #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 #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) __global__ void init(float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; int j = blockIdx.y*blockDim.y + threadIdx.y; int ij = i*Ny + j; a[ij] = 1.0; b[ij] = 2.0; c[ij] = 0.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; int ij = i*Ny + j; c[ij] = a[ij] + b[ij]; } int main(void){ float *a,*b,*c; dim3 thread(NTx, NTy, 1); dim3  block(NBx, NBy, 1); cudaMalloc( (void **)&a, Nbytes); cudaMalloc( (void **)&b, Nbytes); cudaMalloc( (void **)&c, Nbytes); init(a,b,c); add(a,b,c); cudaFree(a); cudaFree(b); cudaFree(c); return 0; } ベクトル和(2次元並列版) 2015/11/25GPGPU実践基礎工学72 vectoradd2d.cu 実行結果 GPGPU実践基礎工学73  2次元版 カーネル スレッド数 実行時間[s] init 16×16 735 add 16×16 298 2015/11/25 # 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 ] 実行結果 GPGPU実践基礎工学74  1次元版(vectoradd.cu) カーネル スレッド数 実行時間[s] init 256(=16×16) 109 add 256(=16×16) 113 # 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 ] 2015/11/25 ベクトル和(2次元並列版)の実行結果 GPGPU実践基礎工学75  実行結果  1次元版と比較して実行時間がかかる  initで7倍  addで3倍  遅くなる要因は?  2次元は添字iだけでなくjも計算  添字の計算負荷は軽い  実行時の並列度の指定 2015/11/25 #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) __global__ void init(float *a, float *b, float *c){ int i = blockIdx.x*blockDim.x + threadIdx.x; int j = blockIdx.y*blockDim.y + threadIdx.y; int ij = i+ Nx*j; a[ij] = 1.0; b[ij] = 2.0; c[ij] = 0.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; int ij = i+ Nx*j; c[ij] = a[ij] + b[ij]; } int main(void){ float *a,*b,*c; dim3 thread(NTx, NTy, 1); dim3  block(NBx, NBy, 1); cudaMalloc( (void **)&a, Nbytes); cudaMalloc( (void **)&b, Nbytes); cudaMalloc( (void **)&c, Nbytes); init(a,b,c); add(a,b,c); cudaFree(a); cudaFree(b); cudaFree(c); return 0; } ベクトル和(2次元並列版) 2015/11/25GPGPU実践基礎工学76 vectoradd2d.cu 実行結果 GPGPU実践基礎工学77  2次元版 カーネル スレッド数 実行時間[s] init 16×16 230 add 16×16 153 # 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 ] 2015/11/25 2次元的な配列アクセスの優先方向(C言語) 2015/11/25GPGPU実践基礎工学78  2次元配列の場合 in[][],out[][] Nx Ny j i for(i=0;i 2次元的な配列アクセスの優先方向(C言語) 2015/11/25GPGPU実践基礎工学79  2次元配列の場合 for(i=0;i 2次元的な配列アクセスの優先方向(C言語) 2015/11/25GPGPU実践基礎工学80  2次元配列の1次元配列的表現 for(i=0;i 2次元的な配列アクセスの優先方向 2015/11/25GPGPU実践基礎工学81  CUDAで2次元的に並列化してアクセスする場合 i = blockIdx.x*blockDim.x + threadIdx.x; j = blockIdx.y*blockDim.y + threadIdx.y; out[i+Nx*j]=in[i+Nx*j]; in[],out[] Nx Ny j i for(i=0;i 2次元的な配列アクセスの優先方向 GPGPU実践基礎工学82  C言語  メモリアドレスは2次元目が連続  その関係を維持するように配列を1次元化  CUDA C  メモリアクセスの際はthreadIdx.xが連続なアドレスにアク セスする方が高速  CUDA Cで2次元的な処理を行うとき  1次元配列は1次元目のメモリアドレスが連続となるように扱っ た方がよい 2015/11/25 2次元配列の1次元表現(C言語) 2015/11/25GPGPU実践基礎工学83  C言語は2次元目が連続  その関係を維持するように1次元化  2次元配列の場合  1次元配列の場合 picutre[1][2] = 11 picture[_______] = 11 Nx=8 1*8 + 2 picture[i][j]  picture[i*Ny+j] j i 1 2 3 4 5 9 10 11 12 6 7 8 picture[Nx*Ny] Ny =8 メ モ リ が 連 続 な 方 向 2次元配列の1次元表現(CUDA C) 2015/11/25GPGPU実践基礎工学84  メモリアクセスの際はthreadIdx.xが連続なアドレスに アクセスする方が高速  2次元配列の場合  1次元配列の場合 picutre[1][2] = 11 Nx=8 j i 1 2 3 4 5 9 10 11 12 6 7 8 picture[Nx*Ny] Ny =8 2次元配列の1次元表現(CUDA C) 2015/11/25GPGPU実践基礎工学85  threadIdx.xが連続なアドレスにアクセスする方がメモ リアクセスが高速  2次元配列の場合  1次元配列の場合 picutre[1][2] = 11 picture[_______] = 111 + 8*2 picture[i][j]  picture[i+Nx*j] 1 2 3 4 5 9 10 11 12 6 7 8 block(1,0) th re ad Id x. y threadIdx.x block(0,0) block(1,1)block(0,1) メモリが連続な方向 想定されるカーネル  1スレッドが2次元配列の1要素を計算  添字 i,j の要素を担当 2015/11/25GPGPU実践基礎工学86 #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] = ...; } 画像処理の流れ GPGPU実践基礎工学87  変数を宣言し,mallocでメモリを確保  画像を格納する変数 p  処理後の画像を格納する変数 filtered  画像pを作成  pをfilteredにコピーしておく  処理を実行  関数を作成し,pとfilteredを渡す  filteredの内容を画面に出力 2015/11/25 メイン関数(CPU版) GPGPU実践基礎工学88 #include #include #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に格納 2015/11/25 imageproc.c メイン関数(CPU版) GPGPU実践基礎工学89 //画面に各画素の値を表示 print(filtered); return 0; } //画像の内容をコピー void copy(unsigned char *src, unsigned char *dst){ int i; for(i=0;i 画像の作成 GPGPU実践基礎工学90 void create(unsigned char *p){ int i,j; int x_origin,y_origin; for(j=0;j 結果の画面表示とファイル保存 GPGPU実践基礎工学91  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 2015/11/25 gnuplotによる結果の表示 GPGPU実践基礎工学92  2次元,3次元データをプロットするアプリケーション  コマンドラインで命令を実行してグラフを描画  関数の描画,ファイルから読み込んだデータの表示が可能  tesla??では正しく動作しないため,grouseで実行する こと 2015/11/25 gnuplotによる結果の表示 2015/11/25GPGPU実践基礎工学93  表示可能なファイルフォーマット  データはスペース区切り  3次元表示では列(または行)の区切りとして空白行が必要 0 0 255 1 0 255 ... 126 0 255 127 0 255 gnuplotによる結果の表示 2015/11/25GPGPU実践基礎工学94  実行する命令 #表示設定(画像っぽく表示するための設定) 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 画像処理 GPGPU実践基礎工学  比較的簡単な4種類の処理を実装  ネガティブ処理  画像反転  水平反転,垂直反転  空間フィルタ  ぼかし,輪郭抽出  モザイク処理 95 2015/11/25 ネガティブ処理 2015/11/25GPGPU実践基礎工学96  色の反転処理  黒(0)を白(255),白(255)を黒(0)に反転  (255−各画素の値)を新しい画素の値とする 原画像 処理画像 void negative(unsigned char *p, unsigned char *filtered){ int i,j; for(j=0;j 水平反転 2015/11/25GPGPU実践基礎工学98  画像の幾何的位置を反転  水平反転 i→(WIDTH‐1)‐i  垂直反転 j→(HEIGHT‐1)‐j  原画像 処理画像 j HE IG HT ‐1 i0 WIDTH‐1 垂直反転 2015/11/25GPGPU実践基礎工学99  画像の幾何的位置を反転  水平反転 i→(WIDTH‐1)‐i  垂直反転 j→(HEIGHT‐1)‐j  原画像 処理画像 j HE IG HT ‐1 i0 WIDTH‐1 void hreflect(unsigned char *p, unsigned char *reflected){ int i,j,iref; for(j=0;j void vreflect(unsigned char *p, unsigned char *reflected){ int i,j,jref; for(j=0;j 空間フィルタ 2015/11/25GPGPU実践基礎工学102  ある画素とその周囲の画素を使って処理  処理の仕方を規定したカーネルを定義  カーネルは1次元配列で表現 原画像 輪郭抽出 0 1 0 1 ‐4 1 0 1 0 = b+d‐4e+f+h フィルタ (カーネル) a b c d e f g h i 空間フィルタ GPGPU実践基礎工学103  カーネルは1次元配列で表現し,引数で渡す  ぼかし(平均フィルタ)  輪郭抽出(ラプラシアンフィルタ) 1/9 1/9 1/9 1/9 1/9 1/9 1/9 1/9 1/9 0 1 0 1 ‐4 1 0 1 0 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}; float laplacian[9] ={ 0.0f, 1.0f, 0.0f, 1.0f,‐4.0f, 1.0f, 0.0f, 1.0f, 0.0f}; 2015/11/25 void boxfilter(unsigned char *p, unsigned char *filtered, float *filter){ int i,j int result = BLACK; //端では何もしない for(j=1;j モザイク処理 2015/11/25GPGPU実践基礎工学105  画像を小さな領域に分け,その領域を全て同じ色にする  領域内の全画素を,領域内の画素の平均値に置き換える 原画像 処理画像 void mosaic(unsigned char *f,unsigned char *filtered, int mosaicSize){ int i,j, isub,jsub; int average; for(j=0;j GPUへの移植の方針 GPGPU実践基礎工学  画像の作成はCPUで行い,GPUへ転送  1スレッドが1画素の処理を実行  2重のforループを排除し,配列添字とスレッド番号を対応  空間フィルタ  端の画素の処理ができないので今回は処理しない  モザイク  領域の大きさ(横×縦)は1ブロックと同じとする  領域内の画素の平均値の計算は非常に難易度が高い  threadIdx.x=0,threadIdx.y=0のスレッドが平均を計算してメモ リに書き出す 107 2015/11/25 メイン関数(GPU版) GPGPU実践基礎工学108 #include #include #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); 2015/11/25 imageproc.cu メイン関数(GPU版) GPGPU実践基礎工学109 //ここで空間フィルタのカーネルを宣言 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; } 2015/11/25 imageproc.cu メイン関数(GPU版) GPGPU実践基礎工学110 void create(unsigned char *p){ int i,j, x_origin,y_origin; for(j=0;j __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]; } ネガティブ処理 GPGPU実践基礎工学111 2015/11/25 imageproc.cu __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]; } 水平反転 GPGPU実践基礎工学112 2015/11/25 imageproc.cu __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]; } 垂直反転 GPGPU実践基礎工学113 2015/11/25 imageproc.cu __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 __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 処理時間の比較 GPGPU実践基礎工学116  画像サイズ 4096×4096  1ブロックあたりのスレッド数 16×16  モザイクのサイズはスレッド数と同じ(16×16) 処理 処理時間[ms] CPU GPU ネガティブ処理 175 1.17 水平反転 187 1.18 垂直反転 185 1.18 空間フィルタ 553 4.13 モザイク処理 260 38.5 補足 ビットマップ画像のフォーマット ビットマップ画像 2015/11/25GPGPU実践基礎工学119  画像を画素(pixel)の集合として捉え,各画素の色を3 原色(赤緑青,RGB)で表す  1画素あたり1バイト~4バイト  jpg画像のように画像圧縮を施さないのでデータ量が画 像サイズに比例して増大  圧縮形式もあるがあまり利用されない  zip形式等のファイル圧縮を使うと効果的に圧縮可能 ビットマップファイルフォーマット 2015/11/25GPGPU実践基礎工学120  ファイルの情報を表すヘッダ+画像の各画素情報  ヘッダ  ファイルヘッダ+情報ヘッダの合計54バイト  ファイルヘッダ  ファイルの種類,ファイルサイズ等  情報ヘッダ  画像の幅,高さ,1画素あたりのデータサイズなど  画像の各画素の情報 ビットマップファイルフォーマット 2015/11/25GPGPU実践基礎工学121  24bitビットマップファイルのバイナリダンプ ファイルヘッダ 情報ヘッダ 画像情報 1画素の情報 ビットマップファイルフォーマット 2015/11/25GPGPU実践基礎工学122  ファイルヘッダ  エンディアン(endian, 多バイトデータの記述・格納形式) 内容 サイズ 備考 ファイルの種類 u2 ビットマップの場合はBM ファイルサイズ(byte) u4 16進数表記(リトルエンディアン,下位 ビットが先に書かれる) 予約領域1 u2 必ず0 予約領域2 u2 必ず0 ファイル先頭から画像までの オフセット u4 windowsビットマップでは54 (16進数表記で36) 36 00 03 00 リトルエンディアン 00 03 00 3616=196,662 byte ビッグエンディアン 36 00 03 0016=905,970,432 byte ビットマップファイルフォーマット 2015/11/25GPGPU実践基礎工学123  情報ヘッダ 内容 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の場合もある ビットマップファイルフォーマット 2015/11/25GPGPU実践基礎工学124  画像情報(24bitビットマップ)  3原色の情報をそれぞれ1バイト(unsigned char)で保持  3原色の並び順はBGR  幅は行単位で4バイトの倍数に揃えられる  画素の並びは左下から右方向,下から上方向 B G R


Comments

Copyright © 2024 UPDOCS Inc.