バイリニア法,バイキュービック法のプログラム


画像処理の入門本を読んでたらこれらの処理をOpenCVに丸投げしていてブチ切れてしまったので自分で実装してみた.あってるかどうかがだいぶ怪しいので誤ってたら教えて下さい.

画像srcを拡大してrate倍の画像dstを作りたい.この時,画素の座標src=(x,y)がdst=(x*rate,y*rate)へ移動するので拡大によって生まれた隙間のpxを補間しなければならない.この補間方法をいくつか実装してみる.

ここではグレースケール画像の2次元配列を扱うが,RGBの3要素2次元配列への拡張は簡単である.

ニアレストネイバー法

ニアレストネイバー法では,隙間のpxの座標を最も近いdest=(x*rate,y*rate)の色にすることで補間を行う.

# 元画像srcの縦横,拡大画像dstの縦横
(h_origin, w_origin) = size(src)
(h, w) = (h_origin*rate, w_origin*rate)
dst = zeros((h,w))

for y_dst in 1:h
    for x_dst in 1:w

        # (x_src,y_src)は今注目する(x_dst,y_dst)に一番近いsrcの座標
        x_src, y_src = Int(round(x_dst/rate)), Int(round(y_dst/rate))

        # 端の値をセット
        if x_src < 1
            x_src = 1
        elseif x_src > w_origin
            x_src = w_origin 
        end
        if y_src < 1
            y_src = 1
        elseif y_src > h_origin 
            y_src = h_origin
        end

    # 最も近いsrcの色をコピー
    dst[y_dst,x_dst] = src[y_src,x_src]
    end
end

ただしこの方法だと拡大画像がチェッカーボードのようなモザイク感を醸し出す.

バイリニア法

ニアレストネイバー法では直近の色をそのままコピーしただけだったので色の変化に乏しい画像になってしまった.
バイリニア法ではこの方法に少しアレンジを加え,近傍のpxの色の変化がグラデーションになるように,srcから色をコピーした後に1次関数の傾きで重み付けする方法である.

# 元画像srcの縦横,拡大画像dstの縦横
(h_origin, w_origin) = size(src)
(h, w) = (h_origin*rate, w_origin*rate)
dst = zeros((h,w))

for y_dst in 1:h
    for x_dst in 1:w

        # (x_src,y_src)は今注目する(x_dst,y_dst)に一番近いsrcの座標
        x, y = x_dst/rate, y_dst/rate
        x_src, y_src = Int(round(x)), Int(round(y))

        # 端の値をセット
        if x_src < 2
            x_src = 2
        elseif x_src > w_origin -1
            x_src = w_origin -1
        end
        if y_src < 2
            y_src = 2
        elseif y_src > h_origin -1
            y_src = h_origin -1
        end

        # 重み
        W1 = (1 - (x - x_src)) * (1- (y - y_src))
        W2 = (x - x_src) * (1- (y - y_src)) 
        W3 = (1- (x - x_src)) * (y - y_src) 
        W4 = (x - x_src) * (y - y_src) 

        # 拡大画像の画素値を計算
        dst[y_dst,x_dst] = 
            ( W1*src[y_src,x_src] 
            + W2*src[y_src,x_src+1] 
            + W3*src[y_src,x_src+1] 
            + W4*src[y_src+1,x_src+1])
    end
end

この重みは,

Y = [y_src + 1 - y_dst
     y_dst - y_src]
X = [x_src + 1 - x_dst
     x_dst - x_src]
S = [src[x_src, y_src] , src[x_src+1, y_src]
     src[x_src, y_src+1] , src[x_src+1, y_src+1]]
dsr[x_dst,y_dst] = Y' * S * X

の計算により得られたもので,x_dsty_dstの1次関数になる.
全体的にぼやけた感じになる.

バイキュービック法

更に良い重みが欲しくなる.バイキュービック法では線形な重みではなく定義域[-2,2]のsinc関数

sinc(x) = \frac{sin(πx)}{πx}

の近似を用いる.この多項式近似が3次なのでキュービックの名前がつけられたんだと思う.ほんとか?

# 重みsinc関数
function sinc(d)
    a = -0.75
    if (d<=1.0)
        ret = (a+2.0)*d^3 - (a+3.0)*d^2 + 1
    elseif (d<=2.0) 
        ret = (a)*d^3 - (5.0*a)*d^2 + (8.0*a)*d - (4.0*a)
    else 
        ret = 0 
    end
    return ret
end

for y_dst in 1:h
    for x_dst in 1:w

        # (x_src,y_src)は今注目する(x_dst,y_dst)に一番近いsrcの座標
        x, y = x_dst/rate, y_dst/rate
        x_src, y_src = Int(round(x)), Int(round(y))

        suma = 0
        for i in (y_src-1):(y_src+1)
            for j in (x_src-1):(x_src+1)
                # 端の値をセット
                if x_src < 1
                    x_src = 1
                elseif x_src > w_origin 
                    x_src = w_origin 
                end
                if y_src < 1
                    y_src = 1
                elseif y_src > h_origin 
                    y_src = h_origin 
                end

                # 画素成分の分子の計算
                dst[x_dst,y_dst] += src[x_src,y_src] * sinc(abs(x - j)) * sinc(abs(y - i))
                # 画素成分の分母の計算
                suma += sinc(abs(x - j)) * sinc(abs(y - i))
            end
        end

        # 分子と分母をセット
        dst[x_dst,y_dst] /= suma
    end
end

この分子と分母の計算では,

dst[x,y] = \frac{\sum_{i,j ∈ 近傍}src[i,j]sinc(i)sinc(j)}{\sum_{i,j ∈ 近傍}sinc(i)sinc(j)}

が起こっている.
わかりやすい → http://koujinz.cocolog-nifty.com/blog/2009/05/bicubic-a97c.html

またsinc関数の定義域をより広めた補間方法も知られる.

まとめ

illustratorやClipStudioでバイリニアとかバイキュービックとか聞かれても何が起こるかわかるようになった.めでたしめでたし.