平滑化 (移動平均) フィルタ

概要

  • 画素の色の違いを滑らかにする (周囲の画素と平均する)

イメージ

黒板の文字を黒板消しで消そうとしたが、完全には消えていない状態 (= チョークの粉を引き延ばした状態?)

分かった部分

カーネル適用部分の中心部分を注目画素という。畳み込みを行ったときの
合計値が注目画素 (カーネルで処理しようとしている部分全体ではない!) の画素値になる。3x3カーネルを1回適用しても画素値が変化するのは1マスだけ

実装してみる (Python)

import numpy as np
from matplotlib import pyplot as plt


def smooth(img, i, j, kernel):
    """
    カーネルの左上のインデックスが (i, j) となるよう
    カーネルを適用する
    """ 
    
    # カーネルのサイズ
    rows, cols = kernel.shape

    # カーネルで平滑化する
    kerneled = img[i:i+rows, j:j+cols] * kernel
        
    # 注目画素の位置 (i, jからのギャップ)
    interest_gap = int(rows / 2)    # 切り捨て

    # 注目画素の色を変える
    img[i+interest_gap, j+interest_gap] = np.sum(kerneled)

    return img


def walk(img, kernel):
    """画像全体にカーネル (フィルタ) を適用する"""

    img_rows, img_cols = img.shape
    k, _ = kernel.shape

    # 画像よりも大きなフィルタは適用できない
    # (フィルタの縦、横いずれかが画像より大きい場合はエラー)
    if img_rows < k or img_cols < k:
        emsg = "kernel size must be less than the size of the image"
        raise ValueError(emsg)

    # 画像全体にカーネルを適用する
    # カーネルは縦、横にそれぞれ、縦(横) - k + 1だけ動ける
    for i in range(img_rows - (k - 1)):
        for j in range(img_cols - (k - 1)):
            img = smooth(img, i, j, kernel)

    return img


def main():
    """平滑化フィルタを適用する"""

    # 画像データ
    img = np.array([
        [1, 1, 1, 0, 0, 0],
        [1, 1, 1, 0, 0, 0],
        [1, 1, 1, 0, 0, 0],
        [1, 1, 1, 0, 0, 0],
        [1, 1, 1, 0, 0, 0],
    ]).astype("float32")

    # 元の画像をコピーしておく
    img_original = img.copy()

    # カーネル
    kernel3 = np.ones((3, 3), np.float32) / 9
    kernel5 = np.ones((5, 5), np.float32) / 25

    # カーネルを選択する
    kernel = (kernel3, kernel5)[0]

    # カーネルの左上の座標 (インデックス) が
    # (i, j) の位置になるようカーネルを適用する
    img = walk(img, kernel)

    # 確認用
    check = False
    if check:
        rnd = np.vectorize(lambda bit: np.round(bit, 1))
        print(rnd(img))

    # 画像を表示する
    plot_images = True
    if plot_images:
        titles = ("Original", "Averaging")
        for i, image in enumerate((img_original, img)):
            plt.subplot(1, 2, i + 1)
            plt.imshow(image, cmap=plt.cm.gray)
            plt.title(titles[i])

        plt.tight_layout()
        plt.show()


if __name__ == "main":
    main()

動作結果

境界線をぼかそうとしているのが分かる。

f:id:zdassen:20170808140259p:plain