LoginSignup
0
2

More than 1 year has passed since last update.

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

Last updated at Posted at 2022-08-14

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も備える

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を個人的に学ぶために作成(ご自由に使ってください)

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2