サイズ3x3のソーベルフィルターを使用して、画像の導関数を計算しています。インターネット上のいくつかの記事を見ると、サイズ5x5および7x7のsobelフィルターのカーネルも一般的であるようですが、それらのカーネル値を見つけることができません。
サイズ5x5および7x7のsobelフィルターのカーネル値を教えてもらえますか?また、誰かがカーネル値を生成するメソッドを共有できれば、それは非常に便利です。
前もって感謝します。
サイズ3x3のソーベルフィルターを使用して、画像の導関数を計算しています。インターネット上のいくつかの記事を見ると、サイズ5x5および7x7のsobelフィルターのカーネルも一般的であるようですが、それらのカーネル値を見つけることができません。
サイズ5x5および7x7のsobelフィルターのカーネル値を教えてもらえますか?また、誰かがカーネル値を生成するメソッドを共有できれば、それは非常に便利です。
前もって感謝します。
tl; dr:セクション「例」にスキップ
別の解決策を追加するには、このドキュメントを拡張します(特に高品質ではありませんが、2ページの下部から使用可能なグラフィックとマトリックスがいくつか示されています)。
私たちがやろうとしているのは、位置(x、y)での画像の局所的な勾配を推定することです。勾配は、x方向とy方向、gxとgyの成分で構成されるベクトルです。
ここで、ピクセル(x、y)とその隣接ピクセルに基づいて、カーネル操作(3x3、5x5、または任意のサイズ)として勾配を概算したいとします。
すべての隣接中心ペアの勾配方向への投影を合計することにより、勾配を概算できます。(Sobelのカーネルは、さまざまな寄与を重み付けするための特定の方法であり、基本的にPrewittも同様です)。
これはローカル画像で、中央のピクセル(x、y)は「o」(中央)とマークされています
a b c
d o f
g h i
正のx方向のグラデーションが必要だとします。正のx方向の単位ベクトルは(1,0)です[正のy方向はDOWN、つまり(0,1)であり、(0,0)は画像の左上にあるという規則を後で使用します) 。]
oからf(略して'of')へのベクトルは(1,0)です。'の方向'の勾配は(f --o)/ 1です(ここではピクセルでの画像の値はfから中心oでの値を引いたもので、それらのピクセル間の距離で割ったものです)。その特定の隣接勾配の単位ベクトルを内積を介して目的の勾配方向(1,0)に投影すると、1が得られます。これは、より簡単なケースから始めて、すべての隣接勾配の寄与を示す小さな表です。対角線の場合、それらの距離はsqrt2であり、対角線方向の単位ベクトルは1 / sqrt2 *(+/- 1、+/- 1)であることに注意してください。
f: (f-o)/1 * 1
d: (d-o)/1 * -1 because (-1, 0) dot (1, 0) = -1
b: (b-o)/1 * 0 because (0, -1) dot (1, 0) = 0
h: (h-o)/1 * 0 (as per b)
a: (a-o)/sqrt2 * -1/sqrt2 distance is sqrt2, and 1/sqrt2*(-1,-1) dot (1,0) = -1/sqrt2
c: (c-o)/sqrt2 * +1/sqrt2 ...
g: (g-o)/sqrt2 * -1/sqrt2 ...
i: (i-o)/sqrt2 * +1/sqrt2 ...
明確にするために編集:次の理由により、1 / sqrt(2)には2つの要素があります。
特定の方向(ここではx)の勾配への寄与に関心があるため、中心ピクセルから隣接ピクセルへの方向勾配を関心のある方向に投影する必要があります。これは、内積を取ることによって実現されます。それぞれの方向の単位ベクトルの、最初の係数1 / L(ここでは対角線の1 / sqrt(2))を導入します。
勾配は、ある点での微小な変化を測定します。これは、有限差分で近似されます。一次方程式では、m =(y2-y1)/(x2-x1)です。このため、距離単位あたりの上昇単位を取得するには、中央のピクセルから隣接するピクセル(y2-y1)までの値の差をそれらの距離(x2-x1に対応)に分散させる必要があります。これにより、2番目の係数1 / Lが得られます(ここでは、対角線の1 / sqrt(2))
さて、これで貢献がわかりました。ピクセル寄与の反対のペアを組み合わせることにより、この式を単純化しましょう。dとfから始めましょう:
{(f-o)/1 * 1} + {(d-o)/1 * -1}
= f - o - (d - o)
= f - d
今最初の対角線:
{(c-o)/sqrt2 * 1/sqrt2} + {(g-o)/sqrt2 * -1/sqrt2}
= (c - o)/2 - (g - o)/2
= (c - g)/2
2番目の対角線は(i --a)/2に寄与します。垂直方向はゼロに寄与します。中央のピクセル「o」からのすべての寄与が消えることに注意してください。
これで、ピクセル(x、y)での正のx方向の勾配に対するすべての最近傍の寄与が計算されたため、x方向の勾配の合計近似は単純にそれらの合計になります。
gx(x,y) = f - d + (c - g)/2 + (i - a)/2
対応する隣接ピクセルの代わりに係数が書き込まれる畳み込みカーネルを使用しても、同じ結果を得ることができます。
-1/2 0 1/2
-1 0 1
-1/2 0 1/2
分数を処理したくない場合は、これに2を掛けて、よく知られているSobel3x3カーネルを取得します。
-1 0 1
G_x = -2 0 2
-1 0 1
2を掛けると、便利な整数が得られるだけです。出力画像のスケーリングは基本的に任意であり、ほとんどの場合、とにかく(はっきりと見える結果を得るために)画像範囲に正規化します。
上記と同じ理由で、正のy方向(0,1)で単位ベクトルに隣接する寄与を投影することにより、垂直勾配gyのカーネルを取得します。
-1 -2 -1
G_y = 0 0 0
1 2 1
5x5以上のカーネルが必要な場合は、距離だけに注意を払う必要があります。
A B 2 B A
B C 1 C B
2 1 - 1 2
B C 1 C B
A B 2 B A
どこ
A = 2 * sqrt2
B = sqrt5
C = sqrt2.
任意の2つのピクセルを接続するベクトルの長さがLの場合、その方向の単位ベクトルのプリファクターは1/Lになります。このため、x勾配(1,0)への任意のピクセル'k'の寄与は、 "(距離の2乗での値の差)倍(正規化されていない方向ベクトル'ok'と勾配ベクトルの内積)に簡略化できます。 、例:(1,0)) "
gx_k = (k - o)/(pixel distance^2) ['ok' dot (1,0)].
接続ベクトルとx単位ベクトルの内積が対応するベクトルエントリを選択するため、位置kの対応するG_xカーネルエントリは
i / (i*i + j*j)
ここで、iとjは、xおよびy方向の中心ピクセルからピクセルkまでのステップ数です。上記の3x3計算では、ピクセル'a'はi= -1(左に1)、j = -1(上に1)であるため、'a'カーネルエントリは-1/(1 + 1 )=-1/2。
G_yカーネルのエントリは次のとおりです。
j/(i*i + j*j).
カーネルに整数値が必要な場合は、次の手順に従います。
要約すると:
Gx_ij = i / (i*i + j*j)
Gy_ij = j / (i*i + j*j)
ここで、i、jは、中心から数えたカーネル内の位置です。必要に応じてカーネルエントリをスケーリングして、整数(または少なくとも近似値)を取得します。
これらの式は、すべてのカーネルサイズに当てはまります。
-2/8 -1/5 0 1/5 2/8 -5 -4 0 4 5
-2/5 -1/2 0 1/2 2/5 -8 -10 0 10 8
G_x (5x5) -2/4 -1/1 0 1/1 2/4 (*20) = -10 -20 0 20 10
-2/5 -1/2 0 1/2 2/5 -8 -10 0 10 8
-2/8 -1/5 0 1/5 2/8 -5 -4 0 4 5
フロート表記の5x5カーネルの中央の3x3ピクセルは、3x3カーネルにすぎないことに注意してください。つまり、より大きなカーネルは、追加の、しかしより低い重みのデータによる継続的な近似を表します。これは、より大きなカーネルサイズに続きます。
-3/18 -2/13 -1/10 0 1/10 2/13 3/18
-3/13 -2/8 -1/5 0 1/5 2/8 3/13
-3/10 -2/5 -1/2 0 1/2 2/5 3/10
G_x (7x7) -3/9 -2/4 -1/1 0 1/1 2/4 3/9
-3/10 -2/5 -1/2 0 1/2 2/5 3/10
-3/13 -2/8 -1/5 0 1/5 2/8 3/13
-3/18 -2/13 -1/10 0 1/10 2/13 3/18
この時点で、正確な整数表現は実用的ではなくなります。
私が知る限り(元の論文にアクセスできない)、これに対する「Sobel」の部分は、貢献を適切に重み付けしています。Prewittの解は、距離の重み付けを省略し、必要に応じてカーネルにiとjを入力するだけで取得できます。
したがって、画像勾配のx成分とy成分を概算できます(これは、最初に述べたように、実際にはベクトルです)。任意の方向のアルファ(数学的に正、この場合は正のyが下向きであるため時計回りに測定)の勾配は、勾配ベクトルをアルファ勾配単位ベクトルに投影することによって取得できます。
アルファ単位ベクトルは(cos alpha、sin alpha)です。alpha = 0°の場合、gxの結果を取得でき、alpha = 90°の場合、gyを取得します。
g_alpha = (alpha-unit vector) dot (gx, gy)
= (cos a, sin a) dot (gx, gy)
= cos a * gx + sin a * gy
gxとgyを隣接する寄与の合計として書き留めるのが面倒な場合は、結果の長い式を同じ隣接ピクセルに適用される項でグループ化し、これをエントリを含む単一の畳み込みカーネルとして書き直すことができることに気付きます。
G_alpha_ij = (i * cos a + j * sin a)/(i*i + j*j)
最も近い整数近似が必要な場合は、上記の手順に従ってください。
他の情報源は、より大きなカーネルの異なる定義を提供しているようです。たとえば、Intel IPPライブラリは、5x5カーネルを次のように提供します。
1 2 0 -2 -1
4 8 0 -8 -4
6 12 0 -12 -6
4 8 0 -8 -4
1 2 0 -2 -1
直感的には、中心に近い要素にもっと注意を払っているので、これは私にとってより理にかなっています。また、3x3カーネルに関しては自然な定義があり、より大きなカーネルを生成するために簡単に拡張できます。とは言うものの、私の簡単な検索で、5x5カーネルの3つの異なる定義を見つけました-したがって、(Paulが言うように)より大きなカーネルはアドホックであると思います。したがって、これは決して決定的な答えではありません。
3x3カーネルは、平滑化カーネルと勾配カーネルの外積です。Matlabでは、これは次のようなものです。
sob3x3 = [ 1 2 1 ]' * [1 0 -1]
より大きなカーネルは、3x3カーネルを別の平滑化カーネルと畳み込むことによって定義できます。
sob5x5 = conv2( [ 1 2 1 ]' * [1 2 1], sob3x3 )
このプロセスを繰り返して、徐々に大きくなるカーネルを取得できます
sob7x7 = conv2( [ 1 2 1 ]' * [1 2 1], sob5x5 )
sob9x9 = conv2( [ 1 2 1 ]' * [1 2 1], sob7x7 )
...
それを書く方法は他にもたくさんありますが、これは何が最もよく起こっているかを正確に説明していると思います。基本的に、一方向の平滑化カーネルと他の方向の導関数の有限差分推定から始めて、必要なカーネルサイズが得られるまで平滑化を適用します。
これは一連の畳み込みであるため、すべての優れたプロパティ(可換性、結合性など)が保持され、実装に役立つ可能性があります。たとえば、5x5カーネルをその平滑化コンポーネントと派生コンポーネントに簡単に分離できます。
sob5x5 = conv([1 2 1],[1 2 1])' * conv([1 2 1],[-1 0 1])
「適切な」微分推定量であるためには、3x3Sobelを1/8の係数でスケーリングする必要があることに注意してください。
sob3x3 = 1/8 * [ 1 2 1 ]' * [1 0 -1]
また、大きなカーネルはそれぞれ1/16の追加係数でスケーリングする必要があります(平滑化カーネルが正規化されていないため)。
sob5x5 = 1/16 * conv2( [ 1 2 1 ]' * [1 2 1], sob3x3 )
sob7x7 = 1/16 * conv2( [ 1 2 1 ]' * [1 2 1], sob5x5 )
...
更新2018年4月23日:以下のリンクで定義されているカーネルは、真のSobelカーネル(5x5以降の場合)ではないようです-エッジ検出の合理的な仕事をする可能性がありますが、Sobelカーネルと呼ばれるべきではありません。より正確で包括的な要約については、ダニエルの回答を参照してください。(この回答は、(a)さまざまな場所からリンクされており、(b)受け入れられた回答は簡単に削除できないため、ここに残しておきます。)
Googleは多くの結果を出しているようです。たとえば、
http: //rsbweb.nih.gov/nih-image/download/user-macros/slowsobel.macroは、3x3、5x5、7x7、および9x9の次のカーネルを提案しています。
3x3:
1 0 -1
2 0 -2
1 0 -1
5x5:
2 1 0 -1 -2
3 2 0 -2 -3
4 3 0 -3 -4
3 2 0 -2 -3
2 1 0 -1 -2
7x7:
3 2 1 0 -1 -2 -3
4 3 2 0 -2 -3 -4
5 4 3 0 -3 -4 -5
6 5 4 0 -4 -5 -6
5 4 3 0 -3 -4 -5
4 3 2 0 -2 -3 -4
3 2 1 0 -1 -2 -3
9x9:
4 3 2 1 0 -1 -2 -3 -4
5 4 3 2 0 -2 -3 -4 -5
6 5 4 3 0 -3 -4 -5 -6
7 6 5 4 0 -4 -5 -6 -7
8 7 6 5 0 -5 -6 -7 -8
7 6 5 4 0 -4 -5 -6 -7
6 5 4 3 0 -3 -4 -5 -6
5 4 3 2 0 -2 -3 -4 -5
4 3 2 1 0 -1 -2 -3 -4
これは、numpyと@Danielの回答を使用してPython3で作成された簡単なソリューションです。
def custom_sobel(shape, axis):
"""
shape must be odd: eg. (5,5)
axis is the direction, with 0 to positive x and 1 to positive y
"""
k = np.zeros(shape)
p = [(j,i) for j in range(shape[0])
for i in range(shape[1])
if not (i == (shape[1] -1)/2. and j == (shape[0] -1)/2.)]
for j, i in p:
j_ = int(j - (shape[0] -1)/2.)
i_ = int(i - (shape[1] -1)/2.)
k[j,i] = (i_ if axis==0 else j_)/float(i_*i_ + j_*j_)
return k
次のようにカーネル(5,5)を返します。
Sobel x:
[[-0.25 -0.2 0. 0.2 0.25]
[-0.4 -0.5 0. 0.5 0.4 ]
[-0.5 -1. 0. 1. 0.5 ]
[-0.4 -0.5 0. 0.5 0.4 ]
[-0.25 -0.2 0. 0.2 0.25]]
Sobel y:
[[-0.25 -0.4 -0.5 -0.4 -0.25]
[-0.2 -0.5 -1. -0.5 -0.2 ]
[ 0. 0. 0. 0. 0. ]
[ 0.2 0.5 1. 0.5 0.2 ]
[ 0.25 0.4 0.5 0.4 0.25]]
Pythonでそれを行うためのより良い方法を誰かが知っているなら、私に知らせてください。私はまだ初心者です;)
(この回答は、上記の@Danielによる分析を参照しています。)
Gx[i,j] = i / (i*i + j*j)
Gy[i,j] = j / (i*i + j*j)
これは重要な結果であり、元の論文よりも優れた説明です。インターネットで入手できる問題に関する他の議論よりも優れているように思われるため、ウィキペディアまたはどこかに記載する必要があります。
ただし、主張されているように、整数値の表現が5*5より大きいサイズのフィルターに対して実用的でないことは実際には真実ではありません。64ビット整数を使用すると、最大15*15のSobelフィルターサイズを正確に表現できます。
これが最初の4つです。結果を「重み」で割って、次のような画像領域の勾配が値1に正規化されるようにする必要があります。
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
1 2 3 4 5
Gx(3):
-1/2 0/1 1/2 -1 0 1
-1/1 0 1/1 * 2 = -2 0 2
-1/2 0/1 1/2 -1 0 1
weight = 4 weight = 8
Gx(5):
-2/8 -1/5 0/4 1/5 2/8 -5 -4 0 4 5
-2/5 -1/2 0/1 1/2 2/5 -8 -10 0 10 8
-2/4 -1/1 0 1/1 2/4 * 20 = -10 -20 0 20 10
-2/5 -1/2 0/1 1/2 2/5 -8 -10 0 10 8
-2/8 -1/5 0/4 1/5 2/8 -5 -4 0 4 5
weight = 12 weight = 240
Gx(7):
-3/18 -2/13 -1/10 0/9 1/10 2/13 3/18 -130 -120 -78 0 78 120 130
-3/13 -2/8 -1/5 0/4 1/5 2/8 3/13 -180 -195 -156 0 156 195 180
-3/10 -2/5 -1/2 0/1 1/2 2/5 3/10 -234 -312 -390 0 390 312 234
-3/9 -2/4 -1/1 0 1/1 2/4 3/9 * 780 = -260 -390 -780 0 780 390 260
-3/10 -2/5 -1/2 0/1 1/2 2/5 3/10 -234 -312 -390 0 390 312 234
-3/13 -2/8 -1/5 0/4 1/5 2/8 3/13 -180 -195 -156 0 156 195 180
-3/18 -2/13 -1/10 0/9 1/10 2/13 3/18 -130 -120 -78 0 78 120 130
weight = 24 weight = 18720
Gx(9):
-4/32 -3/25 -2/20 -1/17 0/16 1/17 2/20 3/25 4/32 -16575 -15912 -13260 -7800 0 7800 13260 15912 16575
-4/25 -3/18 -2/13 -1/10 0/9 1/10 2/13 3/18 4/25 -21216 -22100 -20400 -13260 0 13260 20400 22100 21216
-4/20 -3/13 -2/8 -1/5 0/4 1/5 2/8 3/13 4/20 -26520 -30600 -33150 -26520 0 26520 33150 30600 26520
-4/17 -3/10 -2/5 -1/2 0/1 1/2 2/5 3/10 4/17 -31200 -39780 -53040 -66300 0 66300 53040 39780 31200
-4/16 -3/9 -2/4 -1/1 0 1/1 2/4 3/9 4/16 * 132600 = -33150 -44200 -66300 -132600 0 132600 66300 44200 33150
-4/17 -3/10 -2/5 -1/2 0/1 1/2 2/5 3/10 4/17 -31200 -39780 -53040 -66300 0 66300 53040 39780 31200
-4/20 -3/13 -2/8 -1/5 0/4 1/5 2/8 3/13 4/20 -26520 -30600 -33150 -26520 0 26520 33150 30600 26520
-4/25 -3/18 -2/13 -1/10 0/9 1/10 2/13 3/18 4/25 -21216 -22100 -20400 -13260 0 13260 20400 22100 21216
-4/32 -3/25 -2/20 -1/17 0/16 1/17 2/20 3/25 4/32 -16575 -15912 -13260 -7800 0 7800 13260 15912 16575
weight = 40 weight = 5304000
以下に追加するRubyプログラムは、Sobelフィルターとそれに対応する任意のサイズの重みを計算しますが、整数値のフィルターは15*15より大きいサイズには役立たない可能性があります。
#!/usr/bin/ruby
# Sobel image gradient filter generator
# by <ian_bruce@mail.ru> -- Sept 2017
# reference:
# https://stackoverflow.com/questions/9567882/sobel-filter-kernel-of-large-size
if (s = ARGV[0].to_i) < 3 || (s % 2) == 0
$stderr.puts "invalid size"
exit false
end
s /= 2
n = 1
# find least-common-multiple of all fractional denominators
(0..s).each { |j|
(1..s).each { |i|
d = i*i + j*j
n = n.lcm(d / d.gcd(i))
}
}
fw1 = format("%d/%d", s, 2*s*s).size + 2
fw2 = format("%d", n).size + 2
weight = 0
s1 = ""
s2 = ""
(-s..s).each { |y|
(-s..s).each { |x|
i, j = x, y # "i, j = y, x" for transpose
d = i*i + j*j
if (i != 0)
if (n * i % d) != 0 # this should never happen
$stderr.puts "inexact division: #{n} * #{i} / ((#{i})^2 + (#{j})^2)"
exit false
end
w = n * i / d
weight += i * w
else
w = 0
end
s1 += "%*s" % [fw1, d > 0 ? "%d/%d" % [i, d] : "0"]
s2 += "%*d" % [fw2, w]
}
s1 += "\n" ; s2 += "\n"
}
f = n.gcd(weight)
puts s1
puts "\nweight = %d%s" % [weight/f, f < n ? "/%d" % (n/f) : ""]
puts "\n* #{n} =\n\n"
puts s2
puts "\nweight = #{weight}"
@Paul Rの例に基づいて、アルゴリズムをすばやくハッキングして、任意の奇数サイズ>1のSobelカーネルを生成しました。
public static void CreateSobelKernel(int n, ref float[][] Kx, ref float[][] Ky)
{
int side = n * 2 + 3;
int halfSide = side / 2;
for (int i = 0; i < side; i++)
{
int k = (i <= halfSide) ? (halfSide + i) : (side + halfSide - i - 1);
for (int j = 0; j < side; j++)
{
if (j < halfSide)
Kx[i][j] = Ky[j][i] = j - k;
else if (j > halfSide)
Kx[i][j] = Ky[j][i] = k - (side - j - 1);
else
Kx[i][j] = Ky[j][i] = 0;
}
}
}
それが役に立てば幸い。
TL; DR:代わりにガウス微分演算子を使用してください。
Adam Bowenが彼の回答で説明したように、Sobelカーネルは、一方の軸に沿った平滑化と、もう一方の軸に沿った中央差分導関数の組み合わせです。
sob3x3 = [1 2 1]' * [1 0 -1]
平滑化により、正則化が追加されます(ノイズに対する感度が低下します)。
( Sobel自身が行ったよう1/8
に、この投稿ではすべての要素を省略しています。つまり、演算子はスケーリングまでの導関数を決定します。また、この投稿では常に畳み込みを意味します。)*
これを一般化しましょう:
deriv_kernel = smoothing_kernel * d/dx
畳み込みの特性の1つは、
d/dx f = d/dx * f
つまり、要素微分演算子を使用して画像を畳み込むと、画像の微分が得られます。畳み込みが可換であることにも注意してください。
deriv_kernel = d/dx * smoothing_kernel = d/dx smoothing_kernel
つまり、微分カーネルは平滑化カーネルの微分です。
このようなカーネルを畳み込みによって画像に適用することに注意してください。
image * deriv_kernel = image * smoothing_kernel * d/dx = d/dx (image * smoothing_kernel)
つまり、この一般化された理想化された導関数カーネルを使用して、平滑化された画像の真の導関数を計算できます。もちろん、これはSobelカーネルには当てはまりません。これは、導関数の中心差分近似を使用するためです。しかし、より良いものを選択smoothing_kernel
すれば、これを達成することができます。Gaussianカーネルは、空間領域でのコンパクトさ(カーネルのフットプリントが小さい)と周波数領域でのコンパクトさ(良好な平滑化)の間の最良の妥協点を提供するため、ここでは理想的なオプションです。さらに、ガウス分布は完全に等方性で分離可能です。ガウス微分カーネルを使用すると、可能な限り最高の正則化微分演算子が得られます。
したがって、より大きなSobel演算子を探している場合は、より正則化が必要なため、代わりにガウス微分演算子を使用してください。
Sobelカーネルをもう少し分析してみましょう。
平滑化カーネルは三角形で、サンプルがあります[1 2 1]
。これは三角形関数であり、サンプリングすると、次の3つの値になります。
2 + x , if -2 < x < 0
h = { 2 , if x = 0
2 - x , if 0 < x < 2
その派生物は次のとおりです。
1 , if -2 < x < 0
d/dx h = { 0 , if x = 0 (not really, but it's the sensible solution)
-1 , if 0 < x < 2
したがって、中央差分導関数近似は、平滑化に使用されたのと同じ三角形関数の分析導関数のサンプリングと見なすことができます。したがって、次のようになります。
sob3x3 = [1 2 1]' * d/dx [1 2 1] = d/dx ( [1 2 1]' * [1 2 1] )
したがって、このカーネルを大きくしたい場合は、平滑化カーネルを単純に拡大します。
sob5x5 = d/dx ( [1 2 3 2 1]' * [1 2 3 2 1] ) = [1 2 3 2 1]' * [1 1 0 -1 -1]
sob7x7 = d/dx ( [1 2 3 4 3 2 1]' * [1 2 3 4 3 2 1] ) = [1 2 3 4 3 2 1]' * [1 1 1 0 -1 -1 -1]
これは、カーネルを各次元に沿って3タブの三角形カーネルで畳み込むことを提案しているAdamBowenによるアドバイスとはまったく異なります。中心極限定理により、この三角形のカーネルをそれ自体で畳み込むと、ガウス分布をもう少し近似するフィルターが生成されることに注意してください。それ自体との畳み込みを繰り返すことによって作成するカーネルが大きいほど、このガウス分布に近似します。したがって、この方法を使用する代わりに、ガウス関数を直接サンプリングすることもできます。[1 2 1] * [1 2 1] = [1 4 6 4 1]
[1 2 1] * [1 0 -1] = [1 2 0 -2 -1]
Danielには長い投稿があり、Sobelカーネルをさらに別の方法で拡張することを提案しています。ここでの平滑化カーネルの形状はガウス近似とは異なります。私はその特性を研究しようとはしていません。
Sobelカーネルは明示的に3x3カーネルであるため、Sobelカーネルのこれら3つの可能な拡張は、実際にはSobelカーネルではないことに注意してください(実際に公開したことのない、Sobelによる演算子に関する履歴ノートを参照してください)。
ここで派生した拡張Sobelカーネルを推奨していないことにも注意してください。ガウス導関数を使用してください!
おかげさまで、@ Adam Bowenによる2番目のバリアントを試して、Sobel5x5、7x7、9x9のC#コードを取得します...このバリアントのマトリックス生成(バグを見つけた場合、またはコードを最適化できる場合は、バグがある可能性があります-そこに記述してください):
static void Main(string[] args)
{
float[,] Sobel3x3 = new float[,] {
{-1, 0, 1},
{-2, 0, 2},
{-1, 0, 1}};
float[,] Sobel5x5 = Conv2DforSobelOperator(Sobel3x3);
float[,] Sobel7x7 = Conv2DforSobelOperator(Sobel5x5);
Console.ReadKey();
}
public static float[,] Conv2DforSobelOperator(float[,] Kernel)
{
if (Kernel == null)
throw new Exception("Kernel = null");
if (Kernel.GetLength(0) != Kernel.GetLength(1))
throw new Exception("Kernel matrix must be Square matrix!");
float[,] BaseMatrix = new float[,] {
{1, 2, 1},
{2, 4, 2},
{1, 2, 1}};
int KernelSize = Kernel.GetLength(0);
int HalfKernelSize = KernelSize / 2;
int OutSize = KernelSize + 2;
if ((KernelSize & 1) == 0) // Kernel_Size must be: 3, 5, 7, 9 ...
throw new Exception("Kernel size must be odd (3x3, 5x5, 7x7...)");
float[,] Out = new float[OutSize, OutSize];
float[,] InMatrix = new float[OutSize, OutSize];
for (int x = 0; x < BaseMatrix.GetLength(0); x++)
for (int y = 0; y < BaseMatrix.GetLength(1); y++)
InMatrix[HalfKernelSize + x, HalfKernelSize + y] = BaseMatrix[x, y];
for (int x = 0; x < OutSize; x++)
for (int y = 0; y < OutSize; y++)
for (int Kx = 0; Kx < KernelSize; Kx++)
for (int Ky = 0; Ky < KernelSize; Ky++)
{
int X = x + Kx - HalfKernelSize;
int Y = y + Ky - HalfKernelSize;
if (X >= 0 && Y >= 0 && X < OutSize && Y < OutSize)
Out[x, y] += InMatrix[X, Y] * Kernel[KernelSize - 1 - Kx, KernelSize - 1 - Ky];
}
return Out;
}
結果(NormalMap)またはそこにコピーします。この方法は-№2、@PaulR方法-№1です。これで最後を使用します。これは、よりスムーズな結果が得られ、このコードを使用してカーネルを簡単に生成できるためです。
ダニエルの答えのMatlab実装:
kernel_width = 9;
halfway = floor(kernel_width/2);
step = -halfway : halfway;
i_matrix = repmat(step,[kernel_width 1]);
j_matrix = i_matrix';
gx = i_matrix ./ ( i_matrix.*i_matrix + j_matrix.*j_matrix );
gx(halfway+1,halfway+1) = 0; % deals with NaN in middle
gy = gx';
Danielの答えをPythonNumPyで実装しました。JoaoPonteの実装よりも約3倍速いようです。
def calc_sobel_kernel(target_shape: tuple[int, int]):
assert target_shape[0] % 2 != 0
assert target_shape[1] % 2 != 0
gx = np.zeros(target_shape, dtype=np.float32)
gy = np.zeros(target_shape, dtype=np.float32)
indices = np.indices(target_shape, dtype=np.float32)
cols = indices[0] - target_shape[0] // 2
rows = indices[1] - target_shape[1] // 2
squared = cols ** 2 + rows ** 2
np.divide(cols, squared, out=gy, where=squared!=0)
np.divide(rows, squared, out=gx, where=squared!=0)
return gx, gy