ピクセル色成分の平均化による拡大縮小

前回は画像の横方向の縮小のみを試してみましたが、同様の処理を縦方向にも行うとビットマップの縮小処理に、また拡大縮小率lfRateを1より大きくすると拡大処理になります。今回は、縦方向の処理を加えて任意の比率で画像を拡大縮小できる関数を作ってみましょう。

拡大縮小処理

前回は、縮小のみ(つまりピクセル列は必ず小さくなる)だったので縮小結果をピクセル列に上書きし、DIBのヘッダを調整する、という形で処理しましたが、今回は拡大も入るので拡大・縮小後の大きさでDIBを作り直すようにしましょう。そして、処理前の元画像はlpOPixに入れておきます。

  /* 作業用バッファを確保し、元画像をコピー */
  lpOPix=(LPBYTE)GlobalAlloc(GPTR,dwLength*dwHeight);
  CopyMemory(lpOPix,lpPixel,dwLength*dwHeight);

  dwWid2=(DWORD)(dwWidth*lfXRate); /* 縮小後の横幅計算 */
  dwHei2=(DWORD)(dwHeight*lfYRate); /* 縮小後の横幅計算 */

  if ((dwWid2*3) % 4==0) /* バッファの1ラインの長さを計算 */
      dwLen2=dwWid2*3;
  else
      dwLen2=dwWid2*3+(4-(dwWid2*3) % 4);

  /* 現在の画像を破棄し、拡大縮小後のサイズでビットマップを作り直す */
  GlobalFree(lpDIB);
  lpDIB=(LPBYTE)GlobalAlloc(GPTR,sizeof(BITMAPINFO)+dwLen2*dwHei2);
  lpbiInfo=(LPBITMAPINFO)lpDIB;
  lpPixel=lpDIB+sizeof(BITMAPINFO);
  lpbiInfo->bmiHeader.biSize=sizeof(BITMAPINFOHEADER);
  lpbiInfo->bmiHeader.biPlanes=1;
  lpbiInfo->bmiHeader.biBitCount=24;
  lpbiInfo->bmiHeader.biCompression=BI_RGB;

 これで、元画像のピクセル列はlpOPixに保存されたので拡大縮小の結果を新たに確保したピクセル列lpPixelに書き出して行く事になります。また、元画像のサイズやバッファの横幅はdwWidth, dwHeight, dwLengthに、拡大縮小後の大きさはdwWid2, dwHei2, dwLen2に記録されているので、この値を参照してピクセル列のアドレスを特定して行きましょう。

 では、早速縦方向も考慮した拡大縮小の処理方法を考えてみます。前回は、横方向のみの重さをかけ縦方向は常に1としていたので、今回は縦方向についても元画像の対応ピクセルを計算しその重みをかけていく、という処理をします。
 まず、Y座標iに対応する元画像のY座標の開始点dwSYと最後の点dwEYは、X方向と同様に以下の式で求まります。

  dwSY=(DWORD)floor(i*(1/lfYRate));
  dwEY=(DWORD)ceil((i+1)*(1/lfYRate))-1;

 そして、元画像のあるピクセルが占める重みはX方向とY方向の重みの積なので、ある点の重みをX、Yそれぞれに求めてかければ良いわけです。元画像(l, k)のX方向の重みlfXWは、前回の式そのままですから、

  if (l==dwSX) /* 最初のピクセル */
      lfXW=(l+1)*lfXRate-floor(l*lfXRate);
  else if (l==dwEX) /* 最後のピクセル */
      lfXW=(j+1)-(l*lfXRate);
  else /* 間のピクセル */
      lfXW=lfXRate;

 ですね。また、Y方向の重みlfYWもまったく同様に

  if (k==dwSY) /* 最初のピクセル */
      lfYW=(k+1)*lfYRate-floor(k*lfYRate);
  else if (k==dwEY) /* 最後のピクセル */
      lfYW=(i+1)-(k*lfYRate);
  else /* 間のピクセル */
      lfYW=lfYRate;

 となります。後は、各ピクセルにこの重みをかけて合計し合計結果を重みの合計で割れば良いので、拡大縮小結果のあるピクセル(j, i)に対する処理は以下のようになるでしょう。

  /* (j,i)に対応する元画像のピクセルを計算 */
  dwSX=(DWORD)floor(j*(1/lfXRate));
  dwEX=(DWORD)ceil((j+1)*(1/lfXRate))-1;
  dwSY=(DWORD)floor(i*(1/lfYRate));
  dwEY=(DWORD)ceil((i+1)*(1/lfYRate))-1;

  lfR=0;
  lfG=0;
  lfB=0;
  lfSum=0;

  for (k=dwSY;k<dwEY+1;k++)
      for (l=dwSX;l<dwEX+1;l++) { /* 含まれるピクセルの成分を合計 */

          if (l==dwSX) /* 最初のピクセル */
              lfXW=(l+1)*lfXRate-floor(l*lfXRate);
          else if (l==dwEX) /* 最後のピクセル */
              lfXW=(j+1)-(l*lfXRate);
          else /* 間のピクセル */
              lfXW=lfXRate;

          if (k==dwSY) /* 最初のピクセル */
              lfYW=(k+1)*lfYRate-floor(k*lfYRate);
          else if (k==dwEY) /* 最後のピクセル */
              lfYW=(i+1)-(k*lfYRate);
          else /* 間のピクセル */
              lfYW=lfYRate;

          lfB+=lpOPix[l*3+k*dwLength]*lfXW*lfYW;
          lfG+=lpOPix[l*3+k*dwLength+1]*lfXW*lfYW;
          lfR+=lpOPix[l*3+k*dwLength+2]*lfXW*lfYW;

          lfSum+=lfXW*lfYW;

      }

  /* 縮小後ピクセルの成分を計算 */
  lpPixel[j*3+i*dwLen2]=(BYTE)(lfB/lfSum);
  lpPixel[j*3+i*dwLen2+1]=(BYTE)(lfG/lfSum);
  lpPixel[j*3+i*dwLen2+2]=(BYTE)(lfR/lfSum);

 後は、この処理を全ピクセルの色成分に行うだけですね。

 処理が終わって拡大縮小結果のピクセル列ができたら、大きさを保持する変数dwWidth, dwHeight, dwLengthを更新して元画像を入れておいたバッファを開放します。

  /* DIBのヘッダ・サイズ変数調整 */
  lpbiInfo->bmiHeader.biWidth=(DWORD)(dwWidth*lfXRate);
  dwWidth=lpbiInfo->bmiHeader.biWidth;
  dwLength=dwLen2;
  lpbiInfo->bmiHeader.biHeight=(DWORD)(dwHeight*lfYRate);
  dwHeight=lpbiInfo->bmiHeader.biHeight;

  GlobalFree(lpOPix);

プログラム

 ウインドウに適当な24ビットBMPをドラッグ&ドロップしてからトラックバーで拡大・縮小率を設定し、拡大縮小ボタンをクリックしてください。

プログラムソース表示


プログラミング資料庫 > 画像処理実験室