Подтвердить что ты не робот

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

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

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

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

4b9b3361

Ответ 1

ОБНОВЛЕНИЕ 23-апреля-2018: кажется, что ядра, определенные в ссылке ниже, не являются истинными ядрами Собеля (для 5x5 и выше) - они могут выполнять разумную работу по обнаружению фронта, но их не следует называть ядрами Собеля. См Дэниелс ответ для более точного и полного резюме. (Я оставлю этот ответ здесь, поскольку (а) он связан с различными местами и (б) принятые ответы не могут быть легко удалены.)

Похоже, что 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

Ответ 2

Другие источники, похоже, дают разные определения более крупных ядер. Библиотека 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

Интуитивно, это имеет больше смысла для меня, потому что вы обращаете больше внимания на элементы ближе к центру. Он также имеет естественное определение с точки зрения ядра 3х3, которое легко расширить для создания больших ядер. Тем не менее, в моем кратком поиске я нашел 3 разных определения ядра 5x5 - так что я подозреваю, что (как говорит Пол) более крупные ядра являются специальными, и это ни в коем случае не является окончательным ответом.

Ядро 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 должен быть масштабирован с коэффициентом 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 )
...

Ответ 3

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

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

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

Цель

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

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

Идея решения

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

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

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

a b c
d o f
g h i

Допустим, мы хотим градиент в положительном направлении х. Единичный вектор в положительном направлении x равен (1,0) [Позже я буду использовать соглашение о том, что положительное направление y равно ВНИЗ, то есть (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) по следующей причине:

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

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

где я и j - количество шагов от центрального пикселя до пикселя k в направлении x и y. В приведенном выше расчете 3x3 пиксель "a" будет иметь я = -1 (1 слева), j = -1 (1 вверху), и, следовательно, запись ядра "a" равна -1/( 1 + 1) = -1/2.

Записи для ядра G_y

j/(i*i + j*j). 

Если я хочу целочисленные значения для моего ядра, я следую за этими шагами:

  • проверить доступный диапазон выходного изображения
  • вычислить максимально возможный результат от применения ядра с плавающей запятой (т.е. принять максимальное входное значение для всех положительных записей ядра, поэтому выходное значение равно (сумма по всем положительным значениям ядра) * (максимально возможное значение входного изображения). Если вы подписали входные данные, вам нужно чтобы рассмотреть и отрицательные значения. Наихудший случай - это сумма всех положительных значений + сумма всех значений abs отрицательных значений (если максимальный вход под положительными значениями, -max ввод отрицательных значений). edit: сумма всех значений abs также был точно назван вес ядра
  • рассчитать максимально допустимое масштабирование для ядра (без превышения диапазона выходного изображения)
  • для всех целых чисел (от 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 в нотации с плавающей точкой - это просто ядро 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 можно получить, опуская весовые коэффициенты на расстоянии и просто вводя я и j в ядро в зависимости от ситуации.

Бонус: ядра Собеля для произвольных направлений

Таким образом, мы можем приблизить x и y компоненты градиента изображения (который на самом деле является вектором, как указано в самом начале). Градиент в любом произвольном направлении альфа (измеренный математически положительно, в данном случае по часовой стрелке, так как положительный у направлен вниз) может быть получен путем проецирования вектора градиента на единичный вектор альфа-градиента.

Вектор альфа-единицы (cos alpha, sin alpha). Для альфа = 0 ° вы можете получить результат для gx, для альфа = 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)

Если вы хотите приблизительное целочисленное приближение, выполните действия, описанные выше.

Ответ 4

Генератор фильтра градиента Sobel

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

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

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

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

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

#!/usr/bin/ruby

# Sobel image gradient filter generator
# by <[email protected]> -- 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}"

Ответ 5

Я быстро взломал алгоритм для генерации ядра Sobel любого нечетного размерa > 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;
            }
        }
    }

Надеюсь, что это поможет.

Ответ 6

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

Ответ 7

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

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, этого можно достичь. Ядро Гаусса является идеальным вариантом здесь, поскольку оно предлагает лучший компромисс между компактностью в пространственной области (небольшая площадь ядра) и компактностью в частотной области (хорошее сглаживание). Кроме того, гауссовский язык совершенно изотропен и отделим. Использование ядра гауссовой производной дает наилучший из возможных оператор регуляризованной производной.

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


Давайте проанализируем ядро Собеля немного подробнее.

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

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

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

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

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

Ответ 8

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

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

Ответ 9

Реализация 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';