数行のC++コードでCUDAの画像処理を行う

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も備える
```C++:Warp.hの132行目以降
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を個人的に学ぶために作成（ご自由に使ってください）