2011. 1. 7.
2011. 1.25. 表の「オブジェクト作成」欄で、 「->」→「=」
石立 喬

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

―――― 画像の縮小と拡大 ――――


 画像ソフトには、画像の拡大と縮小の機能が付いている。当初は、その仕組みをプログラムで実現し、紹介しようと思ったが、.NET Frameworkには便利な道具が揃っており、結局、それを試みるにとどまった。ただ、画像の縮小にあたって、4分の1や5分の1など非常に小さくする場合には、原画像の4x4や5x5の範囲の平均を採るのが比較的良い結果が得られ、これは既存メソッドがないため、プログラムで実現した。

概要
 画像の拡大は、原画像の既知のピクセル値を拡大して離散的に配置し、その隙間を補間によって埋める処理であり、画像の縮小は、 縮小後の各ピクセルが、原画像のどのピクセルに相当するかを調べ、その値を補間により求めることである。画像の補間手法については解説も多いので、説明は省略する。
 画像を画面に表示する単純なDrawImageや、Bitmapクラスのコンストラクタを使用しても、画像の拡大・縮小の処理ができるので、まず、これを試み、次に、.NET Frameworkに用意されているInterpolationMode(補間モード)を設定して、DrawImageで画像を拡大・縮小した結果と比較する。

単純なDrawImageとBitmapコンストラクタによる画像の拡大と縮小
 画像の拡大・縮小には、DrawImageを用いて画面に表示する際にサイズを変更する方法と、Bitmapコンストラクタでサイズを変更したBitmapクラスを生成し、そのオブジェクトを単純なDrawImageで表示する方法がある。画面表示ではなく、画像オブジェクトをDrawImageで得るためには、あらかじめ生成した画像オブジェクトに描画すれば良い。

  直接の画面表示 オブジェクト作成
DrawImageを使う場合  原画像をbmap_srcとし、widthとheightを
変更後の画像のサイズとして、
 Graphic^ g=e->Graphics;
 g->DrawImage(bmap_src,x,y,width,height);
により、変更された画像を画面に描画する。
 画像オブジェクトとして、bmap_dstを用意しておき、
 Bitmap^ bmap_dst=gcnew Bitmap(width,height);
 Graphics^ g1=Graphics::FromImage(bmap_dst);
 g1->DrawImage(bmap_src,0,0,width,height);
により、書き込む。
Bitmapコンストラクタ
を使う場合
直接の画面表示はできない。  原画像をbmap_srcとし、widthとheightを、変更後の画
像のサイズとして、
 Bitmap^ bmap_dst=gcnew Bitmap(bmap_src,width,height);
を使う。


補間モードを設定した上での、DrawImageによる画像の拡大と縮小
 GraphicsクラスのDrawImageメソッドを使用するに当たって、色々なプロパティを設定することができ、これを利用すると、より高度な画像の拡大・縮小ができる。
◎補間モード(InterpolationMode)の設定
  using namespace System::Drawing::Drawing2D;
で名前空間を設定した上で、gr(一例)をGraphicsオブジェクトとして取得した後、
  gr->InterpolationMode=InterpolationMode::NearestNeighbor;
などで、g1によるDrawImageの際の補間方法を決め、
  gr->DrawImage(bmap_src,x,y,width,height);
で描画する。ただし、bmap_srcは原画像、xとyは描画する座標位置、widthとheightは、拡大・縮小後の画像のサイズである。
◎補間モードのいろいろ
 DrawImageを実行するに先立って、GraphicsオブジェクトのInterpolationModeプロパティを設定することができる。
 InterpolationMode(補間モード)の主なものには、
   NearestNeighbor(最近傍) --- 最寄りのピクセルの値を、そのまま使用する。一番簡単だが、補間とは言えない。
   Bilinear(双一次) --- 縦横二つの方向に、一次式による線形補間を行う。濃度が平均化され、ボケが生じる。
   Bicubic(双三次) --- 縦横二つの方向に、三次式による補間を行う。濃度にオーバーシュートが生じ、変化が強調されることもある。
   HighQualityBilinear(高品質双一次) --- 処理前にエイリアス防止の高域カットフィルタを通してからBilinear処理を行う。
   HighQualityBicubic(高品質双三次) --- 処理前にエイリアス防止の高域カットフィルタを通してからBicubic処理を行う。
