cvl-robot's diary

研究ノート メモメモ https://github.com/dotchang/

画像のn倍拡大

遠隔地に画像をネットワーク配信するときに、遅延を少なくフレームレートを上げたい場合、とりあえず画像サイズを小さくして送っておいて、受信側で高解像度化できたらいいな、ということがあります。ざっと画像拡大縮小アルゴリズムを調べてみましょう。

一般的な画像によく使われる拡大縮小アルゴリズム

  • Nearest neighbor(ニアレストネイバー)
  • Bilinear(バイリニア)
  • Bicubic(バイキュービック
  • Lanczos-3(ランチョス3)

任意の浮動小数点のスケール倍に拡大縮小するアルゴリズムです。これらはOpenCVのresize関数の補間アルゴリズムとしても実装されています。opencvではINTER_AREAという指定もできます。またLanczos-3ではなく4が実装されています。ただし、Lanczosアルゴリズムの結果の割には、ものすごい変な結果が返ってきますので、品質重視の場合は自分で実装しなおした方が無難です。

[1]では、このほかに、

等も紹介されていますが、オープンソースでは無いようなので使い道は限定的です。半導体組み込みIP向けにはDir8や他の物も知られているようです。

Lanczos-3が最もポピュラーなようですね。とくに縮小アルゴリズムとしては、品質が高いという評価のようです。

 

アイコンの拡大に使われるn倍アルゴリズム

  • Nearest neighbor
  • Microsoft Method
  • Hq4x (hqx)
  • Bicubic
  • EPX
  • SuperEagle
  • Super2xSaI
  • PhotoZoom Pro 4
  • Vector Magic
  • Live Trace

整数倍に拡大するアルゴリズムです。定数倍であることを利用して、テンプレートやルックアップテーブルを用意して高速化と品質向上する工夫がされています。古いゲームの高解像度化に使われているようです。Microsoftアルゴリズム[2]の結果が素晴らしいので試してみたいのですが、実装は公開されてなさそうですね。

2015年5月 DeepLearningを使ったこれが話題 

waifu2x

gigazine.net

これもすごい

github.com

 

hq4xを試してみる

[4]で配布されているhqx-1.1.tar.gzを持ってきます。

2倍、3倍、4倍アルゴリズムが提供されています。

srcの中のcommon.h,init.c, hq2x.c, hq3x.c, hq4x.c, hqx.hを自分のプロジェクトにコピーして持ってきます。 

hqx.hのdllimport宣言がコンパイルの邪魔になることがありますので、その場合は

#define HQX_API

// __declspec(dllimport)

無理やり空にしちゃいます。

使用時は最初に、ルックアップテーブルを作るためにinit.c内のhqxInit()関数を読んでおく必要があります。

入力画像フォーマットは、1ピクセルがBGRAの8bit4chで構成されたwidth×heightの画像です。オーダーはlittleendianです(Windowsはふつうlittleendian)。1ピクセルを32bitのuint32_tにcastして関数に渡します。

出力は、BGRAフォーマットのn(2~4)倍解像度の画像です。

OpenCVやPDFの色フォーマットはBGRの並びですが、多くの画像コンテナではRGBの順に並んでいることが多いので、関数を呼び出す前と後に多少整形してやる必要があります。

 

openframeworksのofImageの場合、ダミーコードはこんな感じ。

hqxInit();

ofImage img = ofxCv::toOf( mat ); // 適当な絵をopencvで読み込む

img.setImageType(OF_IMAGE_COLOR_ALPHA);

ofImage dst;

dst.allocate(img.getWidth()*2, img.getHeight()*2, OF_IMAGE_COLOR_ALPHA);

hq2x_32( (uint32_t*)img.getPixels(), (uint32_t *)dst.getPixels(), img.getWidth(), img.getHeight());

// BGRAをRGBAにひっくり返す

for(int i=0; i<height; i++){

  for(int j=0; j<width; j++){

    int b = img.getPixels()[(i*width+j)*4+0];

    int g = img.getPixels()[(i*width+j)*4+1];

    int r = img.getPixels()[(i*width+j)*4+2];

    int a = img.getPixels()[(i*width+j)*4+3];

    img.getPixels()[(i*width+j)*4+0] = b;

    img.getPixels()[(i*width+j)*4+0] = g;

    img.getPixels()[(i*width+j)*4+0] = r;

    img.getPixels()[(i*width+j)*4+0] = a;

  }

}

dst.saveImage("file.png", 100);

 

lanczosを試してみる

[6]のページのjavac++, openFrameworks向けにそのまま書き直してみます。

#define _USE_MATH_DEFINES

#include <math.h>

void lanczos(ofImage& image, ofImage& image2, int n, float sc)
{
  //int n = 2; // N値
  int nx = n-1;

#ifdef _OPENMP
#pragma omp parallel for
#endif
  for (int y = 0; y < image2.height; y++) {
    for (int x = 0; x < image2.width; x++) {
      double x0 = x/sc;
      double y0 = y/sc;

      int xBase = (int)x0;
      int yBase = (int)y0;

      int color = 0;

      // ランツォシュの処理範囲
      if (xBase >= nx && xBase < image.width - n && yBase >= nx && yBase < image.height - n) {
        double *color_element = new double[3];
        memset(color_element, 0, sizeof(double)*3);

        double w_total = 0.0;

        // 周辺(a*2)^2画素を取得して処理
#ifdef _OPENMP
#pragma omp parallel for
#endif
        for (int i = -nx; i <= n; i++) {
           for (int j = -nx; j <= n; j++) {
             int xCurrent = xBase + i;
             int yCurrent = yBase + j;

             // 距離決定
             double distX = abs( (double)xCurrent - x0);
             double distY = abs( (double)yCurrent - y0);

             // 重み付け
             double weight = 0.0;

             if (distX == 0.0) {
               weight = 1.0;
              } else if (distX < n) {
                double dPIx = M_PI*distX;
                weight = (sin(dPIx)*sin(dPIx/(double)n))/(dPIx*(dPIx/(double)n));
              } else {
                continue;
              }

              if (distY == 0.0) {
                ;
              } else if (distY < n) {
                double dPIy = M_PI*distY;
                weight *= (sin(dPIy)*sin(dPIy/(double)n))/(dPIy*(dPIy/(double)n));
               } else {
                 continue;
               }

               // 画素取得
               ofColor color_process = image.getColor(xCurrent, yCurrent);
               color_element[0] += color_process.r * weight;
               color_element[1] += color_process.g * weight;
               color_element[2] += color_process.b * weight;

               w_total += weight;
            }
         }

#ifdef _OPENMP
#pragma omp parallel for
#endif
        for (int i = 0; i < 3; i++) {
           if (w_total != 0) color_element[i] /= w_total;
           color_element[i] = (color_element[i] > 255) ? 255
             : (color_element[i] < 0) ? 0
             : color_element[i];
           color += (int)color_element[i] << i*8;
         }
         delete [] color_element;
      }

      ofColor c( (color>>0*8) & 0xff, (color>>1*8) & 0xff, (color>>2*8) & 0xff);
      image2.setColor(x, y, c);
    }
  }
}

  

[1] Loggia Logic: PSNRによる画像拡大アルゴリズム7種の画質評価結果

[2] Depixelizing Pixel Art

[3] Scale2x

[4] hqx - hqx is a fast, high-quality magnification filter designed for pixel art. - Google Project Hosting

[5] hqx - Wikipedia, the free encyclopedia

[6] 画像処理 - HexeRein