C言語で画像の拡大縮小(最近傍補間編)

このページでは、最近傍補間を用いた画像の拡大縮小法についての説明と、そのプログラムの例の紹介を行います。

最近傍補間による画像の拡大縮小

画像の拡大縮小には様々なアルゴリズムがあります。ここで説明するのは「最近傍補間法」です。最近傍補間法は「ニアレストネイバー補間法」とも呼ばれます。

下記記事で説明しているように拡大縮小では画素と画素の間の距離を変化させ、必要になる画素を補間する処理です。

画像の拡大縮小・リサイズの原理、アルゴリズムによる違いを解説!

この記事に記載の通り、横方向にH倍、縦方向にV倍拡大縮小する場合は、拡大縮小後の座標(X, Y)と元画像の座標(x, y)の関係は下記式で表すことができます。

x = 1 / H × X

y = 1 / V × Y

この計算により求められる座標(x, y)の画素が元画像に存在しない場合、つまり x や y が整数でない場合に補間処理を行って、存在しない画素を補って拡大縮小処理を行います。このページではこの補間処理を最近傍補間による補間で行います。

最近傍補間では、その追加した画素の輝度値を元画像の中で一番近い画素の輝度値とする方法で補間を行います。

補間したい画素の座標が (x, y) であるとき、その画素に一番近い画素の座標 (x’, y’) は下記で求めることができます。

x’ = (int)(x + 0.5) /* xはdouble型、x’はint型 */

y’ = (int)(y + 0.5) /* yはdouble型、y’はint型 */

double型をint型へキャストすれば小数点以下が切り捨てされます。ですので 0.5 を足してから int 型にキャストすれば小数点以下を四捨五入した結果が得られますので、x に一番近い整数x’、y に一番近い y’ を求める事ができます。

プログラム例

main.c
#include "myJpeg.h"

int main(int argc, char *argv[]){

  BITMAPDATA_t bitmap, scaledBitmap;
  int m, n, c;
  int m0, m1, n0, n1;
  double originalm, originaln;
  double dm, dn;
  double scaleW, scaleH;
  char outname[256];

  if(argc != 4){
    printf("ファイル名、幅方向拡大率、高さ方向拡大率の3つを引数に指定してください\n");
    return -1;
  }

  scaleW = atof(argv[2]);
  scaleH = atof(argv[3]);

  if(jpegFileReadDecode(&bitmap, argv[1]) == -1){
    printf("jpegFileReadDecode error\n");
    return -1;
  }

  /* ここから画像処理 */
  /* 最近傍彭法で画像を指定された倍率に拡大縮小 */
  scaledBitmap.width = scaleW * bitmap.width;
  scaledBitmap.height = scaleH * bitmap.height;
  scaledBitmap.ch = bitmap.ch;

  if(scaledBitmap.width == 0 || scaledBitmap.height == 0){
    printf("拡大縮小後の幅もしくは高さが0です\n");
    freeBitmapData(&bitmap);
    return -1;
  }

  scaledBitmap.data = (unsigned char*)malloc(sizeof(unsigned char) * scaledBitmap.width * scaledBitmap.height * scaledBitmap.ch);
  if(scaledBitmap.data == NULL){
    printf("malloc scaledBitmap error\n");
    freeBitmapData(&bitmap);
    return -1;
  }

  for(n = 0; n < scaledBitmap.height; n++){
    for(m = 0; m < scaledBitmap.width; m++){
      for(c = 0; c < scaledBitmap.ch; c++){

        originalm = (double)m / (double)scaleW;
        m0 = (int)originalm;
        dm = originalm - m0;
        m1 = m0 + 1;
        if(m1 == bitmap.width) m1 = bitmap.width - 1;

        originaln = (double)n / (double)scaleH;
        n0 = (int)originaln;
        dn = originaln - n0;
        n1 = n0 + 1;
        if(n1 == bitmap.height) n1 = bitmap.height - 1;

        scaledBitmap.data[scaledBitmap.ch * (m + n * scaledBitmap.width) + c]
          = bitmap.data[bitmap.ch * (m1 + n1 * bitmap.width) + c] * dm * dn
          + bitmap.data[bitmap.ch * (m1 + n0 * bitmap.width) + c] * dm * (1 - dn)
          + bitmap.data[bitmap.ch * (m0 + n1 * bitmap.width) + c] * (1- dm) * dn
          + bitmap.data[bitmap.ch * (m0 + n0 * bitmap.width) + c] * (1 -dm) * (1 - dn);
      }
    }
  }
  /* ここまで画像処理 */

  sprintf(outname, "%s", "linear.jpeg");

  if(jpegFileEncodeWrite(&scaledBitmap, outname) == -1){
    printf("jpegFileEncodeWrite error\n");
    freeBitmapData(&scaledBitmap);
    freeBitmapData(&bitmap);
    return -1;
  }

  freeBitmapData(&scaledBitmap);
  freeBitmapData(&bitmap);

  return 0;
}

