2011. 2. 2.
石立 喬

Visual C++ 2010 Express を用いた易しい画像処理(10)

―――― 画像の回転と拡大 ――――


 画像処理で残っていた話題で、画像の回転がある。画像の回転は、GraphicsクラスのRotateTransformメソッドが使える。「Visual C++ 2005の易しい画像処理(17)」では、自作プログラムによる回転のアルゴリズムを紹介したが、今回は、既存ライブラリのメソッドを積極的に使用した。また、従来は、GUIを利用して、より実用的な機能を持たせていたが、ここではプログラムの基本的な部分のみに止めた。

画像の回転と拡大の必要性
 ほとんどの画像ソフトには、90°や180°の回転機能が付いているが、微細な角度の回転は含まれていないものが多い。カメラなどで撮影した画像は、水平が傾いている場合があり、これを、画像を見ながら任意の角度だけ回転できると便利である。画像を回転すると、以前と同様なサイズでは、画像の欠如する部分が生じるので、画像を自動的に拡大して、それを防止する。

画像の回転と拡大に便利なGraphicsクラスのメソッド
 Graphicsクラスには、画像のアフィン変換(Affine Transform、平行移動や回転、拡大・縮小など)のメソッドがある。これは、Graphicsオブジェクトの変換マトリックスに対する操作であり、代表的なものを以下に示す。
  ・RotateTransform(float angle) ---- 角度angleだけ、時計方向に回転する
  ・TranslateTransform(float dx,float dy) --- X方向にdx、Y方向にdyだけ移動する
  ・ScaleTransform(float sx,float sy) --- X方向にsx、Y方向にsyの倍率で拡大・縮小を行う
 これらの操作は、カスケードに(縦続的に)実行することができるが、その場合には、RotateTransform(angle,MatrixOrder::Append)のように、必ず引数にMatrixOrder::Appendを追加する。プログラム上で後から記述して、その操作を先に行いたい場合には、明示的にMatrixOrder::Prependを使用すると良い。MatrixOrdeを引数として与えないデフォルトでは、MatrixOrder::Prependになっている模様で、後から記述した命令の方が先に実行される。

画像の回転の結果と説明
 プログラムの説明に先立って、最初にプログラムの実行結果を示す。図1で、左上は原画像で、右上は、RotateTransform(angle)を実行した結果である。回転の中心が、画像の中央ではなく、画像の原点(左上)のため、希望する結果とは異なるものになっている。
 左下は、TransferTransform(-WIDTH/2,-HEIGHT/2)を用いて、一旦、画像の中央を原点に移動させたのち、RotateTransform(angle,MatrixOrder::Append)で回転させ、さらにTransferTransform(WIDTH/2,HEIGHT/2,MatrixOrder::Append)で画像の中央を元に戻した画像で、回転後の画像の中央と、回転禅の画像の中央が一致している。しかし、周辺に欠けた部分が存在する。
 そこで、欠けた部分が無くなるように、「画像の拡大率の計算方法」(後述)で求めた倍率ratioを掛けて拡大したのが、右下の画像である。
 すなわち、ここで述べる画像の回転には、便利なRotateTransform(angle)の他に、前後に実施する画像の移動や拡大が必要である。


図1 拡大の必要性を示す実行画面


