CUDA関数が使いづらいので、簡単に使える部品を作った
・コード全体は以下の通り
https://github.com/rueiwoqpqpwoeiru/image-process-CUDA
・利用にはlibtiffが必要(libtiffをソースからビルドする方法は以下に書いた)
https://qiita.com/rueiwoqpqpwoeiru/items/e0eda6752ac259484288
・ビルドしたバイナリを引数無しで実行すると、サンプルコードが実行され、
入力画像(test_img.tif)に対して以下の各種処理を適用した画像が出力される
・tifのαチャンネルを利用するため、画像の表示にはphotoshop等が必要
・数行のC++コードとは(※以下のサンプルコードはmain.cuから呼ばれる)
・画像変形(拡縮、回転、射影変換)→ Warp.hの132行目
・フィルタ(畳込み(いまはGaussianのみ)、メジアン、
morphology(膨張、収縮、Opening、Closing))→ Filter.hの409行目
・高速フーリエ変換(FFT)を用いた畳込み(いまはGaussianのみ)→ Fft.hの189行目
・基本的な演算(四則演算、絶対値、べき乗、閾値、クリップ)、型変換
→ Basic.hの221行目
・統計量算出(平均値、標準偏差、最大値、最小値)→ Stat.hの66行目
・共通する設計思想を「Warp.h」を例に説明
・入力画像と出力画像の型を、テンプレート引数として与える(141行目)
・画像は多チャンネルで保持
・142行目では入力の2倍サイズの出力領域を10チャンネル確保
・画像処理パラメータも多チャンネルで保持
・143行目では10チャンネル確保(※出力のチャンネル数と異なっていても構わない)
・145行目から154行目では、チャンネルごとにパラメータを設定
(例)154行目では、9チャンネルに、射影変換のパラメータ(四隅の座標)を設定
・実行時は、入力画像のチャンネル、出力画像のチャンネル、パラメータのチャンネルを指定
(例)165行目では、入力が2チャンネル、出力が9チャンネル、パラメータが9チャンネル
・出力画像やパラメータへのアクセスは、119行目のget_out()、120行目のget_param()
・処理後の画像を、次の処理の入力として与える例は、Basic.hの246、247行目を参照
・デバグのために、121行目のimwriteや、124行目のparam_to_csvも備える
void unit_test_Warp(int argc, char* argv[]) { // 132行目
const std::string in_file = argv[1];
const std::string out_dir = argv[2];
// input img
D_mem<uint16> in_img;
in_img.imread(in_file);
// ++++++++++++++++
// test for warp
// ++++++++++++++++
Warped_img<uint16, uint16> img_warp; // 141行目
img_warp.alloc_out(in_img.d_data.w * 2, in_img.d_data.h * 2, 10); // 142行目
img_warp.alloc_param(10); // 143行目
// set param
img_warp.set_resize(0.4, 0.8, 0); // 145行目
img_warp.set_resize(0.8, 0.4, 1);
img_warp.set_resize(1.0, 1.0, 2);
img_warp.set_resize(1.4, 1.8, 3);
img_warp.set_resize(1.8, 1.4, 4);
img_warp.set_rot(in_img.d_data.w / 2, in_img.d_data.h / 2, 30, 5); // 150行目
img_warp.set_rot(in_img.d_data.w / 2, in_img.d_data.h / 2, 60, 6);
img_warp.set_rot(in_img.d_data.w / 2, in_img.d_data.h / 2, -30, 7);
img_warp.set_rot(in_img.d_data.w / 2, in_img.d_data.h / 2, -60, 8);
img_warp.set_perspective(122, 74, 422, 122, 367, 258, 144, 295, 9); // 154行目
// do process
img_warp.do_proc(in_img, 2, 0, 0);
img_warp.do_proc(in_img, 2, 1, 1);
img_warp.do_proc(in_img, 2, 2, 2);
img_warp.do_proc(in_img, 2, 3, 3);
img_warp.do_proc(in_img, 2, 4, 4);
img_warp.do_proc(in_img, 2, 5, 5);
img_warp.do_proc(in_img, 2, 6, 6);
img_warp.do_proc(in_img, 2, 7, 7);
img_warp.do_proc(in_img, 2, 8, 8);
img_warp.do_proc(in_img, 2, 9, 9); // 165行目(入力が2ch、出力が9ch、パラメータが9ch)
// output
img_warp.imwrite(out_dir + "/out_03_warp.tif", PHOTOMETRIC_MINISBLACK);
img_warp.param_to_csv(out_dir + "/out_03_warp.csv");
}
・Warp.hの使い方
・使用するクラスは「Warped_img」のみ(141行目)
・拡縮: 横の倍率と、縦の倍率を指定し、チャンネルも指定(145~149行目)
・回転: 回転の中心座標と、回転角を指定し、チャンネルも指定(150~153行目)
・射影変換: 変換前の四隅座標を左上から右回りで指定し、チャンネルも指定(154行目)
・Filter.hの使い方
・使用するクラスは「Filtered_img」のみ(418行目、439行目、461行目)
・その上にあるクラスは「Filtered_img」クラスの部品で、単体でも使える
・新しい機能を作る際は「Conv_img」クラスをコピペして修正するのがお勧め
・出力領域を確保する際に、conv、median、dilate、erode、open、closeを引数として
与えることで、実行時の動作が変わる(419行目、440行目、462行目)
・フィルタ: フィルタサイズと、フィルタ数を指定(420行目)
Gaussianのσと、チャンネルを指定(422~426行目)
・メジアン: フィルタ数を指定(441行目)
フィルタサイズを指定(443~447行目)
※455行目はミスだが正常に動く(get_out()を介しても画像出力ができる)
※NPP関数を使用しており、初回は遅い
・morphology: フィルタサイズと、フィルタ数を指定(463行目)
構造的要素(値が1の矩形)のサイズを指定(465~469行目)
・Fft.hの使い方
・使用するクラスは「Filtered_img_fft」のみ(198行目)
・使い方は前述のFilter.hのフィルタとほぼ同じで、ほぼ同じ結果が得られる
・高速化効果は、フィルタサイズが大きいときに絶大
・1920x1080の画像に、65x65のフィルタを適用した際、Filter.hでは154ミリ秒
Fft.hでは3.5ミリ秒であった
・Basic.hの使い方
・使用するクラスは、Cast_imgと、Op_2imgと、Op_1img(※これらは動作確認が一部未実施)
・Cast_img(43行目)は型変換
・入力画像と出力画像の型は、テンプレート引数で指定
・Cast_imgだけ例外的に、全チャンネルを同時に処理
(Filter.hの250行目と254行目は非効率だが、処理時間が短いため問題ない)
・Op_2img(72行目)は、2枚の画像の、和、差、要素ごとの積
・演算の種類は、do_proc関数の引数(mode)で指定(78行目)
・Op_1img(122行目)は、1枚の画像と、スカラー値との演算(など)
・演算の種類や、入力値は、do_proc関数の引数で指定(128行目)
・add、sub、mulは、定数の加算、減算、乗算(引数で指定するスカラー値は1つ)
・abs、pow、divは、各画像の絶対値、べき乗、逆数(スカラー値は指定しない)
・thは閾値処理(引数で指定するスカラー値は1つ)※出力の型によって出力値が変わる
・clipはクリッピング(引数で指定するスカラー値は2つ)
・246行目では、元画像(in_img)を0.8倍した画像と、0.2倍した画像を足し合わせるため、
結果が格納されるop_2imgの0チャンネルは、元画像(in_img)と同じになるはず
・Stat.hの使い方
・使用するクラスは、Op_statのみ
・do_procの引数としてmean, stdev, max, minを与えることで動作が変わる
※NPP関数を使用しており、初回は遅い
・その他
・上記コードにおけるメモリ管理については以下に記載
https://qiita.com/rueiwoqpqpwoeiru/items/d85ce72631b6352999dc
・CUDAカーネルをC++クラスのメンバ関数に実装する方法は以下に記載
https://qiita.com/rueiwoqpqpwoeiru/items/7de81ac35441676fbe64
・高速化の工夫については以下に記載
https://qiita.com/rueiwoqpqpwoeiru/items/203ba0b090180bd118e3
なお、上記コードは、CUDAを個人的に学ぶために作成(ご自由に使ってください)