双三次補間法 – AS3.0

前回の画像のリサイズ – AS3.0で、バイキュービック補間の名前を出しておきながら、例を出してなかったので補足しておきます。

双三次補間法。(参考:内挿 (Wikipedia)
Photoshop等の多くのグラフィックソフトではバイキュービック(Bi-Cubic)補間と表現されています。
これはバイリニア(線形)補間よりも高精度な補間法で、16近傍の画素値から三次関数を用いて補間します。
用いる式はsinc関数(矩形関数をフーリエ変換したときに得られる)の近似式になります。

これをテイラー展開により三次の項で近似し補間式として用います。

aは補間関数の性質を制御するための変数(-0.5?-2程度が用いられる)
この手法は標本化(サンプリング)定理に基づいて中間部分を補間するものと言えます。

ActionScript3.0での例は以下より。
参照する座標の決定処理部分はflashrodさんのエントリーを参考にするといいです。
参考:AS3で画像処理入門(その4) – flashrod

/* 実数座標を渡します。
(切り捨てる部分も厳密には補間法の理論の中に入っているので) */

private function interpolate(imgData:BitmapData, x1:Number, y1:Number):int {
  var px:Number = Math.floor(x1);
  var py:Number = Math.floor(y1);
  var adr:int = 0;
  var r:int = 0;
  var g:int = 0;
  var b:int = 0;
  var val:uint = 0;
  var bufR:Vector.<int> = new Vector.<int>(16);  //16近傍の画素値
  var bufG:Vector.<int> = new Vector.<int>(16);
  var bufB:Vector.<int> = new Vector.<int>(16);
  
  for(var ly:int=0;ly<4;ly++){
    adr = ly*4;
    for(var lx:int=0;lx<4;lx++){
      val = imgData.getPixel(px+lx-1, py+ly-1);
      bufR[adr] = (val >> 16) & 0xff;
      bufG[adr] = (val >> 8) & 0xff;
      bufB[adr] = val & 0xff;
      adr++;
    }
  }
  r = biCubic(x1, y1, bufR);
  g = biCubic(x1, y1, bufG);
  b = biCubic(x1, y1, bufB);
  return (r << 16) | (g << 8) | b;
}

private function biCubic(x1:Number, y1:Number, buf:Vector.<int>):int{
  var fx:Vector.<Number> = new Vector.<Number>(4);
  var fy:Vector.<Number> = new Vector.<Number>(4);
  var tmp:Vector.<Number> = new Vector.<Number>(4);
  var mat:Vector.<Number> = new Vector.<Number>(4);
  var dx:Number = x1 - Math.floor(x1);
  var dy:Number = y1 - Math.floor(y1);
  var res:int = 0;
  
  fx[0] = cubicFunc(1.0+dx);
  fx[1] = cubicFunc(    dx);
  fx[2] = cubicFunc(1.0-dx);
  fx[3] = cubicFunc(2.0-dx);
  
  fy[0] = cubicFunc(1.0+dy);
  fy[1] = cubicFunc(    dy);
  fy[2] = cubicFunc(1.0-dy);
  fy[3] = cubicFunc(2.0-dy);
  
  for(var i:int=0;i<4;i++){
    for(var j:int=0;j<4;j++){
      mat[j] = buf[j*4+i];
    }
    tmp[i] = innerProduct(fy, mat);
  }
  res = Math.floor(innerProduct(tmp, fx));
  if(res<0) res = 0;
  else if(res>255) res = 255;
  return res;
}

private function cubicFunc(t:Number):Number {
  var at:Number = 0.0;
  var res:Number = 0.0;
  var a:Number = -1.0;

  at = t < 0 ? -t : t;
  if(0.0 <= at && at < 1.0){
    res = (a+2)*at*at*at-(a+3)*at*at+1.0;
  }else if(1.0 <= at && at < 2.0){
    res = a*at*at*at-5*a*at*at+8*a*at-4*a;
  } 
  return res;
}
    
private function innerProduct(x:Vector.<Number>, y:Vector.<Number>):Number {
  var res:Number = 0.0;
  for(var i:int=0;i<4;i++) res += x[i]*y[i]
  return res;
}

ニアレストネイバー法と比べるとかなり計算コストが高いので処理に時間がかかります。


(↑画像にしてるので若干圧縮されてますが;)

ところで、flashrodさんの記事で最後に

bd.draw()のsmoothingがfalseのときは、最近傍法と同じようだ。trueのときは、線形補間法のときよりももっとスムーズになっているので、たぶん線形補間でない補間法をつかっているのだろう。

とありますが、たぶんバイキュービック補間だと思います。
ただ、補間関数に何か手を加えて適用してるのかもしれません。
Photoshopでリサイズするときのオプションを見ても、
・バイキュービック法(滑らか)
・バイキュービック法(シャープ)
と一種類ではありませんから。
それか、smoothingというプロパティ名から考えると、補間後に軽く平滑化してるのかも。

あと、前回は紹介しませんでしたが、Lanczos3という補間法も高品質な拡大縮小処理が行えます。
AviUtl(動画編集ソフト)のLanczos 3-lobedプラグインで馴染みがある人が多いかと。
補間法はいろいろありますが、目的に応じて使い分けするのがいいですね。

<追記>11/11
BitmapData.draw()を使って拡大したもの(smoothingはtrue)と比較してみました。

”たぶんバイキュービック補間だと思います”と上で書きましたが違うかもしれません。
今度は画像ではなくswfを上げておきます。
BiCubic.swf (要FP10 ・バイキュービック補間の計算に少し時間がかかります)
2つを比べてみると、BitmapData.draw()はエッジが保存されておらず境界がボケています。ということは、どうやらバイキュービックよりバイリニアの方に近いようです。ここでは元画像を2倍に拡大していますが、PSNRで見るまでもなく目視でわかるほど違いが出ています。

Photoshopで補間を行うのと違って、Flashの場合はビットマップ操作を高速に行いたいという需要があるため、計算コストの高いバイキュービックは採用されなかったのかもしれません。

自然画像において頻繁に拡大縮小を行わない場合は、BitmapData.draw()よりもきちんとバイキュービック補間した方が処理時間がかかる分、境界のにじみがなくなり画像が鮮明になるようです(にじんでる方が良いという人もいますが;)。

どちらにしろ、目的に応じて使い分けするのが良いというのは変わりませんね。

・関連記事
画像のリサイズ – AS3.0

あわせて読む:

コメントを残す

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