画像の拡大率の計算方法
 図2は、画像の回転に使用する拡大率を求める方法を示す。図2の左の図で、黒は回転画像として最終的に欲しい画像の範囲、赤は、拡大しないで回転した原画像を示す。原画像を、画像の中央を固定してangleだけ回転すると(図の場合は時計方向に)、回転後の画像に、グレイで塗りつぶした、原画像データのない部分が生じる。緑色の線で示した範囲は、原画像の形状と相似であり、かつデータが完全に存在する範囲である。したがって、緑色の範囲を黒の範囲に拡大する操作を行なえば、欠陥部分を無くすることができる。
 拡大率の計算は、図2の右の図から求める。右の図は、左の図の一部分を取り出したもので、左の図の赤い画像(すなわち回転した原画像)の対角線の長さの半分を1とし、緑の画像範囲(すなわち、欠陥部分が無く元の画像と相似形)の対角線の半分をxとしている。このxを求めて、逆数を取れば必要な拡大率が得られる。xは1より小さい値であり、回転画像は、原画像の、より狭い範囲を、元のサイズに対応させるので、結局、1/xの倍率で拡大することになる。
 回転角angleには、正(時計方向)と負(反時計方向)があるが、図2の左の図から想像できるように、左右を反転しただけであるので、式の形は変わらない。したがって、実際の計算ではangleの絶対値を採れば良い。
 結局、ratio=cos(α-|angle|)/cosαによって求めたratioを使って、
 gb->ScaleTransform(ratio,MatrixOrder::Append);
で画像を拡大する。

図2 画像の拡大率の求め方


画像の補間
 すでに、「易しい画像処理(6)」で述べたように、GraphicsオブジェクトのInterpolationModeプロパティを使用する。このため、名前空間として、System::Drawing::Drawing2D を記述し、InterpolationMode::Bicubicを使用する。

プログラムの説明
1)原画像のサイズ(WIDTH,HEIGHT)から、縦横比で決まる角度ALPHAを計算しておく。
2)回転角は、RotateTransformに対しては度(°)で与えるのでangleを用い、その他の計算にはラジアンを用いるので、ラジアン化したangle_rを用意する。
3)回転角angle_rとALPHAから、必要な拡大率ratioを計算する。
4)以下により回転を実施する(必要なMatrixOrder::Appendの引数は、説明では省略)。
  ・書き込むべきbitmap_dstを用意する。
  ・bmap_dstに対するGraphicsクラスのオブジェクトgbを設定する。
  ・gbに対して、InterpolationModeを設定する。
  ・TranslateTransform(-WIDTH/2,-HEIGHT/2)で、原画像の中央を原点に移動する。
  下記二つの命令は、原理的には順序を逆にしても良い(補間の精度では、このままの順の方が良いかもしれない)。
    ・ScaleTransform(ratio,ratio)で拡大する。
    ・RotateTransform(angle)で回転する。
  ・TranslateTransform(WIDTH/2,HEIGHT/2)で、原画像の中央を元に戻す。
  ・DrawImageでbmap_dstに書き込む。
5)bitmap_dstを表示する。

プログラム
 InterpolationModeプロパティを使用するために、下記のように名前空間を追加しておく
using namespace System::Drawing::Drawing2D;

