2012年09月06日 18:03 [Edit]

javascript - で bilateral filter (選択的ガウスぼかし)を実装してみた

選択的ガウスぼかし」がえらい気に入ったので、アルゴリズムの学習も兼ねてJavaScriptでやってみたら思いの他使い物になりそうということで。


Demo:

File APIを実装しているブラウザーで動きます。IEの方ごめんなさい。IEだと10以降になります。小さめのファイルを読み込ませて下さい。1024*1024ピクセルを一応の上限に設定してあります。

Info:
Source:
Radius:
Threshold:(0-255)
32

実装

ざっとこんな感じです。img中の画像にradiusthresholdで指定されたとおりに bilateral filter をかけ、結果をData URIとして帰します。画像操作には使い捨てのcanvasを使っています。canvasに関しては「HTML5 Canvas」が参考になりました。この場を借りて献本御礼。



要点と感想

  • アルゴリズムの実装そのものは簡単でした。以下のページのおかげです。
  • 同ページの分類で行くと、"Better Burute-Force"な実装ということになります。計算量は処理対象の画素数と、ぼかし半径の二乗(つまりカーネルの面積)に比例します。
  • 実は一番悩ましかったのが、ガウスぼかしをGausissanたらしめる、σの設定をどうするかでした。ソース中に2.04045 というマジックナンバーが出てきますが、これは solve x for erf(x) = 255/256 の xです。ぼかし半径の外では計算結果が必ず最低輝度以下、つまりゼロになる、と。
  • これに最大距離、つまり空間方向にはぼかし半径、色差方向にはRGBのユークリッド距離をかけたものがσの基本になります。
  • それにさらに(閾値/255)をかけてものを、色差のσとして使っています。こうすることで、閾値外のものは確実にゼロなるというわけです。
  • 前述のとおり、色差にはRGBのユークリッド距離を使っています。マンハッタン距離だと、可能な状態が768通りしかないので、空間方向の係数と同様ルックアップテーブルを使って高速化しやすいのですが(そういう実装もちらほら見受けられた)、採用しなかったのは、たとえば rgb(80,80,80) とrgb(240,0,0)の距離が同じになってしまうから。ユークリッド距離だと後者は√3倍つまり1.7倍以上離れています。
  • あるいはYCbCrにしてからYだけ見るか。これも一応試してみたのですが、YCbCr変換のコストが以外と大きくて、手元の環境では素直に計算した方が高速でした。今日日のブラウザーのMathはあなどれません。

Enjoy!

Dan the JavaScripter


Demo Source

HTML


JavaScript

(function(global){

if (!global.FileReader) return; /* throw new Error('FileReader not supported'); */

var $ = function(id){ return document.getElementById(id) };
var stubIcon = 'http://blog.livedoor.jp/dankogai/img/1x1.gif';
var clearImg = function(){
    $('theImage').src = $('busy').src = stubIcon;
};
var readImg = function (file, img, onload) {
    var reader = new FileReader();
    reader.onload = function (ev) {
        img.src = ev.target.result;
    };
    reader.readAsDataURL(file);
    $('theFiltered').src = stubIcon;
};
var filterit = function() {
    var img = $('theImage'),
        radius =    1 * $('theRadius').value,
        threshold = 1 * $('theThreshold').value;
    /* safety valve */
    if (img.width * img.height > 1024 * 1024) {
        throw 'image too large (Max = 1M pixels)';
    }
    $('filterIt').disabled = true;
    $('busy').src = 'http://blog.livedoor.jp/dankogai/img/ajax-loader.gif';
    setTimeout(function(){
        var started = Date.now();
        $('theFiltered').src = bilateralFilter(img, radius, threshold);
        $('elapsed').innerHTML = (Date.now() - started) + 'ms';
        $('filterIt').disabled = false;
        $('busy').src = stubIcon;
    }, 0);
};
var readit = function () {
    var file = $('theFile').files[0];
    var info = {
            name: file.name,
            lastModifiedDate: file.lastModifiedDate,
            size: file.size,
            type: file.type
        };
    $('fileInfo').textContent = JSON.stringify(info, null, '  ');
    if (file.type.indexOf('image') === 0) {
        readImg(file, $('theImage'));
    } else {
        clearImg();
    }
};

$('theFile').addEventListener('change', readit, false);
$('readIt').addEventListener('click', readit, false);
$('filterIt').addEventListener('click', filterit, false);
clearImg();

})(this);

この記事へのトラックバックURL

この記事へのトラックバック
急激に冷えてきましたねー。もうね寒暖の変化についていけない体質なので、本当にきついんです。(‘A’) 皆さんも風邪には気をつけて冬物をガサゴソしてくださいな。何だか楽しみにしてくれている方も増えてきた今月のまとめ記事をどうぞ!Web系多めです。...
2012年9月に追加したお気に入り記事まとめ(WEB制作/SEO・面白ネタなど)【すしぱくweb|susi-paku <楽しければいいのです)】at 2012年09月28日 10:38