がある。
 原画像からの縮小率が高くなると、縮小後の画像で表現できる空間周波数を情報量が超えてしまうので、エイリアス(Alias、別名→別の姿に変わってしまう)によるアーティファクト(偽信号)が発生し、画質を著しく損なう。したがって、その防止のために、縮小処理に先立ってフィルタリング(Pre-filtering、高周波成分の除去)を行う必要がある。これを用いたのが、HighQualityBilinearとHighQualityBicubicで、縮小率の高い場合には必須である。
◎ピクセルオフセットモード(PixelOffsetMode)プロパティの設定
 補間モードにHighQualityBilinearやHighQualityBicubicを使用すると、事前に実行するフィルタリングの周辺効果のためか、画像上辺と左辺に白みがかった部分が現れる。これに対処するのがPixelOffsetModeである。
 PixelOffsetModeの主要なものには、
  HighSpeed
  HighQuality
  Half
がある。これらを比較したところ、HighSpeedは、ピクセルオフセットモードを設定しないDefaultと同じと見られ、改善されない。HighQualityとHalfは改善されていたが、 試みた例の場合に限っては、HighQualityとHalfの差は、ほとんどなかった。

画像の縮小プログラム
 画像の縮小は、それ程の縮小率でない場合と、原画像に比べて非常に小さくなるように縮小する場合とで異なる。後者では、エイリアスの現象が強く出るため、HighQualityBilinearまたはHighQualityBicubicの補間モードを必須とする(Bilinearは50%以下への縮小、Bicubicでは25%以下への縮小は推奨されない)。したがって、ここでは、まず最初に80%への縮小を紹介し、続いて20%に縮小する場合に分けて紹介する。

◎80%に縮小するプログラム
 この程度の縮小比率であれば、単純なDrawImageやBitmapコンストラクタで十分である、参考までに、NearestNeighborに補間モードを設定した結果も表示する。

名前空間を下記のように記述しておく
using namespace System::Drawing::Drawing2D;

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

  Graphics^ gr=e->Graphics;

  int X0=10,X1=340;
  int Y0=10,Y1=260;
  int WIDTH=320,HEIGHT=240;
  float RATIO=0.8f;

  //原画像をファイルから読み込む
  Bitmap^ bmap_src=gcnew Bitmap("sample1.JPG");
  //原画像を左上に表示する
  gr->DrawImage(bmap_src,X0,Y0);
  //DrawImageで縮小しながら右上に描画する
  gr->DrawImage(bmap_src,X1,Y0,(int)(WIDTH*RATIO),(int)(HEIGHT*RATIO));
  gr->DrawString("DrawImageによる縮小",Font,Brushes::Black,X1+50,Y0+(int)(HEIGHT*RATIO)+15);
  //Bitmapコンストラクタで縮小してから左下に描画する
  Bitmap^ bmap_shrink=gcnew Bitmap(bmap_src,(int)(WIDTH*RATIO),(int)(HEIGHT*RATIO));
  gr->DrawImage(bmap_shrink,X0,Y1);
  gr->DrawString("Bitmapによる縮小",Font,Brushes::Black,X0+50,Y1+(int)(HEIGHT*RATIO)+15);
  //補間モードNearestNeighborを使用して、DrawImageで縮小しながら右下に描画する
  gr->InterpolationMode=InterpolationMode::NearestNeighbor;
  gr->DrawImage(bmap_src,X1,Y1,(int)(WIDTH*RATIO),(int)(HEIGHT*RATIO));
  gr->DrawString("NearestNeighborによる縮小",Font,Brushes::Black,X1+50,Y1+(int)(HEIGHT*RATIO)+15);

}

