ガウスぼかしと画像処理 (ガウスぼかし)

ガウスぼかしは画像処理で広く使用されており、従来のぼかし効果に加えて、画像のピラミッド分解、アンチエイリアシング、高周波と低周波の分解、ノイズ抑制、照明効果などにも使用できます。ガウスぼかしは基本的すぎて広く使用されているため、実際のアプリケーションで意図せずに掘り下げてしまうのを避けるために、この機能をできるだけ深く理解する必要があります。

1次元ガウスカーネル関数

公式

G ( x ) = 1 2 π σ e − ( x − μ ) 2 2 σ 2 G(x) = \frac{1}{\sqrt{2\pi}\sigma}e^{-\frac{(x -\mu)^2}{2\sigma^2}}G ( × )=午後2時 p1e2P _2( x μ )2

ここで、μ \μμは対称軸であり、μ ≠ 0 は通常、画像処理では考慮されません\mu \neq0メートル=0なので、このパラメータは無視できます (この記事ではμ \muメートル)。

σ \シグマσ は関数の高さを決定します、σ \sigmaσ が小さいほど薄く、大きいほど分厚くなります。ぼかしに使用する場合、背が高くて薄いということは、中心ピクセルの計算結果への寄与が大きく、遠方のピクセルの寄与が相対的に小さいことを意味します。つまり、ぼかした結果は中心ピクセルの影響が大きくなります。短く太いものはぼけ度が高いことを意味します。したがって、他の条件が変わらない場合、σ \sigmaσにするとぼけが強くなり、σ にするとぼけが弱まります。

e インデックスの前の係数により、カーネル関数が( − ∞ , + ∞ ) (-\infty, +\infty)になることが保証されます。( ,+ )は 1 ですが、画像処理では明らかに無限の半径を使用することは不可能なので、ガウス カーネルの係数が正規化され、その後 e インデックスの前の係数が正規化で削減されるため、まったく計算する必要があります。

曲線形状

下の図は、1 次元ガウス カーネル関数の曲線形状です。

以下の図は、依然として e インデックスの前に係数を計算し、カーネル関数の積分を検証していることに注意してください。これは、σ \sigmaに関係なく、σ がどのように変化しても、その積分値は常に 1 になります。

ここに画像の説明を挿入

# -*- coding: utf-8 -*-
import numpy as np
import matplotlib.pyplot as plt


def gaussian_1d(x, sigma):
    coef = 1. / (np.sqrt(2 * np.pi) * sigma)
    return coef * np.exp(-x * x / (2 * sigma * sigma))


def plot_gaussian_1d():
    dx = 0.01
    xx = np.arange(-10, 10, dx)
    plt.figure(0, figsize=(8, 6))
    for sigma in [0.5, 1, 2]:
        yy = gaussian_1d(xx, sigma)
        ss = np.sum(yy * dx)
        plt.plot(xx, yy, label="sigma = %0.1f, integral = %0.2f" % (sigma, ss))
    plt.legend(loc='best')
    plt.savefig('gaussian_1d.png', dpi=300)
    plt.close(0)


if __name__ == '__main__':
    plot_gaussian_1d()

有効ブラー半径

一般に、ぼかし半径を大きくすると、ぼかし効果が大きくなります。ただし、曲線の形状から、対称中心から遠く離れた場合、ガウス カーネルの係数が非常に小さくなることがわかります。半径を大きくしてもぼやけ度合いはほとんど増えませんが、計算量は全く減りません。

したがって、ここでは有効ブラー半径の概念に注意を払う必要があります。なぜならσ \sigmaσはカーブの高さに影響するため、有効ブラー半径がσ \sigmaσの影響。有効ブラー半径を定義するには、相対的な概念を持つ基準を定義する必要があります。たとえば、簡単に言うと、曲線上の点の値と最大値の比率を次のように定義します。
arg max ⁡ x ( G ( x , σ ) G ( 0 , σ ) ) > limit \argmax_x(\frac{ G(x , \sigma)}{G(0,\sigma)}) > 限界バツ引数マックス(G ( 0 ,s )G ( x ,p )。)>上記の式を満たす最大
x を有効有効ブラー半径と呼ぶと、σ \sigma との関係を調べることができますσ関係。

