我正在使用大小为 3x3 的 sobel 滤波器来计算图像导数。看网上的一些文章,尺寸 5x5 和 7x7 的 sobel 过滤器的内核似乎也很常见,但我找不到它们的内核值。
有人可以让我知道尺寸为 5x5 和 7x7 的 sobel 滤波器的内核值吗?此外,如果有人可以共享一种生成内核值的方法,那将非常有用。
提前致谢。
我正在使用大小为 3x3 的 sobel 滤波器来计算图像导数。看网上的一些文章,尺寸 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)。“of”方向的梯度为 (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) 有两个因数,原因如下:
我们感兴趣的是对特定方向(此处为 x)的梯度的贡献,因此我们需要将从中心像素到相邻像素的方向梯度投影到我们感兴趣的方向上。这是通过取标量积来实现的在各个方向上的单位向量,它引入了第一个因子 1/L(这里是对角线的 1/sqrt(2))。
梯度测量一个点的无穷小变化,我们用有限差分来近似。就线性方程而言,m = (y2-y1)/(x2-x1)。出于这个原因,从中心像素到相邻像素 (y2-y1) 的值差必须分布在它们的距离上(对应于 x2-x1),以便获得每个距离单位的上升单位。这产生了 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
第二条对角线贡献 (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 并获得著名的 Sobel 3x3 内核。
-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.
如果连接任意两个像素的向量的长度为 L,则该方向上的单位向量的前置因子为 1/L。出于这个原因,任何像素'k'对(比如说)x梯度(1,0)的贡献可以简化为“(平方距离上的值差)乘以(非归一化方向向量'ok'与梯度向量的DotProduct , 例如 (1,0) )"
gx_k = (k - o)/(pixel distance^2) ['ok' dot (1,0)].
因为连接向量与x单位向量的点积选择了对应的向量入口,所以位置k对应的G_x核入口正好
i / (i*i + j*j)
其中 i 和 j 是从中心像素到像素 k 在 x 和 y 方向上的步数。在上述 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 分量(它实际上是一个向量,如开头所述)。可以通过将梯度向量投影到 alpha-梯度单位向量上来获得任意方向 alpha 上的梯度(在数学上测量为正,在这种情况下为顺时针,因为正 y 向下)。
alpha 单位向量是 (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)
如果您想要最接近的整数近似值,请按照上述步骤操作。
其他来源似乎对较大的内核给出了不同的定义。例如,英特尔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 种不同定义——所以我怀疑(正如保罗所说)较大的内核是临时的,所以这绝不是确定的答案。
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])
请注意,为了成为“正确”的导数估计器,3x3 Sobel 应按 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)接受的答案不能轻易删除。)
谷歌似乎出现了很多结果,例如
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 答案使用 python 3 制作的简单解决方案。
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)
这是一个重要的结果,也是比原始论文更好的解释。它应该写在Wikipedia或其他地方,因为它似乎也优于互联网上任何其他关于该问题的讨论。
然而,正如声称的那样,对于尺寸大于 5*5 的滤波器,整数值表示实际上是不切实际的。使用 64 位整数,可以精确表达最大 15*15 的 Sobel 滤波器大小。
这是前四个;结果应除以“权重”,以便将如下图所示图像区域的梯度归一化为值 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}"
TL;DR:改用高斯导数运算符。
正如Adam Bowen 在他的回答中解释的那样,Sobel 核是沿一个轴的平滑和沿另一个轴的中心差分导数的组合:
sob3x3 = [1 2 1]' * [1 0 -1]
平滑增加了正则化(降低对噪声的敏感性)。
(我1/8
在这篇文章中省略了所有因素, Sobel 本人也是如此,这意味着运算符确定了缩放的导数。此外,*
在这篇文章中总是意味着卷积。)
让我们概括一下:
deriv_kernel = smoothing_kernel * d/dx
卷积的特性之一是
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)
也就是说,通过这个广义的、理想化的导数核,我们可以计算平滑图像的真实导数。索贝尔核当然不是这种情况,因为它使用了对导数的中心差分近似。但是选择一个更好的smoothing_kernel
,这是可以实现的。高斯核是这里的理想选择,因为它在空间域的紧凑性(小内核占用空间)与频域的紧凑性(良好的平滑性)之间提供了最佳折衷。此外,高斯是完全各向同性和可分离的。使用高斯导数核会产生最好的正则化导数算子。
因此,如果您正在寻找更大的 Sobel 算子,因为您需要更多的正则化,请改用高斯导数算子。
让我们再分析一下 Sobel 核。
平滑内核是三角形的,带有样本[1 2 1]
。这是一个三角函数,采样后得出这三个值:
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]
这与Adam Bowen 给出的建议完全不同,他建议将内核与沿每个维度的 3-tab 三角形内核进行卷积:[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 内核扩展实际上都不是 Sobel 内核,因为 Sobel 内核明确地是一个 3x3 内核(参见 Sobel 关于他的操作符的历史记录,他从未真正发表过)。
另请注意,我并不是在提倡从这里派生的扩展 Sobel 内核。使用高斯导数!
根据@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;
}
}
}
希望能帮助到你。
谢谢大家,我将尝试@Adam Bowen 的第二个变体,为这个变体获取 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,@Paul R 方法 - №1。现在我使用最后一个,因为它提供了更流畅的结果,并且使用此代码很容易生成内核。
我对Daniel's answer做了一个 Python NumPy 实现。它似乎比Joao Ponte 的实现快 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
丹尼尔答案的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';