◎画像の縮小(80%)の実行画面
 図1は上記のプログラムの結果で、左上は原画像、その他は原画像を80%に縮小した画像である。DrawImageによるものとBitmapによるものは、結果が同じと思われ、満足できる結果であるが、補間モードNearestNeighborによる縮小は、ジャギー(Jaggie)が目立つ。DrawImageやBitmapは、非常にスムーズで、意外に凝った処理をしているようである。


図1 画像縮小プログラム(80%)の実行結果


◎80%の縮小で、補間モードを変えた場合の結果
 補間モードのNearestNeighborが不十分であることが分かったので、BilinearとBicubicの補間モードを試してみた。図2は、その結果で、ジャギーはなく改善されているが、単純なDrawImageやBitmapコンストラクタを使ったものより優れているとも思われず、BilinearとBicubicの差は分からなかった。さらに高画質と言われるHighQualityBilinearやHighQualityBicubicについても調べたが、特に改善が認められなかった。これらは、この程度の縮小率では、必要ないと考えられる。

Bilinearによる縮小 Bicubicによる縮小

図2 より高度な補間方法による画像の縮小


◎20%に縮小するプログラム
 縮小率が高くなると(4分の1や5分の1に)、補間モードの違いが明確になると考え、単純なDrawImageやBitmapコンストラクタの他に、各種の補間モードでのDrawImageを試してみた。
 ただし、ここに示すプログラムは、補間モードのNearestNeighborと、既存のメソッドによらず、平均化による縮小結果とを比較したものである。その他の方法による縮小は、プログラムの一部を単純に書き換えるだけであるので、プログラムの紹介は省略する。

