[C#] Bicubic(バイキュービック法)~1.基本編~

  • このエントリーをはてなブックマークに追加

画像リサイズ補間アルゴリズム第3弾です。久方すぎる…
さて、バイキュービックとはなんぞやというご紹介。

概要

バイリニアでは2×2のピクセルを用いた直線的な補間方法でした。
今回のバイキュービックでは4×4ピクセルを用いた重み付け関数により曲線的な補間をします。

この方法を使うとバイリニアよりシャープなリサイズ結果を得ることができます。
アルゴリズムは下記サイトを参考にさせて頂きました。

画像の拡大「Bicubic法」: koujinz blog

こちらに載っているようにコアとなる以下の重み付け関数が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 )

実は上記式にはαという係数が含まれていて、その値をいじるとシャープさが変わるらしいとのこと。(以下参照)

Bicubic interpolation – Wikipedia, the free encyclopedia

ちなみに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へ拡大)

Water lilies_r

やっぱバイリニアより良い感じじゃないですか?とはいえこの画像ってもともとノイズ多いですよね・・・って今頃思いました。
さて、速度はというと…
(AMD A10-5700 DDR1866で計測)

ElapsedTime: 26373.06

あー、そりゃそうですよね。単位はmsなんで約26秒掛かってます(;´Д`)
Delphiと違って、C#はマネージドコードなんで無難な書き方するとこんなもんです。
ということで、次回は高速化を試みたいと思います。

でわでわ。

  • このエントリーをはてなブックマークに追加

SNSでもご購読できます。




コメントを残す