2016. 5. 2.
石立 喬

OpenMPとVisual C++による並列処理(8)

――― 補間処理を含む画像の回転では、OpenMPの効果がある -----

 画像の回転は、画像の全ピクセル位置に対して座標変換演算を行う。一般には、得られた対応する座標位置が実数のため、近傍周辺データから補間により目的ののデータを得る必要がある。これは処理量の多い演算であり、OpenMPによる並列化に向いていると考え、試してみた。

画像の回転とその並列化
 画像の回転と、それに伴う拡大は、詳細にコード化したものを「Visual C++ 2005 Express Edition を用いた易しい画像処理(17)」(現在は削除)で取り上げ、.NET FrameworkのGraphicsクラスのメソッドを用いたものを「Visual C++ 2010 Expressを用いた易しい画像処理(10)」で紹介している。画像の回転の式、画像の拡大の式については、「CodeZine:画像を小刻みに回転して写真の傾きを補正する」(http://codezine.jp/article/detail/398)に詳細に述べてあるので参照されたい。OpenMPによる並列化を試みるには、詳細にコード化する必要があり、念のために、処理方法を詳しく紹介する。
◎画像の回転
  画像の回転とは、回転後の画像の座標位置から回転前の原画像の座標位置を逆算し、その場所の色情報を回転後の画像の座標位置の色とすることである。図1で、太い黒で表したのは回転後の画像の座標である(回転後の画像の座標に対して、原画像が曲がっていると考える)。回転後の座標(画像の中心から測った)で見たP点の位置を(x y)とし、回転前の座標(赤で表示)で見た位置を(xx, yy)とすると、図中の式で示すような関係が得られる。図では、回転前の画像から見て時計方向に角度θだけ画像を回転させた場合を示しているが、θがマイナスになっても、式はそのまま使用できる。x、yは整数であるが、xx、yyは一般に実数である。画像の中心を軸として回転するには、座標の原点を(WIDTH/2, HEIGHT/2)にする変換が必要である。


図1 画像を回転させるための式(O点は画像の中心に選ぶ)


◎画像の拡大
 画像を回転させて、回転前と同じ大きさで画像を表示しようとすると、画像データの欠ける部分が生じる。図2の左の図で、黒は回転後の画像の範囲、赤は回転前の原画像を示し(求める回転後の画像に対して、原画像が、傾斜していると考える)、ここでは、原画像も回転後の画像も同じサイズで(拡大しないで)示してある。グレイ色は画像データが欠如する部分である。緑色で示した部分は、原画像の形状と相似であり、かつデータが欠如していない範囲である。 したがって、緑色の範囲を黒の範囲に拡大する操作を行なえば、欠如部分を無くすことができる。
 拡大倍率の計算は、下の右の図を参照して欲しい。右の図は、左の図の一部分を取り出したもので、左の図の赤い画像(すなわち回転前の画像)の対角線の長さの半分を1とし、緑の画像範囲(すなわち、欠如部分が無く元の画像と相似形)の対角線の半分をXとしている。このXを求めて、逆数を取れば拡大倍率が得られる。Xは1より小さい値であり、回転画像は、原画像の、より狭い範囲を、元のサイズに対応させるので、結局、1/Xの倍率で拡大することになる。
 回転角θには、正(時計方向)と負(反時計方向)があるが、図の左の図から想像できるように、左右を反転しただけであるので、式の形は変わらない。したがって、実際の計算ではθの絶対値を取れば良い。
 zoomは1より大きい拡大すべき比率であるが、プログラムの中では、対応する原画像の座標位置をzoomで割ることにより、原画像の、より狭い部分を射影して拡大の効果を得ている。


図2 拡大すべき倍率zoomを求める方法


画像の回転と拡大、および座標位置の移動を含む最終的な座標変換の式
 以上の結果から、最終的に使用する座標変換の式は、下記の通りになる。画像の中心を軸として回転させるための座標移動が含まれている。WIDTH2とHEIGHT2は、画像の中心点を表す定数である(高速化のために、WIDTH/2とHEIGHT/2を定数化)。画像を拡大するには、原画像の座標に換算する際に拡大倍率zoomで割る。ALPHAは、画像の縦横比で決まる定数(単位はラジアン)である。
 拡大倍率 zoom を計算する
  zoom = cos(ALPHA - abs(theta)) / cos(ALPHA)
 左上隅を原点とする回転後の画像の座標値 i および j を、画像の中心を原点とする座標値 x および y に変換する。
  x = i - WIDTH2
  y = j - HEIGHT2
 上記座標に持ってくるべき回転前の原画像の座標値(画像の中心を原点とする) xx および yy を求める。
  xx = xcos(theta) + ysin(theta)) / zoom
  yy = ((ycos(theta) - xsin(theta)) ./ zoom
 画像の中心を原点とする、原画像の座標値 xx および yy を、左上隅を原点とする座標値 ii および jj に変換する。
  ii = xx + WIDTH2
  jj = yy + HEIGHT2

画像データの補間
 画像回転の式により、回転後の画像の座標(i, j)から、逆算して原画像の座標(ii, jj)を求めると、ii、jjは一般には整数とならない。図の左は、実数座標点(ii, jj)を囲む4個の整数座標点を示したもので、i0はiiをintにキャストした整数、j0はjjをintにキャストした整数である。
 図の右は周辺4座標点のR成分画像データからP点のR成分値を補間する計算式を導出するためのものである。図で、di = ii - i0、dj = jj -j0であり、それぞれ1未満の実数値である。 rAは原画像の点A(i0, j0)のR成分の値であり、rBは点B(i0 +1, j0)のR成分の値などである。これらの値を比例配分で求めたのが補間値rPである。


図3 周辺の座標値(左)と補間の計算方法(右)


作成したプログラム
 「・・・による並列処理(4)」を基礎にしている。図4に示すように、プロジェクトの最初の部分で、定数を宣言し、ユーザ関数のプロトタイプを宣言している。
 実行時間測定用の関数をUserProgramから呼び出す方法を図5に示す。実行時間を確認するには、補間を含まない、回転と拡大のみのrotateImage関数と、補間を含むrotateImageInter関数を用意し、従来と同様に、これらの関数に要する時間を測定する。回転角を外部で設定(233行)してから、これらの関数を呼び出す。


図4 プロジェクトの最初の部分でユーザ関数を宣言する



図5 回転角を指定して、関数を呼び出す


補間をしないrotateImage関数
 この関数は、補間をしないので、高速ではあるが、回転後の画像にギザギザが生じる。回転角をthetaとして受け取り、画像の回転と拡大の式を実行する。OpenMPを使用しないVC++デフォルトの場合は、#pragma文をコメントアウトする。


図6 rotateImage関数の内容


補間を実施するrotateImageInter関数
 この関数は補間処理を含むので、実行時間がかかるが、回転後の画像はスムーズになる。求めたP(ii, jj)から、基準点となる座標点(i0, j0)をint型へのキャストで求め、4個の座標値(一次元表示のpointA〜pointD)を得る。これらの4点のRGB値を求め、補間式に入れる。結果は、RGBマクロでCOLORREF型化し、回転後の画像のピクセルデータ配列pdata1に入れる。



図7 rotateImageInter関数の内容


rotateImage関数とrotateImageInter関数の実行結果
 実用時に使用される可能性の大きいRelease構成について、OpenMPを使用した結果の例を図8と図9に示す。図8は補間を行わないrotateImage関数の結果で、高速ではあるが、回転後の画像にギザギザが生じている。図9は補間処理を含むrotateImageInter関数の結果で、時間がかかるが、回転後の画像はスムーズである。
 Debug構成の場合やVC++デフォルトの場合については、表1を参照されたい。


図8 Release構成のrotateImage関数で得られた結果の一例、補間をしてないのでギザギザが目立つ



図9 Release構成のrotateImageInter関数で得られた結果の一例、補間を実施しているためスムーズ


表1 色々な場合の実行時間測定結果



結 論
 画像の回転の実行時間を、補間なしの関数と補間ありの関数を使って調べた。実際に使われるであろうと思われる、Release構成の補間ありでは、OpenMPを用いると、VC++デフォルトの場合に比べて、実行時間が45%に、ほぼ半減した。
 補間なしに比べて、補間ありは処理時間が長いので、OpenMPによる効果が出ている。





「Visual C++の勉強部屋」(目次)