private: System::Void Form1_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e) {

  Graphics^ g=e->Graphics;

  int WIDTH=320,HEIGHT=240;                //画像のサイズ
  float ALPHA=(float)Math::Atan((double)WIDTH/HEIGHT);  //画像の対角線の角度(0.9273ラジアン)

  int X0=10,Y0=10;
  int X1=340,Y1=260;

  float angle=2.0f;                              //回転角(度)
  float angle_r=angle*(float)Math::PI/180.0f;                 //回転角(ラジアン)
  float ratio=(float)(Math::Cos(ALPHA-Math::Abs(angle_r))/Math::Cos(ALPHA));//拡大率(倍)

  //原画像を読み込む
  Bitmap^ bmap_src=gcnew Bitmap("sample.PNG");
  //原画像を左上に表示する
  g->DrawImage(bmap_src,X0,Y0);

  //単なる回転のみ(原点を中心に回転)
  Bitmap^ bmap_dst1=gcnew Bitmap(WIDTH,HEIGHT);
  Graphics^ gb1=Graphics::FromImage(bmap_dst1);
  gb1->InterpolationMode=InterpolationMode::Bicubic;  //内挿モードを設定
  gb1->RotateTransform(angle);             //回転させる
  gb1->DrawImage(bmap_src,0,0,WIDTH,HEIGHT);      //書き込む
  g->DrawImage(bmap_dst1,X1,Y0);            //回転画像を右上に表示する

  //画像の中央を中心に回転させる、拡大はしない
  Bitmap^ bmap_dst2=gcnew Bitmap(WIDTH,HEIGHT);
  Graphics^ gb2=Graphics::FromImage(bmap_dst2);
  gb2->InterpolationMode=InterpolationMode::Bicubic;  //内挿モードを設定
  
gb2->TranslateTransform(-WIDTH/2,-HEIGHT/2);     //画像の中央を原点に移動
  gb2->RotateTransform(angle,MatrixOrder::Append);   //回転させる
  gb2->TranslateTransform(WIDTH/2,HEIGHT/2,MatrixOrder::Append); //画像の中央を元に戻す
  gb2->DrawImage(bmap_src,0,0,WIDTH,HEIGHT);      //書き込む
  g->DrawImage(bmap_dst2,X0,Y1);            //回転画像を左下に表示する

  //画像の中央を中心に拡大して回転する
  Bitmap^ bmap_dst3=gcnew Bitmap(WIDTH,HEIGHT);
  Graphics^ gb3=Graphics::FromImage(bmap_dst3);
  gb3->InterpolationMode=InterpolationMode::Bicubic;  //内挿モードを設定
  gb3->TranslateTransform(-WIDTH/2,-HEIGHT/2);     //画像の中央を原点に移動
  gb3->ScaleTransform(ratio,ratio,MatrixOrder::Append);//縦横共にratio倍に拡大する
  gb3->RotateTransform(angle,MatrixOrder::Append);   //回転させる
  gb3->TranslateTransform(WIDTH/2,HEIGHT/2,MatrixOrder::Append); //画像の中央を元に戻す
  gb3->DrawImage(bmap_src,0,0,WIDTH,HEIGHT);      //書き込む
  g->DrawImage(bmap_dst3,X1,Y1);            //回転画像を右下に表示する

}

より実用的な実行結果
 実際に使用する機会が多いのは、ごく僅かな角度の回転である。図3では、試行の結果、回転角を2°に設定した。本来ならば、画像を見ながら、スライダーを動かすなどによって回転させるのが便利であろうが、ここでは省略した。
 なお、念のために、回転する向きを変えて(時計方向から反時計方向に)試してみたが、全く問題はなく、回転角ratioを負で与えるだけで良い。


図3 実用的な場面での実行画面


Matrixクラスを使用する方法

 Graphicsクラスのメソッドを直接使用する方法については、本文で紹介した。Matrixクラス(名前空間として、System::Drawing::Drawing2D が必要)にも、画像のアフィン変換に便利なメソッドがある。その中の主なものを下記に示す。
  ・Rotate(float angle) ---- 角度angleだけ、時計方向に回転する
  ・RotateAt(float angle,PointF point) ---- 点pointを中心に角度angleだけ、時計方向に回転する(Graphicsクラスにはない)
  ・Translate(float dx,float dy) --- X方向にdx、Y方向にdyだけ画像を移動する
  ・Scale(float sx,float sy) --- X方向にsx、Y方向にsyの倍率で拡大・縮小を行う
 これらの操作は、カスケードに実行することができるが、その場合には、Graphicsクラスの場合と同様に、引数にMatrixOrderを追加する。
 このようにして設定したMatrixクラスを、Graphicsで使用するには、下記の手順を採る(一例)。
  Matrix^ mx=gcnew Matrix();
  mx->RotateAt(angle,PointF(-WIDTH/2,-HEIGHT/2));
  gb->Transform=mx;
 すなわち、Matrixクラスのオブジェクトmxをコンストラクタで生成し、それに指定した点(-WIDTH/2,-HEIGHT/2)を中心にangleだけ回転するマトリックスを設定し(最初の場合はMatrixOrderは不要)、それを、すでに取得してあるGraphicsオブジェクトgbのプロパティに渡す。
 RotateAt()のように、一見便利そうなMatrixクラスであるが、Matrixクラスのオブジェクト生成、Graphicsクラスのプロパティの設定などの手間と、ここで使用したかったScaleAt()がない不便さを考えると、必ずしも推奨できない。


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