デジタル・デザイン・ラボラトリーな日々

アラフィフプログラマーが数学と物理を基礎からやり直す

射影変換(ホモグラフィ)について理解してみる その9

今回は、画像補間処理について説明します。
射影変換により画像を自由変形出来るようになり、拡大や回転も可能になりました。
拡大や回転する際に足りない画像の画素と画素の間の輝度値を参照して、より美しく描画します。
美しくするようにすればする程、処理にコストがかかり遅くなります。

画像補間方法には、主に下記の3つがあります。

  1. 最近傍補間(ニアレストネイバー Nearest neighbor)
  2. 双一次補間(バイリニア補間 Bilinear)
  3. 双三次補間(バイキュービック補間 Bicubic)

バイキュービック > バイリニア > ニアレストネイバー
( 優れる > 劣る )

ところで、CSSCanvasで拡大や回転した場合、どのアルゴリズムを使用しているのでしょうか?
どうも調べてみると、ブラウザおよび状況によって使い分けているようです。
CSSで下記のように画像補間方法を指定すれば、指定の方法で描画するようです。

/* 品質重視 */
img.high {
 -ms-interpolation-mode:bicubic; /* IE 7+ */
 image-rendering: optimizeQuality; /* Fx, (Gecko 1.9.2) */
}
/* 速度重視 */
img.low {
 -ms-interpolation-mode:nearest-neighbor; /* IE 7+ */
 image-rendering: -moz-crisp-edges; /* Fx, (Gecko 1.9.2) */
}

今回は、ニアレストネイバーとバイリニア補間の2つを実装してみました。
プログラムは下記サイトを参考にしました。
HTML Canvasで拡大縮小 - NullPointer's Blog

下図は猫画像の鼻の部分を拡大したものです。ニアレストネイバーだとやはりドットが目立ちますね。
左がニアレストネイバー、右がバイリニア補間
f:id:Yaju3D:20131017014533j:plain

最近傍補間(ニアレストネイバー Nearest neighbor)
ニアレストネイバー法は、一番近くの点をサンプリングする方法です。
小数点が生じた場合、四捨五入することで近い点に寄せます。

var floorX = (tmpX + 0.5) | 0;
var floorY = (tmpY + 0.5) | 0;

ちなみに今まではどのように描画していたかというと、小数点を切り捨てていました。
微妙な違いがあるってことですね。

var floorX = tmpX | 0;
var floorY = tmpY | 0;

■双一次補間(バイリニア補間 Bilinear)
f:id:Yaju3D:20131017013259j:plain
バイリニア補間は、近傍4点の加重平均値をとる方法です。
1つの点に対して近傍4点参照することになるので、速度は遅くなりますが画像はきれいです。
具体的なアルゴリズムは下記サイトにお任せするとして、作成する際に気になった点を2つ書いてみます。

きれいな拡大・縮小~Bilinear法の解説~ Delphi
画素の補間(Nearest neighbor,Bilinear,Bicubic)

1.下記ソースのdx,dyは何をしているか?
実は小数点部分のみを取得しています。
バイリニア補間は、4つの点の位置の比率で色を決めるようになっており、例えば横方向 dx=0.8の場合、0.2と0.8の比率となり、縦方向 dy=0.4の場合、0.6と0.4の比率となります。それを赤・緑・青の成分ごと行います。

var floorX = tmpX | 0;
var floorY = tmpY | 0;
var dx = tmpX - floorX;
var dy = tmpY - floorY;

//赤成分
var r0 = (rgb00.R * (1 - dx)) + (rgb10.R * dx);
var r1 = (rgb01.R * (1 - dx)) + (rgb11.R * dx);
var R = (r0 * (1 - dy) + r1 * dy) | 0;

2.端の処理はどうしているのか?
1つの点に対して近傍4点参照しているので、どうしても端になれば参照するところがなくなります。
何も対処しなければ、通常は例外エラーになるわけです。
getPixel内で端か調べて、端なら1つ前にしています。端の場合には2点の色から求めることになります。

function getPixel(imageData, x, y, w, h)
if(x == w) { x = w - 1; }
if(y == h) { y = h - 1; }


■プログラム
jsdo.it


jsdo.itに投稿する上で、自作の行列演算クラスはやめてメジャーな行列演算ライブラリのglMatrixを採用しました。
その方がソースリストも短くなるし、参考にしてもらいやすいと思ったからです。
また、プログラム自体はTypescriptで組んでいるのですが、jsdo.itにはTypescriptで吐き出したJavascriptにコメントを追加して公開しています。これが結構面倒くさいんですよね、でも、Typescriptの普及率がまだまだなので仕方ない。
Typescriptで組んだソースリストは、github/Homographyに公開しました。


次回は、透明付き画像の描画に挑戦します。今は画像をputImageDataを使用して描画しているため、透明部分は無効になってしまっています。そこを改善してみます。