Ядро фильтра Sobel большого размера

Я использую фильтр Собеля размера 3x3 для вычисления производной изображения. Глядя на некоторые статьи в интернете, кажется, что ядра для sobel filter для размера 5x5 и 7x7 также распространены, но я не могу найти их значения ядра.

может кто-нибудь, пожалуйста, дайте мне знать значения ядра для фильтра sobel размером 5x5 и 7x7? Кроме того, если кто-то может поделиться методом генерации значений ядра, это будет очень полезно.

спасибо заранее.

8 ответов


UPDATE 23-Apr-2018: Похоже, что ядра, определенные в приведенной ниже ссылке, не являются истинными ядрами 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


другие источники дают различные определения больших ядер. Например, библиотека 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, которое легко расширить для создания больших ядер. Тем не менее, в моем кратком поиске я нашел 3 разных определения ядра 5x5 - так что я подозреваю что (как говорит Павел) большие ядра являются ad hoc, и поэтому это отнюдь не окончательный ответ.

ядро 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 )
...

полное решение для произвольных размеров и углов ядра Собеля

tl; dr: перейдите к разделу "примеры"

чтобы добавить другое решение, разверните на документ (это не особенно высокое качество, но он показывает некоторые полезные графики и матрицы, начиная с нижней части страницы 2).

цель

то,что мы пытаемся сделать, это оценить локальный градиент изображения в позиции (x, y). Этот градиент-это вектор, состоящий из компонентов в направлении x и y, gx и gy.

теперь представьте,что мы хотим аппроксимировать градиент на основе нашего пикселя (x, y) и его соседей как операцию ядра (3x3, 5x5 или любого размера).

Идеи Решения

мы можем аппроксимировать градиент, суммируя проекции всех пар соседних центров на направление градиента. (Ядра Собеля-это просто конкретный метод взвешивания различных вклады, как и Пруитт, в основном).

явные промежуточные шаги для 3x3

это локальное изображение, центральный пиксель (x,y), помеченный как " o " (центр)

a b c
d o f
g h i

Предположим, нам нужен градиент в положительном направлении x. Единичный вектор в положительном направлении x равен (1,0) [позже я буду использовать соглашение о том, что положительное направление y вниз, т. е. (0,1), и что (0,0) является верхним левым от изображения).]

вектор от o до f ('of' для short) is (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) по следующей причине:

  1. нас интересует вклад в градиент в направлении (здесь x), поэтому нам нужно спроецировать градиент направления от центрального пикселя к соседнему пикселю в направлении, которое нас интересует. Это достигается путем взятия скалярного произведения единичные векторы в соответствующих направлениях, что вводит первый фактор 1/L (здесь 1 / sqrt(2) для диагоналей).

  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. Перпендикулярное направление вносит ноль. Обратите внимание, что все вклады из центрального пикселя "о" исчезает.

теперь мы вычислили вклады всех ближайших соседей в градиент в положительном направлении x в пикселе (x, y), поэтому наше полное приближение градиента в направлении 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

умножение на два служит только для получения удобных целых чисел. Масштабирование выходного изображения в основном произвольно, в большинстве случаев вы нормализуете его в свой диапазон изображений (чтобы получить четко видимые результаты).

по тем же рассуждениям, что и выше, вы получаете ядро для вертикального градиента gy, проецируя соседние вклады на единичный вектор в положительном направлении y (0,1)

      -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) можно упростить до "(разность значений по квадрату расстояние) раз (DotProduct ненормированного вектора направления "ok" с вектором градиента, например (1,0))"

gx_k = (k - o)/(pixel distance^2) ['ok' dot (1,0)].

поскольку точечное произведение соединительного вектора с вектором единицы x выбирает соответствующую векторную запись, соответствующая запись ядра G_x в позиции k просто

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). 