スポンサーリンク

プログラムの説明

引数

引数として拡大縮小を行う対象とするファイルの名前、横方向の拡大縮小率、縦方向の拡大縮小率を渡すようにしています。

引数で渡された拡大縮小率は文字列のため、下記で浮動小数点型に変換しています。

  scaleW = atof(argv[2]);
  scaleH = atof(argv[3]);

入力JPEG画像の読み込みとデコード

下記で引数で渡されたファイル名のファイルを読み込み、さらにデコードした BITMAP形式の画像データの先頭アドレスをbitmap.dataポインタに指させています。

  if(jpegFileReadDecode(&bitmap, argv[1]) == -1){
    printf("jpegFileReadDecode error\n");
    return -1;
  }

この画像データを基に拡大縮小後の画素を補間していきます。

この関数は私の自作の関数で下記で説明していますので必要に応じて参照してください。

libJPEGのインストールとC言語での使用方法・使用例libJPEGのインストールとC言語での使用方法・使用例

拡大縮小後の画像データのメモリ領域確保

下記で元画像のサイズから拡大縮小後のサイズを計算しています。

  scaledBitmap.width = scaleW * bitmap.width;
  scaledBitmap.height = scaleH * bitmap.height;

さらに下記でそのサイズ×3バイト分(RGB)のメモリ領域を確保し、scaledBitmap.dataポインタにその領域の先頭アドレスを指させています。

  scaledBitmap.data = (unsigned char*)malloc(sizeof(unsigned char) * scaledBitmap.width * scaledBitmap.height * scaledBitmap.ch);

拡大縮小後の各画素の輝度値はこのポインタの先のメモリ領域に格納していきます。

拡大縮小処理

ここからいよいよ拡大縮小を行なっていきます。

拡大縮小後の全画素に対して補間処理を行なっていきますので、下記のループで繰り返し処理を行います。

  for(n = 0; n < scaledBitmap.height; n++){
    for(m = 0; m < scaledBitmap.width; m++){
      for(c = 0; c < scaledBitmap.ch; c++){

下記では拡大縮小後の(m, n)画素が拡大縮小前だとどの座標の画素にあたるかを計算しています。

        originalm = m / scaleW + 0.5;
        originaln = n / scaleH + 0.5;

m / scaleWとn / scaleHは整数になるとは限りません。整数でない場合、この画素は拡大縮小前の画像には存在しない画素となります。

最近傍補間ではこの画素の輝度値を一番近い画素の輝度値と同じだろうと推測して補間処理を行います。

このためには単純に座標を小数点未満四捨五入してやれば求められます。そのために上の処理では+0.5を行っています。それがoriginalm, originalnの型であるint型にキャストされるので、結果的にm / scaleWとm / scaleHに一番近い画素の座標が決まります。

拡大縮小前の座標が決まりましたので、これを拡大縮小後の座標の輝度値として設定します。

scaledBitmap.data[scaledBitmap.ch * (m + n * scaledBitmap.width) + c] = bitmap.data[bitmap.ch * (originalm + originaln * bitmap.width) + c];

あとはこれを拡大縮小後の全画素に対して実行すれば良いだけです。

拡大縮小後画像データのJPEGエンコードとファイル作成

拡大縮小後のBITMAP形式の画像データの情報と出力先ファイル名を指定して JPEG形式へのエンコードとファイル保存を行います。

  if(jpegFileEncodeWrite(&scaledBitmap, outname) == -1){
    printf("jpegFileEncodeWrite error\n");
    freeBitmapData(&bitmap);
    return -1;
  }

画像処理前後の画像

・画像処理前

・画像処理後(横幅方向を0.5倍・高さ方向を0.3倍に変倍した画像)

まとめ

最近傍補間法では、「追加したい画素の画素値」を「その画素に一番近い画素と同じ画素値」と見立てて画素値を追加していく補間法となります。一番簡単な補間法ですので、是非この原理は覚えておきましょう。

線形補間(バイリニア補間)については下のページで紹介していますので、興味があれば読んでみてください。

C言語で画像の拡大縮小(線形補間編)

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です