有効なぼかし半径を見つけることは、計算能力を浪費して対応するぼかし効果が得られないことを避けるために、画像処理を実践する上で非常に重要です。ただし、この概念が議論されることはほとんどありません。

以下は、さまざまな制限条件下でのシグマと有効ブラー半径の関係であり、これが線形関係であることが簡単にわかります。
したがって、「ファジー」関数を開発し、ファジー次数パラメーターのみを開きたい場合、一般的に言えば、このパラメーターは半径にリンクする必要がありますが、半径が実際に機能するためには、適応されたシグマが必要です。このシグマはsigma = radius / a sigma = radius / aを使用できます。シグマ_ _ _=r a d i u s / aを計算します。画像のぼかしには、経験によれば、[2, 2.5]a の間の任意の数値を使用できます。
画像の説明を追加してください

def effective_radius():
    dx = 0.01
    xx = np.arange(0, 100, dx)
    limits = [0.1, 0.01, 0.001]
    colors = ['red', 'green', 'blue']

    sigmas = np.arange(0.1, 10, 0.1)

    plt.figure(0, figsize=(8, 6))
    for i, limit in enumerate(limits):
        effective_r = []
        for sigma in sigmas:
            yy = gaussian_1d(xx, sigma)
            yy = yy / np.max(yy)
            radius = np.max(np.argwhere(yy >= limit))
            effective_r.append(radius * dx)
        plt.plot(sigmas, effective_r, color=colors[i],
                 label='limit=%0.3f' % limit)

    plt.legend(loc='best')
    plt.grid()
    plt.xlabel('sigma')
    plt.ylabel('effective radius')
    plt.savefig('effective radius', dpi=300)
    plt.close(0)

2次元ガウスカーネル関数

公式

G ( x , y ) = 1 2 π σ x σ e − ( ( x − μ x ) 2 2 σ x 2 + ( y − μ y ) 2 2 σ y 2 ) G(x,y) = \frac{ 1}{2\pi\sigma_x\sigma_y}e^{-(\frac{(x-\mu_x)^2 }{2\sigma_x^2} + \frac{(y-\mu_y)^2 }{2 \sigma_y^2})}G ( x ,y =2_×pはい1e2P _バツ2( x m×)2+2P _y2( y mはい)2)

画像のぼやけの場合、平均値μ \muは気にしません。μと前の係数を使用するため、式は次のように簡略化できます。
G ( x , y ) = e − ( x 2 2 σ x 2 + y 2 2 σ y 2 ) G(x,y) = e^{-( \frac {x^2 }{2\sigma_x^2} + \frac{y^2 }{2\sigma_y^2})}G ( x ,y =e2P _バツ2バツ2+2P _y2y2)

以下はカーネル生成のコードと結果です。

# -*- coding: utf-8 -*-
import cv2
import numpy as np


def gaussian_2d(radius, sigma_x, sigma_y):
    coord = np.arange(-radius, radius, 0.01)
    xx, yy = np.meshgrid(coord, coord)
    res = np.exp(
        -(xx * xx / (sigma_x * sigma_x) + yy * yy / (sigma_y * sigma_y)) / 2)
    res = res / np.sum(res)
    return res


def plot_gaussian_2d():
    radius = 10
    sigma_x = 1.0
    sigma_y = 1.0

    kernel = gaussian_2d(radius, sigma_x, sigma_y)
    kernel = kernel / np.max(kernel)
    kernel = np.clip(np.round(kernel * 255), 0, 255)
    kernel = np.uint8(kernel)

    cv2.imwrite('radius=%d, sigma=(%d, %d).png' % (radius, sigma_x, sigma_y),
                kernel)

ここに画像の説明を挿入

行と列のネストされたループによるガウスぼかし

# -*- coding: utf-8 -*-
import cv2
import numpy as np


