LoginSignup
0
0

More than 1 year has passed since last update.

【画像処理解説】メディアンフィルタ(Javaソース付き)

Last updated at Posted at 2021-08-16

はじめに

画像処理を簡単に解説していきます。
下部にはJavaでのソースコードも載せているのでぜひご参照ください。
不定期にはなりますが、随時更新していく予定です。

メディアンとは?

日本語では中間値といい、名前の通り数集合の中間に位置する数のことです。

簡単な例ですが、あるテストを受けた人が7人いて、点数分布は以下の通りでした。
$\begin{matrix}
34 & 36 & 39 & 42 & 44 & 48 & 100
\end{matrix}$

この例において平均点を計算すると、49点となります。
直感的には49点が真ん中というのは高いように感じませんか?
2位ですら平均点以下となってしまっています。
これは一人だけ100点という高得点を取っている人がいるため、平均点が高めに出ているのです。

次に、メディアンを計算してみましょう。
7人の中間なので、4位の点数である42点が採用されます。
この点は4位の点数そのままなので、1位の100点には引っ張られていません。
メディアンのほうが「真ん中」という感じがしますよね?

以上、説明した通りメディアンには外れ値(極端に他と異なる値)の影響を受けにくいという長所があります。
これを利用したのが以下のメディアンフィルタです。

メディアンフィルタとは?

ノイズ除去などの平滑化処理で用いられるフィルタです。
名前の通り、自身を含む周囲 $n\times m$ 画素の中から、メディアンを取得し、新しい画素としてセットします。

例えば、以下のカーネルの場合、メディアンは5となり外れ値の255が除外されます。

\begin{bmatrix}
 10 & 4 & 8 \\
 2 & 255 & 12 \\
 1 & 3 & 5
\end{bmatrix}

アルゴリズム

  1. 画像をラスタスキャンし、各画素に2から5の処理を行う。
  2. カーネル範囲の値を保存するための配列変数を用意する。
  3. カーネル範囲を全探索し、各画素の値を2で用意した配列変数に追加する。
  4. 3で更新した配列変数を昇順にソートする
  5. 4でソートした配列の中間値を取得し、1で選択している画素にセットする

メディアンフィルタの例

元画像(カラー画像)
imori_256x256_noise.png

結果画像(メディアン画像)
out010.png

ソースコード

Smoothing.java
public static SImage median(SImage oriImg, int ksize) {
    // グレースケール画像の場合
    if (oriImg.channel == 1) {
        // メディアン画像用を生成
        var medImg = new SImage(oriImg.width(), oriImg.height(), 1);

        // 画像をラスタスキャン
        for (int y = ksize / 2; y < oriImg.height() - ksize / 2; y++)
            for (int x = ksize / 2; x < oriImg.width() - ksize / 2; x++) {
                // メディアン取得用の変数初期化
                int[] vArray = new int[ksize * ksize];

                // カーネル範囲をスキャン
                int index = 0;
                for (int dy = -ksize / 2; dy <= ksize / 2; dy++)
                    for (int dx = -ksize / 2; dx <= ksize / 2; dx++)
                        // 値配列に値を追加
                        vArray[index++] = oriImg.getGray(x + dx, y + dy);

                // 値配列をソート
                Arrays.sort(vArray);

                // 中間値を出力画像にセット
                medImg.setGray(x, y, vArray[4]);
            }
        return medImg;

        // RGBカラー画像の場合
    } else if (oriImg.channel == 3) {
        // メディアン画像用を生成
        var medImg = new SImage(oriImg.width(), oriImg.height(), 3);

        // 画像をラスタスキャン
        for (int y = ksize / 2; y < oriImg.height() - ksize / 2; y++)
            for (int x = ksize / 2; x < oriImg.width() - ksize / 2; x++) {
                // メディアン取得用の変数初期化
                int[] rArray = new int[ksize * ksize];
                int[] gArray = new int[ksize * ksize];
                int[] bArray = new int[ksize * ksize];

                // カーネル範囲をスキャン
                int index = 0;
                for (int dy = -ksize / 2; dy <= ksize / 2; dy++)
                    for (int dx = -ksize / 2; dx <= ksize / 2; dx++) {
                        // 値配列に値を追加
                        int[] rgb = oriImg.getRGB(x + dx, y + dy);
                        rArray[index] = rgb[0];
                        gArray[index] = rgb[1];
                        bArray[index++] = rgb[2];
                    }

                // 値配列をソート
                Arrays.sort(rArray);
                Arrays.sort(gArray);
                Arrays.sort(gArray);

                // 中間値を出力画像にセット
                int[] newRgb = new int[]{rArray[ksize * ksize / 2], gArray[ksize * ksize / 2], bArray[ksize * ksize / 2]};
                medImg.setRGB(x, y, newRgb);
            }
        return medImg;

        // アルファチャネルを持つ場合
    } else {
        // メディアン画像用を生成
        var medImg = new SImage(oriImg.width(), oriImg.height(), 4);

        // 画像をラスタスキャン
        for (int y = ksize / 2; y < oriImg.height() - ksize / 2; y++)
            for (int x = ksize / 2; x < oriImg.width() - ksize / 2; x++) {
                // メディアン取得用の変数初期化
                int[] aArray = new int[ksize * ksize];
                int[] rArray = new int[ksize * ksize];
                int[] gArray = new int[ksize * ksize];
                int[] bArray = new int[ksize * ksize];

                // カーネル範囲をスキャン
                int index = 0;
                for (int dy = -ksize / 2; dy <= ksize / 2; dy++)
                    for (int dx = -ksize / 2; dx <= ksize / 2; dx++) {
                        // 値配列に値を追加
                        int[] argb = oriImg.getARGB(x + dx, y + dy);
                        aArray[index] = argb[0];
                        rArray[index] = argb[1];
                        gArray[index] = argb[2];
                        bArray[index++] = argb[3];
                    }

                // 値配列をソート
                Arrays.sort(aArray);
                Arrays.sort(rArray);
                Arrays.sort(gArray);
                Arrays.sort(gArray);

                // 中間値を出力画像にセット
                int[] newArgb = new int[]{aArray[ksize * ksize / 2], rArray[ksize * ksize / 2], gArray[ksize * ksize / 2], bArray[ksize * ksize / 2]};
                medImg.setARGB(x, y, newArgb);
            }
        return medImg;
    }
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0