名前空間を下記のように記述しておく
using namespace System::Drawing::Drawing2D;

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

  Graphics^ gr=e->Graphics;

  int i,j,m,n;
  int r,g,b;

  int X0=10,X1=340;
  int Y0=10,Y1=260;
  int WIDTH=320,HEIGHT=240;
  int SIZE=5;
  int AREA=SIZE*SIZE;
  Color color1;

  //原画像をファイルから読み込む
  Bitmap^ bmap_src=gcnew Bitmap("sample1.JPG");
  //原画像を左上に表示する
  gr->DrawImage(bmap_src,X0,Y0);

  //------------- NearestNeighborによる縮小と表示 ---------------------------------
  //Form1に対すE描画のための補間モードを設定する
  gr->InterpolationMode=InterpolationMode::NearestNeighbor;
  //原画像を縮小して、縮小画像を右上に表示(描画)する
  gr->DrawImage(bmap_src,X1,Y0,WIDTH/SIZE,HEIGHT/SIZE); //DrawImageで画面に直接描画
  gr->DrawString("NearestNeighborによる縮小(左下に拡大)",Font,Brushes::Black,X1+72,Y0+20);
  //縮小画像bmap_shrinkを用意すEる
  Bitmap^ bmap_shrink=gcnew Bitmap(WIDTH/SIZE,HEIGHT/SIZE);
  Graphics^ gr1=Graphics::FromImage(bmap_shrink);
  //bmap_shrinkに対する描画のための補間モードを設定する
  gr1->InterpolationMode=InterpolationMode::NearestNeighbor;
  //原画像を縮小して、bmap_shrinkに描画する
  gr1->DrawImage(bmap_src,0,0,WIDTH/SIZE,HEIGHT/SIZE);
  //縮小画像bmap_shrinkを拡大してモザイク化する
  Bitmap^ bmap_mos=gcnew Bitmap(WIDTH,HEIGHT);
  for(j=0;j<HEIGHT;j+=SIZE)
    for(i=0;i<WIDTH;i+=SIZE){
      color1=bmap_shrink->GetPixel(i/SIZE,j/SIZE);
      for(n=0;n<SIZE;n++)
        for(m=0;m<SIZE;m++)
          bmap_mos->SetPixel(i+m,j+n,color1);
    }
  //モザイク化画像を左下に表示する
  gr->DrawImage(bmap_mos,X0,Y1);

  //------------- 平均化による縮小と表示 ---------------------------------
  //縮小画像bmap_shrink1を用意する
  Bitmap^ bmap_shrink1=gcnew Bitmap(WIDTH/SIZE,HEIGHT/SIZE);
  //原画像をSIZExSIZEに分割し、その範囲内で平均を採り、bmap_shrink1に描画する
  for(j=0;j<HEIGHT;j+=SIZE)
    for(i=0;i<WIDTH;i+=SIZE){
      r=0;g=0;b=0;
      for(n=0;n<SIZE;n++)
        for(m=0;m<SIZE;m++){
          color1=bmap_src->GetPixel(i+m,j+n);
          r+=color1.R;
          g+=color1.G;
          b+=color1.B;
        }
      r/=AREA;
      g/=AREA;
      b/=AREA;
      color1=Color::FromArgb(r,g,b);
      bmap_shrink1->SetPixel(i/SIZE,j/SIZE,color1);
    }

  //縮小画像bmap_shrink1を右上に表示する
  gr->DrawImage(bmap_shrink1,X1,Y0+60);
  gr->DrawString("平均化による縮小(右下に拡大)",Font,Brushes::Black,X1+72,Y0+80);

  //縮小画像bmap_shrink1を拡大してモザイク化する
  Bitmap^ bmap_mos1=gcnew Bitmap(WIDTH,HEIGHT);
  for(j=0;j<HEIGHT;j+=SIZE)
    for(i=0;i<WIDTH;i+=SIZE){
      color1=bmap_shrink1->GetPixel(i/SIZE,j/SIZE);
      for(n=0;n<SIZE;n++)
        for(m=0;m<SIZE;m++)
          bmap_mos1->SetPixel(i+m,j+n,color1);
  }
  //モザイク化画像を右下に表示する
  gr->DrawImage(bmap_mos1,X1,Y1);

}


◎画像の縮小(20%)の実行画面
 図3は上記のプログラムの結果で、左上は原画像、右上は5分の1に縮小した画像である。詳細が分かり難いので、左下と右下に拡大した画像を示してある。これによると、NearestNeighborによる縮小は、拡大してみるとブロックが目立ち、原画像をより分かり難くくし、平均化画像の方が原画像に近い。
 原画像を推測しにくくするのが目的のモザイク化としては、NearestNeighborの方が好ましいのかもしれない。 既存の補間モードを使わないで、プログラム作成によって得た、平均化による縮小は、簡単ながら意外と滑らかで、性能が良い。


図3 画像縮小プログラム(20%)の実行結果


◎20%に縮小する場合のDrawImageとBitmapの比較
 画像の縮小には、DrawImageでの描画の際にサイズを縮小する方法と、Bitmapコンストラクタでサイズを縮小する方法があることはすでに述べた。具体的なプログラムは省略するが、20%に縮小した場合の結果を図4に示す。いずれも、5分の1に縮小してから、縮小画像の1ピクセルを5x5ピクセルに割り当てて拡大してある。補間の設定は行っていない。
 結論として、補間モードを用いない単純なDrawImageによる方法と、Bitmapコンストラクタによる方法とは、画像を20%縮小する場合には、全く同等と言える。補間モードNearestNeighborによるものよりは、やや滑らかである。

単純(補間モードを設定なし)なDrawImage Bitmapコンストラクタ

図4 単純なDrawImageとBitmapコンストラクタによる縮小結果