def gaussian_2d(radius, sigma_x, sigma_y, d_radius=1):
    coord = np.arange(-radius, radius + d_radius, d_radius)
    xx, yy = np.meshgrid(coord, coord)
    res = np.exp(-(xx * xx / (sigma_x * sigma_x) +
                   yy * yy / (sigma_y * sigma_y)) / 2)
    res = res / np.sum(res)
    return res


def convolve_2d(image, kernel, border='transparent'):
    image_height, image_width = image.shape[:2]
    kernel_height, kernel_width = kernel.shape[:2]
    radius_h = kernel_height // 2
    radius_w = kernel_width // 2

    # check: kernel_height and kernel_width must be odd
    if radius_h * 2 + 1 != kernel_height or radius_w * 2 + 1 != kernel_width:
        raise ValueError('kernel_height and kernel_width must be odd')

    res = np.zeros_like(image, dtype=np.float32)
    for row in range(image_height):
        # print(row)
        for col in range(image_width):
            pix = 0.0
            weight = 0
            for i in range(kernel_height):
                for j in range(kernel_width):
                    row_k = row + i - radius_h
                    col_k = col + j - radius_w
                    if border == 'transparent':
                        if 0 <= row_k < image_height and \
                                0 <= col_k < image_width:
                            pix += image[row_k, col_k] * kernel[i, j]
                            weight += kernel[i, j]
                    elif border == 'zero':
                        if 0 <= row_k < image_height and \
                                0 <= col_k < image_width:
                            pix += image[row_k, col_k] * kernel[i, j]
                        weight += kernel[i, j]
                    elif border == 'copy':
                        if row_k < 0:
                            row_k = 0
                        elif row_k >= image_height:
                            row_k = image_height - 1
                        if col_k < 0:
                            col_k = 0
                        elif col_k >= image_width:
                            col_k = image_width - 1
                        pix += image[row_k, col_k] * kernel[i, j]
                        weight += kernel[i, j]
                    elif border == 'reflect':
                        if row_k < 0:
                            row_k = np.abs(row_k)
                        elif row_k >= image_height:
                            row_k = 2 * (image_height - 1) - row_k
                        if col_k < 0:
                            col_k = np.abs(col_k)
                        elif col_k >= image_height:
                            col_k = 2 * (image_width - 1) - col_k
                        pix += image[row_k, col_k] * kernel[i, j]
                        weight += kernel[i, j]
                    else:
                        raise ValueError('border must be one of '
                                         '[transparent, zero, copy, reflect]')
            res[row, col] = np.float32(pix / weight)
    res = np.uint8(np.clip(np.round(res), 0, 255))
    return res


def main_gaussian_blur():
    radius = 20
    sigma_x = 10.0
    sigma_y = 10.0
    borders = ['transparent', 'zero', 'copy', 'reflect']

    image = cv2.imread('lena_std.bmp')
    kernel = gaussian_2d(radius, sigma_x, sigma_y, d_radius=1)

    for border in borders:
        blur_image = convolve_2d(image, kernel, border)
        cv2.imwrite('blur_radius=%d,sigma=(%0.1f,%0.1f),border=%s.png' % (
            radius, sigma_x, sigma_y, border), blur_image)

上記のコードは、計算プロセスの例としてのみ使用されています。Python の for ループは非常に遅いため、カーネルがわずかに大きい限り、上記のコードは非常に長い時間実行されます。画像を 2 方向に循環させる必要があるため、カーネルも 2 方向に循環させる必要があるため、全体の計算量は o(n^4) となり、非常に遅くなります。