если мне нужны целочисленные значения для моего ядра, я выполняю следующие шаги:

  • проверьте доступный диапазон выходного изображения
  • вычислить максимально возможный результат от применения ядра с плавающей запятой (т. е. предположить максимальное входное значение для всех положительных записей ядра, поэтому выходное значение (сумма по всем положительные значения ядра) * (максимальное возможное значение входного изображения). Если у вас есть подписанный ввод, вам также нужно учитывать отрицательные значения. В худшем случае-сумма всех положительных значений + сумма всех значений abs отрицательных записей (если max input под положительными, - max input под отрицательными). edit: сумма всех значений abs также была метко названа вес ядра
  • вычислить максимально допустимое масштабирование для ядра (без переполнения диапазона вывода image)
  • для всех целых кратных (от 2 до выше максимума) ядра с плавающей запятой: проверьте, какая имеет наименьшую сумму абсолютных ошибок округления и используйте это ядро

таким образом:

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

обратите внимание, что центральные пиксели 3x3 ядра 5x5 в float-нотации-это только ядро 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

точные целочисленные представления становятся непрактичными в этот момент.

насколько я могу судить (не имею доступа к оригинальной бумаге), " Собель" частью этого является правильное взвешивание взносов. Решение 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)

если вы хотите самое близкое целое число, выполните действия, описанные выше.


я быстро взломал алгоритм для генерации ядра Sobel любого нечетного размера > 1, основываясь на примерах, приведенных @Paul R:

    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;
            }
        }
    }

надеюсь, что это помогает.


генератор градиентного фильтра Собеля

(этот ответ относится к анализ дано @Daniel, выше.)

Gx[i,j] = i / (i*i + j*j)

Gy[i,j] = j / (i*i + j*j)

это важный результат и лучшее объяснение, чем можно найти в Оригинал статьи. Это должно быть написано в Википедия, или где-то, потому что он также кажется превосходящим любое другое обсуждение проблемы, доступной в интернете.

однако на самом деле это не так что целочисленные представления непрактичны для фильтров размером более 5*5, как утверждается. Используя 64-разрядные целые числа, размер фильтра Sobel до 15*15 может быть точно выражен.

вот первые четыре; результат должен быть разделен на "вес", так что градиент в области изображения, например, нормированная на значение 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, добавленная ниже, вычислит фильтры Собеля и соответствующие веса любого размера, хотя целочисленные фильтры вряд ли будут полезны для размеров больше 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}"

As Адам Боуэн объяснил в своем ответе, ядро Собеля представляет собой комбинацию сглаживания вдоль одной оси и Центральной разностной производной вдоль другой оси:

sob3x3 = [1 2 1]' * [1 0 -1]

сглаживание добавляет регуляризацию (уменьшает чувствительность к шуму).

(я опускаю все факторы 1/8 в этой статье как и сам Собель, что означает, что оператор определяет производную до масштабирования. Кроме того,* всегда свертка в этом посте.)

давайте обобщим это:

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 немного больше.

в сглаживающее ядро треугольное, с образцами [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]

это совершенно отличается от совет, данный Адамом Боуэном, кто предлагает свернуть ядро с треугольным ядром 3-tab вдоль каждого измерения:[1 2 1] * [1 2 1] = [1 4 6 4 1] и [1 2 1] * [1 0 -1] = [1 2 0 -2 -1]. Обратите внимание, что из-за центральной предельной теоремы свертывание этого треугольного ядра с самим собой приводит к фильтру, который аппроксимирует Гауссова немного больше. Чем больше ядро мы создаем повторными свертками с самим собой, тем больше приближаемся этот Гаусс. Таким образом, вместо использования этого метода вы можете также непосредственно попробовать функцию Гаусса.

Даниил имеет длинный пост в которой он предлагает расширить ядро Собеля еще одним способом. Форма сглаживающего ядра здесь расходится с гауссовским приближением, я не пытался изучить его свойства.

обратите внимание, что ни одно из этих трех возможных расширений ядра Sobel не является ядром Sobel, так как ядро Sobel является явно ядром 3x3 (см. историческую заметку Собеля о его операторе, который он никогда не опубликовал).

Обратите также внимание, что я не защищаю расширенное ядро Sobel, полученное здесь. Используйте гауссовы производные!


Спасибо за все, я попробую второй вариант @Adam Bowen, возьмите код C# для Sobel5x5, 7x7, 9x9... матрица получения Для этот вариант (возможно с ошибками, если вы нашли ошибку или оптимизировать код - напишите его здесь):

    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 metod - №1. Теперь я использую последний, потому что он дает более плавный результат, и легко генерировать ядра с этой код.


вот простое решение, сделанное с python 3 с использованием numpy и ответа @Daniel.

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, пожалуйста, дайте мне знать. Я еще новичок;)