◎20%に縮小に、より高度な補間モードを使用した場合
 各種補間モードおよびピクセルオフセットモードを試みた結果を図5に示す。。
 1)NearestNeighborは、補間モードを指定しないDrawImageやBitmapとほぼ同じ結果が得られた(比較のため再録)。
 2)BilinearとBicubicは、NearestNeighborよりは滑らかであるが、BilinearとBicubicの差は確認できない(Bilinearの結果を代表として示す)。
 3)HighQualityBilinearとHighQualityBicubicは、さらに滑らかさを増し、改善が見られた。HighQualityBilinearとHighQualityBicubicの区別はほとんどつかないが、HighQualityBicubicの方が、少し良いような気がする(HighQualityBicubicの結果を代表として示す)。
 4)HighQualityBilinearとHighQualityBicubicは、高域カットフィルタを使用するので、上辺と左辺に周辺現象が見られた。しかし、PixelOffsetModeを設定したら改善された(HalfとHighQualityとの違いは確認されなかった)。
       gr1->PixelOffsetMode=PixelOffsetMode::Half;
      gr1->InterpolationMode=InterpolationMode::HighQualityBicubic;
などのようにして使用する。

NearestNeighborを使用した場合(比較のため再録) Bilinear/Bicubic(両者の差はない)を使用した場合
最高品質と言われるHighQualityBicubicを使用した場合(HighQualityBilinearも結果はほぼ同じ)。原画像に近いが、上辺と左辺に少し不自然さがある。 HighQualityBicubicモードで、PixelOffsetModeをHalfにした場合
HighQualityBicubicモードで、PixelOffsetModeをHighQualityにした場合 ここで紹介した平均化方式(比較のため(再録)

図5 各種補間モード、ピクセルオフセットモードを使用した縮小の比較(既出画像を含む)


◎2倍に拡大するプログラム

名前空間を下記のように記述しておく
using namespace System::Drawing::Drawing2D;

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

  Graphics^ gr=e->Graphics;

  int X0=10,X1=340;
  int Y0=10,Y1=260;
  int WIDTH=160,HEIGHT=120;
  int RATIO=2;

  //原画像をファイルから読み込む
  Bitmap^ bmap_src=gcnew Bitmap("sample2.JPG");
  //原画像を左上に表示する
  gr->DrawImage(bmap_src,X0,Y0);
  gr->DrawString("右はDrawImageによる拡大",Font,Brushes::Black,X0,Y0+HEIGHT+25);
  gr->DrawString("左下はBitmapによる拡大",Font,Brushes::Black,X0,Y0+HEIGHT+45);
  gr->DrawString("右下はNearestNeighborによる拡大",Font,Brushes::Black,X0,Y0+HEIGHT+65);
  //DrawImageで拡大して描画する
  gr->DrawImage(bmap_src,X1,Y0,WIDTH*RATIO,HEIGHT*RATIO);
  //Bitmapコンストラクタで拡大して描画する
  Bitmap^ bmap_shrink=gcnew Bitmap(bmap_src,WIDTH*RATIO,HEIGHT*RATIO);
  gr->DrawImage(bmap_shrink,X0,Y1);
  //補間モードを使用する
  gr->InterpolationMode=InterpolationMode::NearestNeighbor;
  //原画像を拡大して、拡大画像を右下に表示(描画)する
  gr->DrawImage(bmap_src,X1,Y1,WIDTH*RATIO,HEIGHT*RATIO);

}

◎画像の拡大(2倍)の実行画面
 図6は上記のプログラムの結果で、左上は原画像、その他は、左上画面に表示の方法により2倍に拡大したそれぞれの画像である。単純なDrawImageと、Bitmapコンストラクタによる拡大は、ほぼ同等である。補間モードNearestNeighborを用いたものは、ジャギーが認められる。補間モードBininear、Bicubic、HighQualityBilinear、HighQualityBicubicなどの補間モードも試みたが、単純なDrawImage以上の結果は得られなかった。これは、原画像の品質が悪く、本来の性能が出なかったものとも考えられる。



図6 画像拡大プログラム(2倍)の実行結果


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