通常のイメージのガウスぼかしに加えて、上記のコードはいくつかの境界条件も示しています。

  • 透過: 境界を越えたインデックスは計算に参加しません。このような境界条件では重みを蓄積し、最終的に蓄積された重みを正規化に使用する必要があります。この境界条件は重みを累積する必要があるため、計算量は若干多くなりますが、効果は数ある境界条件の中で最も良く安定しています。
  • ゼロ: 範囲外のインデックスは 0 に置き換えられます。0 は黒を表すため、カーネルが増加するにつれて、より多くの 0 が境界のぼやけに関与するため、この境界条件により黒いエッジが発生し、その影響は最悪になります。しかし、並列加速の場合、その実装は比較的簡単です。ニューラル ネットワークの畳み込みではよく使用されますが、従来の画像処理ではそのような境界条件を使用しないようにしてください。
  • copy: 境界を越えたインデックスは最も近いエッジのピクセル値を使用します。効果は非常に満足のいくもので、実装も簡単です。
  • 反映: 境界を越えたインデックス。境界を対称中心として画像内の対応する位置のピクセル値を取得します。インデックスの計算は少し面倒になります。特にカーネル サイズが非常に大きい場合は、ミラーインデックスが反対側の境界を超えています。ただし、カーネル サイズがそれほど極端ではない場合、この境界条件の影響は一般に悪くありません。

下の図は、4 の境界条件、半径 = 20、シグマ = 10 の影響です。
左上:透明、右上:ゼロ、
左下:コピー、右下:反射
画像の説明を追加してください

行と列を分離したガウスぼかし

