メディアンフィルタによるノイズ除去

メディアンフィルタとは、3*3など一定の領域のピクセルを明度などでソートしその中央値のピクセルの色を領域の中心のピクセルに設定する画像処理です。隣接領域の中から「無難」な色を選択して設定することで、ノイズ除去などの効果を得ることができます。

メディアンフィルタのアルゴリズム

メディアンフィルタ(median filter)では、ピクセルの色を決める時、以下のような処理を行います。

  • 周囲3*3ピクセルについて、それぞれの情報(明度など)を配列に格納
  • 配列をソート
  • 配列の中央の値(を持つピクセルの値、配列の要素数が9なら5番目)をピクセルの値とする

このアルゴリズムでは、周辺画素との置換(移動)が生じますが、加算・平均化など画素の値そのものを直接変更する処理は行われません。

メディアンフィルタ処理で期待される主な効果は、ノイズ除去です。画像内の「極端なピクセル」が取り除かれ(周囲と大きく異なるピクセルは、「中央」には来にくい)、細かなノイズを除去することができます。

特にデジタルカメラの画像で生じる粒状の暗部ノイズに有効で、高感度撮影でざらついた画像もメディアンフィルタでノイズ除去を行なうと見栄えが良くなります。

ただ、星など「小さい特徴的な点」や細部の構造が崩れやすいので、画像によっては適用範囲を限定するなどの工夫が必要になりそうです。

メディアンフィルタのプログラム

今回は、「R+G+B成分の合計値」をもとにピクセルをソートしてメディアンフィルタ処理を試してみたいと思います。これは、簡易的な明度としてRGB各成分の平均を使うような感覚です。

メディアンフィルタのアルゴリズムをそのまま適用すると、画像の各ピクセルに対して以下のような処理を繰り返すことになりますね。

  • 配列に周囲9ピクセル(x - 1, y - 1)~(x + 1, y + 1)のR+G+Bの値を格納。
  • 配列をソート
  • 並び替えた配列の中央にあるピクセルのRGB値をピクセルの値とする

JavaScriptでは複数の値をまとめたオブジェクトを簡単に作れますから、配列にはRGBの合計値の他にRGBそれぞれの値もまとめたオブジェクトを格納しましょう。ソート後に「配列の5つ目のオブジェクトのRGB」を参照すれば、その値をそのまま画素に設定することができます。

この方式でRGBAバッファ(ImageDataのdataプロパティ)と画像サイズを渡すとメディアンフィルタをかけた画像のImageDataを返す関数は、以下のようになります。

// メディアンフィルタをかけたImageDataを作成
function processImage(image_rgba, width, height) {

    let output_data = new ImageData(width, height);

    // 処理結果を格納するRGBAバッファ
    let output_buf = output_data.data;

    // 周辺領域のピクセルデータを格納する配列
    let pixel_array = new Array(9);

    for (let y = 1;y < height - 1;y++) {

        let y_index = y * width * 4;

        for (let x = 1;x < width - 1;x++) {

            let array_index = 0;

            // (x - 1, y - 1)~(x + 1, y + 1)の範囲を処理
            for (let py = y - 1;py <= y + 1;py++) {

                let py_index = py * width * 4;

                for (let px = x - 1;px <= x + 1;px++) {

                    // 処理対象ピクセルデータの位置を算出
                    let pixel_index = py_index + px * 4;

                    let r = image_rgba[pixel_index];
                    let g = image_rgba[pixel_index + 1];
                    let b = image_rgba[pixel_index + 2];

                    // 配列にRGB合計値とRGB個別値を保持するオブジェクトを格納
                    pixel_array[array_index++] = {"val": r + g + b, "r": r, "g": g, "b": b};

                }

            }

            // 配列をソート
            pixel_array.sort(function(a, b) { return a.val - b.val; });

            // ソート後配列中央の画素データを取得
            let pixel = pixel_array[4];

            // 出力画像データの対応座標に中央の画素値を設定
            output_buf[y_index + x * 4] = pixel.r;
            output_buf[y_index + x * 4 + 1] = pixel.g;
            output_buf[y_index + x * 4 + 2] = pixel.b;

            // アルファは常に255(透過なし)
            output_buf[y_index + x * 4 + 3] = 255;

        }

    }

    return output_data;

}

簡略化のため上下左右の端は処理対象から外しています。

メディアンフィルタの実験

画像を選択して「処理」ボタンをクリックすると、実際にメディアンフィルタをかけた画像が表示されます。


画像はそのままの大きさで表示されるので、あまり大きな画像は使用しない方がよいでしょう。


創作プログラミングの街