画像リサイズ補間アルゴリズム第3弾です。久方すぎる…
さて、バイキュービックとはなんぞやというご紹介。
概要
バイリニアでは2×2のピクセルを用いた直線的な補間方法でした。
今回のバイキュービックでは4×4ピクセルを用いた重み付け関数により曲線的な補間をします。
この方法を使うとバイリニアよりシャープなリサイズ結果を得ることができます。
アルゴリズムは下記サイトを参考にさせて頂きました。
こちらに載っているようにコアとなる以下の重み付け関数が3次式となっているため「Cubic」と呼ばれているそうです。
bicubic_weight( d ) = 1 - 2*d^2 + d^3 (d < 1.0 )
4 - 8*d + 5*d^2 - d^3 (1.0 ≦ d ≦ 2.0 )
0 (d > 2.0 )
実は上記式にはα
という係数が含まれていて、その値をいじるとシャープさが変わるらしいとのこと。(以下参照)
ちなみにwikipediaにはこのα
の値は-0.5~-0.75
程度と書かれていますが、一般的には-1
が使われるとのこと。
上記の式はまさにα=-1
が適用された状態のようです。
プログラム実装
最近はDelphiだとコストが嵩むようになったので、もっぱらC#でプログラミングするようになりました。
インラインアセンブラも骨が折れるのでやってません。
ということで、C#によるお手軽実装してみました。
(以下のコードはコンソールアプリとして動作します。)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.IO;
using System.Diagnostics;
namespace BicubicFilter
{
class Program
{
static void Main()
{
var file = @"Water lilies.jpg";
var dw = 1280;
var dh = 960;
using(var bmp = new Bitmap(file))
{
var sw = new Stopwatch();
sw.Start();
var dst = resizeBicubic(bmp, dw, dh);
sw.Stop();
dst.Save(Path.ChangeExtension(file, ".png"));
Console.WriteLine(String.Format("ElapsedTime: {0:f2}", sw.Elapsed.TotalMilliseconds));
Console.ReadLine();
}
}
static Bitmap resizeBicubic(Bitmap bmp, int width, int height)
{
var a = -1.0d;
int sw = bmp.Width, sh = bmp.Height;
double wf = (double)sw / width, hf = (double)sh / height;
Func trimByte<double,byte> = x => Math.Min(Math.Max(0, (int)Math.Round(x)), 255);
// バイキュービック重み付け関数
Func weightFunc<double,double> = d =>
d <= 1.0 ? ((a + 2.0) * d * d * d) - ((a + 3.0) * d * d) + 1:
d <= 2.0 ? (a * d * d * d) - (5.0 * a * d * d) + (8.0 * a * d) - (4.0 * a) : .0;
var dst = new Bitmap(width, height, PixelFormat.Format24bppRgb);
for (var iy = 0; iy < height; iy++)
{
for (var ix = 0; ix < width; ix++)
{
double wfx = wf * ix, wfy = hf * iy;
var x = (int)Math.Truncate(wfx);
var y = (int)Math.Truncate(wfy);
double r = .0, g = .0, b = .0;
for (int jy = y - 1; jy <= y + 2; jy++)
{
for (int jx = x - 1; jx <= x + 2; jx++)
{
var w = weightFunc(Math.Abs(wfx - jx)) * weightFunc(Math.Abs(wfy - jy));
if (w == 0) continue;
var sx = (jx sw - 1) ? x : jx;
var sy = (jy sh - 1) ? y : jy;
var sc = bmp.GetPixel(sx, sy);
r += sc.R * w;
g += sc.G * w;
b += sc.B * w;
}
}
dst.SetPixel(ix, iy, Color.FromArgb(trimByte(r), trimByte(g), trimByte(b)));
}
}
return dst;
}
}
}
とても短いですね。ものの30分程度で出来てしまいました。さすがC#!お手軽ゥ!
なお、例のごとくあえて速度は度外視しています。
さて、実行結果はというとこんなかんじに仕上がります。
(800x600pxから1280x960pxへ拡大)
やっぱバイリニアより良い感じじゃないですか?とはいえこの画像ってもともとノイズ多いですよね・・・って今頃思いました。
さて、速度はというと…
(AMD A10-5700 DDR1866で計測)
ElapsedTime: 26373.06
あー、そりゃそうですよね。単位はmsなんで約26秒掛かってます(;´Д`)
Delphiと違って、C#はマネージドコードなんで無難な書き方するとこんなもんです。
ということで、次回は高速化を試みたいと思います。
でわでわ。
コメント