ネストされたループを使用したガウスぼかしは、明らかに書き方が適切ではありません。実際には行と列を分ける形で書くことが多く、計算量はo(n^4)からo(n^3)に減ります。
上では、2 次元ガウス カーネル関数を次のように書きました。
G ( x , y ) = e − ( x 2 2 σ x 2 + y 2 2 σ y 2 ) G(x,y) = e^{-(\frac { x^2 }{2\sigma_x^2} + \frac{y^2 }{2\sigma_y^2})}G ( x ,y =e2P _バツ2バツ2+2P _y2y2)
この関数は逆アセンブルして、x と y を分離する形式で記述することができることに注意してください。G ( x ) = e − x 2 2 σ x 2 G ( y ) = e − y 2 2 σ y 2 G(x) = e^{-\frac{x^2 }{2\sigma_x^2} を覚えておいてください

} \\[2ex] G(y) = e^{-\frac{y^2 }{2\sigma_y^2}}G ( × )=e2P _バツ2バツ2G ( y )=e2P _y2y2
那么有:
G ( x , y ) = e − x 2 2 σ x 2 ⋅ e − y 2 2 σ y 2 = G ( x ) G ( y ) G(x,y) = e^{-\frac{ x^2 }{2\sigma_x^2}} \cdot e^{-\frac{y^2 }{2\sigma_y^2}}=G(x)G(y)G ( x ,y =e2P _バツ2バツ2e2P _y2y2=G ( x ) G ( y )
およびガウスぼかしの計算プロセスは次のとおりです。blur
( x , y ) = ∫ ∫ I ( x , y ) G ( x , y ) dxdy = ∫ ∫ I ( x , y ) G ( x ) G ( y ) dxdy = ∫ [ ∫ I ( x , y ) G ( x ) dx ] ⋅ G ( y ) dy (離散形式で記述) = ∑ yi [ ∑ xi I ( xi , yi ) G ( xi ) ] G ( yi ) \begin{aligned} Blur(x,y) &= \int \int I(x,y)G(x,y)dxdy \\[2ex] &= \int \int I(x, y)G(x)G(y)dxdy \\[2ex] &=\int [\int I(x,y)G(x)dx] \cdot G(y)dy \\ [2ex] & (離散形式として記述) \\[2ex] & =\sum_{y_i} [\sum_{x_i} I(x_i,y_i)G(x_i)]G(y_i) \end{aligned}ぼかし( x , _ _ _y =∫∫( x ,y ) G ( x y ) d x d y=∫∫( x ,y ) G ( x ) G ( y ) d x d y=[ ( x ,y ) G ( x ) d x ]G ( y ) d y(離散形式で書かれています)=y私は[バツ私は×私はy私は) G ( ×私は)] G ( y私は)

つまり、x 方向にブラーし、次に中間結果を y 方向にブラーします。毎回一方向にのみループし、2 つのループはネストされずに分離されます。次のコードは複数の境界条件を実装せず、透過型のみを書き込みます。

# -*- coding: utf-8 -*-
import cv2
import numpy as np


def gaussian_1d(radius, sigma, d_radius=1):
    xx = np.arange(-radius, radius + d_radius, d_radius)
    res = np.exp(-xx * xx / (sigma * sigma) / 2)
    res = res / np.sum(res)
    return res


def convolve_2d_xy(image, kernel_x, kernel_y):
    image_height, image_width = image.shape[:2]
    kernel_x_len = len(kernel_x)
    kernel_y_len = len(kernel_y)
    radius_x = kernel_x_len // 2
    radius_y = kernel_y_len // 2

    # check: kernel_height and kernel_width must be odd
    if radius_x * 2 + 1 != kernel_x_len or radius_y * 2 + 1 != kernel_y_len:
        raise ValueError('kernel size must be odd')

    res = np.zeros_like(image, dtype=np.float32)
    # convolve in x direction
    for row in range(image_height):
        for col in range(image_width):
            pix = 0.0
            weight = 0
            for j in range(kernel_x_len):
                col_k = col + j - radius_x
                if 0 <= col_k < image_width:
                    pix += image[row, col_k] * kernel_x[j]
                    weight += kernel_x[j]
            res[row, col] = pix / weight

    # convolve in y direction
    image = res.copy()
    for col in range(image_width):
        for row in range(image_height):
            pix = 0.0
            weight = 0
            for i in range(kernel_y_len):
                row_k = row + i - radius_y
                if 0 <= row_k < image_height:
                    pix += image[row_k, col] * kernel_y[i]
                    weight += kernel_y[i]
            res[row, col] = pix / weight
    res = np.uint8(np.clip(np.round(res), 0, 255))
    return res


def main_gaussian_blur_xy():
    radius = 20
    sigma_x = 10.0
    sigma_y = 10.0

    image = cv2.imread('lena_std.bmp')
    kernel_x = gaussian_1d(radius, sigma_x, d_radius=1)
    kernel_y = gaussian_1d(radius, sigma_y, d_radius=1)

    blur_image = convolve_2d_xy(image, kernel_x, kernel_y)
    cv2.imwrite('xy_blur_radius=%d,sigma=(%0.1f,%0.1f).png' % (
        radius, sigma_x, sigma_y), blur_image)

私のコンピューターでは、行と列の分離にかかる時間は約 45 秒、ネストされたループには約 1020 秒かかります。Python のループは非常に遅いため、今回の比較はカウントされないかもしれませんが、大きさの違いはより顕著です。

以下の左の図は前のネストされたループの結果、中央は行と列を分離した結果、右の図は 2 つの差の絶対値を 50 倍したものです。その結果、 2 つのあいまいな結果はまったく同じです。
画像の説明を追加してください

ステップ付きガウスぼかし

ファジー半径が非常に大きい場合、いくつかのステップを適切に追加して計算を節約できます。
このとき、期待されるぼかし効果を達成するには、画像パッチとカーネルの乗算と加算のインデックスにステップを追加する必要があるだけでなく、カーネル係数の計算にもステップを追加する必要があることに注意してください段差がない場合に比べて非常に大きくなります。

# -*- coding: utf-8 -*-
import cv2
import numpy as np
import time


def gaussian_1d(radius, sigma, d_radius=1.0):
    xx = np.arange(-radius, radius + d_radius, d_radius)
    res = np.exp(-xx * xx / (sigma * sigma) / 2)![请添加图片描述](https://img-blog.csdnimg.cn/0d509cd3e63a4f4680df49de7b807309.png)

    res = res / np.sum(res)
    return res


def convolve_2d_xy_step(image, kernel_x, kernel_y, step):
    image_height, image_width = image.shape[:2]
    kernel_x_len = len(kernel_x)
    kernel_y_len = len(kernel_y)
    radius_x = kernel_x_len // 2
    radius_y = kernel_y_len // 2

    # check: kernel_height and kernel_width must be odd
    if radius_x * 2 + 1 != kernel_x_len or radius_y * 2 + 1 != kernel_y_len:
        raise ValueError('kernel size must be odd')

    res = np.zeros_like(image, dtype=np.float32)
    # convolve in x direction
    for row in range(image_height):
        for col in range(image_width):
            pix = 0.0
            weight = 0
            for j in range(kernel_x_len):
                col_k = int(round(col + (j - radius_x) * step))
                if 0 <= col_k < image_width:
                    pix += image[row, col_k] * kernel_x[j]
                    weight += kernel_x[j]
            res[row, col] = pix / weight

    # convolve in y direction
    image = res.copy()
    for col in range(image_width):
        for row in range(image_height):
            pix = 0.0
            weight = 0
            for i in range(kernel_y_len):
                row_k = int(round(row + (i - radius_y) * step))
                if 0 <= row_k < image_height:
                    pix += image[row_k, col] * kernel_y[i]
                    weight += kernel_y[i]
            res[row, col] = pix / weight
    res = np.uint8(np.clip(np.round(res), 0, 255))
    return res


def main_gaussian_blur_xy():
    radius = 20
    sigma_x = 10.0
    sigma_y = 10.0
    step = 4.0

    image = cv2.imread('lena_std.bmp')
    kernel_x = gaussian_1d(radius, sigma_x, d_radius=step)
    kernel_y = gaussian_1d(radius, sigma_y, d_radius=step)

    blur_image = convolve_2d_xy_step(image, kernel_x, kernel_y, step)
    cv2.imwrite('xy_blur_radius=%d,sigma=(%0.1f,%0.1f)_step.png' % (
        radius, sigma_x, sigma_y), blur_image)


以下の左の図は前のネストされたループの結果、中央は行と列の分離 + step=4 の結果、右の図は 2 つの差の絶対値に 50 を掛けたものです。
この時点で、計算された diff はすべて 0 ではなくなり、中間結果をよく見ると、ブロック状のテクスチャがいくつか表示され、ブラーの品質が影響を受け始めていることがわかります。
画像の説明を追加してください

任意の 2D ガウス カーネルの生成

ここで、任意のガウス カーネルとは、任意のアスペクト比と回転角度を持つガウス カーネルを指します。
式導出の考え方は、ガウス カーネル G(x,y) を画像とみなして、画像回転の考え方を使用して 2 次元ガウス カーネルの式を変換することです。

以下に、ガウス カーネルの公式を再度示します。
G ( x , y ) = e − ( x 2 2 σ x 2 + y 2 2 σ y 2 ) G(x,y) = e^{-(\frac{x^2 }{2\sigma_x^2} + \frac{y^2 }{2\sigma_y^2})}G ( x ,y =e2P _バツ2バツ2+2P _y2y2)
画像回転の原理により、画像上の点(x,y)が円の中心を回転中心として反時計回りに回転すると、新しい点(x',y')の座標式は次のようになります。 : { x ' =
x ∗ cos θ − y ∗ sin θ y ' = x ∗ sin θ + y ∗ cos θ \begin{cases} x' = x*cos\theta - y*sin\theta \\ y' = x*sin\theta + y*cos\theta \end{cases} \\[4ex]{ バツ=バツcosθ _yθsy=バツθs+ycosθ _

これをガウス カーネル公式に代入し、便宜上 (x, y) として表すと、次のようになります。
G ( x , y ) = e − ( ( x ∗ cos θ − y ∗ sin θ ) 2 2 σ x 2 + ( x ∗ sin θ + y ∗ cos θ ) 2 2 σ y 2 ) = e − ( x 2 ∗ cos 2 θ + y 2 ∗ sin 2 θ − 2 xy ∗ sin θ cos θ 2 σ x 2 + x 2 sin 2 θ + y 2 ∗ cos 2 θ + 2 xy ∗ sin θ cos θ 2 σ y 2 ) = e − ( x 2 ( cos 2 θ 2 σ x 2 + sin 2 θ 2 σ y 2 ) + xy ( − sin 2 θ 2 σ x 2 + sin 2 θ 2 σ y 2 ) + y 2 ( sin 2 θ 2 σ x 2 + cos 2 θ 2 σ y 2 ) ) \begin{aligned} G(x,y) &= e^{-(\frac{(x*cos\theta - y*sin\theta)^2 }{2\sigma_x^2} + \frac{(x*sin\theta + y*cos\ theta)^2 }{2\sigma_y^2})} \\[2ex] &=e^{-(\frac{x^2*cos^2\theta + y^2*sin^2\theta -2xy *sin\theta cos\theta}{2\sigma_x^2} + \frac{x^2sin^2\theta + y^2*cos^2\theta + 2xy*sin\theta cos\theta }{2\sigma_y ^2})} \\[2ex] &=e^{-(x^2(\frac{cos^2\theta}{2\sigma_x^2} + \frac{sin^2\theta}{2\ sigma_y^2}) + xy(-\frac{sin2\theta}{2\sigma_x^2} + \frac{sin2\theta}{2\sigma_y^2}) + y^2( \frac{sin^2 \theta}{2\sigma_x^2} + \frac{cos^2\theta}{2\sigma_y^2} ))} \end{整列}G ( x ,y =e2P _バツ2( x cos θ y s in θ )2+2P _y2( x s in θ + y cos θ )2)=e2P _バツ2バツ2cos_ +y_2sin_2 θ2xysinθcosθ+2P _y2バツ2シン__ +y_2cos_2 θ+2xysinθcosθ_)=e( ×2 (2P _バツ2コス_2+2P _y2シン_ _2) + x y ( 2P _バツ2s _+2P _y2s _) + y2 (2P _バツ2シン_ _2+2P _y2コス_2) )

上の式は、任意の 2D ガウス カーネルがどのようになるかを示しています。
以下は、比較的横方向に長いカーネルを反時計回りに20度回転させるPythonの実装コードと図です。
画像の説明を追加してください

# -*- coding: utf-8 -*-
import cv2
import numpy as np


def generalized_gaussian_kernel(ksize,
                                sigma_x,
                                sigma_y=None,
                                angle=0.0):
    """
    Generate generalized gaussian kernel.

    Parameters
    ----------
    ksize: kernel size, one integer or a list/tuple of two integers, must be
        odd
    sigma_x: standard deviation in x direction
    sigma_y: standard deviation in y direction
    angle: rotate angle, anti-clockwise

    Returns
    -------
    kernel: generalized gaussian blur kernel
    """
    # check parameters
    if not isinstance(ksize, (tuple, list)):
        ksize = [ksize, ksize]
    else:
        ksize = list(ksize)
    ksize[0] = ksize[0] // 2 * 2 + 1
    ksize[1] = ksize[1] // 2 * 2 + 1

    if sigma_y is None:
        sigma_y = sigma_x

    # meshgrid coordinates
    radius_x = ksize[0] // 2
    radius_y = ksize[1] // 2
    x = np.arange(-radius_x, radius_x + 1, 1)
    y = np.arange(-radius_y, radius_y + 1, 1)
    xx, yy = np.meshgrid(x, y)

    # coefficients of coordinates
    angle = angle / 180 * np.pi
    cos_square = np.cos(angle) ** 2
    sin_square = np.sin(angle) ** 2
    sin_2 = np.sin(2 * angle)

    alpha = 0.5 / (sigma_x * sigma_x)
    beta = 0.5 / (sigma_y * sigma_y)
    a = cos_square * alpha + sin_square * beta
    b = -sin_2 * alpha + sin_2 * beta
    c = sin_square * alpha + cos_square * beta

    # generate and normalize kernel
    kernel = np.exp(-(a * xx * xx + b * xx * yy + c * yy * yy))
    kernel = kernel / np.sum(kernel)
    return kernel


if __name__ == '__main__':
    ksize = (511, 511)
    sigma_x = 100.0
    sigma_y = 20.0
    angle = 20.0

    gaussian_kernel = generalized_gaussian_kernel(ksize=ksize,
                                                  sigma_x=sigma_x,
                                                  sigma_y=sigma_y,
                                                  angle=angle)
    gaussian_kernel = gaussian_kernel / np.max(gaussian_kernel)
    gaussian_kernel = np.uint8(np.round(gaussian_kernel * 255))
    cv2.imwrite('generalized_gaussian_kernel_1.png', gaussian_kernel)

おすすめ

転載: blog.csdn.net/bby1987/article/details/131086530