このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。
さて、今日は昨日紹介したアルゴリズムである、
線形補間法をコードで実装してみよう。
ピクセルを領域として考えないと、
画像の端にあるピクセルの情報量が減ってしまうことは、
以前、「ピクセルは点か領域か」の日記で考察した。
なので、ピクセルを領域として計算する方法で行いたい。
その場合、コードを書く前に座標の問題を考える必要がある。
何が問題かというと、画像の端に当たるピクセルの計算時に、
元の画像の範囲を超えてしまう可能性が考えられるのだ。
これは、近傍法では起こらない。
例を挙げよう。
3 x 3 の画像を、15 x 15 に拡大するとする。
黒赤黒
青白黄
黒緑黒
試しに、拡大先の画像の右上に当たるピクセルで考える。
右上なので、ピクセルの位置(添字)は、(14, 0) だ。
領域と考えると、(14, 0) - (15, 1) の範囲となる。
座標変換を行うために、範囲から代表となる点を選べば、
範囲の中心である、(14.5, 0.5) となる。
次に、この点に対して座標変換を行う。
新しい画像の座標から、元の画像の座標に変換するのだ。
14.5 / 15 * 3 = 2.9, 0.5 / 15 * 3 = 0.1
つまり、対応する元の座標は、(2.9, 0.1) となる。
この座標も、代表となる点の座標と考えないといけないため、
この点を代表とする領域を考えると、
(2.4, -0.4) - (3.4, 0.6) という範囲となる。
そして、この範囲をピクセルの位置(添字)に戻せば、
(2.4, -0.4) という要素番号が求まる。
近傍法の場合は、これに最も近い点を使うため、
四捨五入して、(2, 0) となるため、元の画像内に収まる。
線形補間法の場合、算出位置に最も近い 4 点を使うため、
(2, -1), (3, -1), (2, 0), (3, 0) の 4 つの点が必要だ。
(2, 0) 以外は、元の画像の範囲外となってしまった。
つまり、座標の範囲チェックが必要になるってことだ。
では、実装に入ろう。
# 負の方向に小数を切り捨てる関数 floor を使う。
use POSIX;
# BMP 読み込み。
my $src = read_bmp;
# 画像の幅と高さを取得。
my $scx = @{$src->[0]};
my $scy = @$src;
# 新しい画像の幅と高さを決める。
my $dcx = 105;
my $dcy = 90;
# 新しい画像のデータを準備する。
my $dest = [ map { [ (0) x $dcx ] } 1 .. $dcy ];
for (my $dy = 0; $dy < $dcy; ++$dy) {
for (my $dx = 0; $dx < $dcx; ++$dx) {
no integer;
# 新しい点の位置に対応する元の点の位置を求める。
my $sx = ($dx + 0.5) / $dcx * $scx - 0.5;
my $sy = ($dy + 0.5) / $dcy * $scy - 0.5;
# 位置を整数化し、最も近い左上の点を得る。
# 計算結果が負の場合があるので、
# int 関数は使えない。そこで POSIX::floor を使う。
my $px = floor($sx);
my $py = floor($sy);
# 色を累算する配列。
my @rgb = (0, 0, 0);
# 周辺の 4 点に対して処理をする。
foreach my $yy ($py, $py + 1) {
foreach my $xx ($px, $px + 1) {
# 縦と横の影響力を求め、積を重みとする。
my $weight = (1 - abs($yy - $sy))
* (1 - abs($xx - $sx));
# 座標が範囲外であれば、範囲内に収める。
my ($tx, $ty) = ($xx, $yy);
$tx = 0 if $tx < 0;
$tx = $scx - 1 if $tx >= $scx;
$ty = 0 if $ty < 0;
$ty = $scy - 1 if $ty >= $scy;
# RGB に分解し、それぞれ重みを掛けて累算する。
if ($weight) {
my @clr = unpack_rgb($src->[$ty][$tx]);
$rgb[$_] += $clr[$_] * $weight foreach 0 .. 2;
}
}
}
# RGB それぞれ累計値を整数化する。
$rgb[$_] = floor($rgb[$_] + 0.5) foreach 0 .. 2;
# 32 ビット整数値に戻しておく。
$dest->[$dy][$dx] = pack_rgb($rgb[0], $rgb[1], $rgb[2]);
}
}
# BMP を書き出す。
write_bmp($dest, 24);