Ядро фильтра Собеля большого размера - PullRequest
40 голосов
/ 05 марта 2012

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

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

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

Ответы [ 8 ]

36 голосов
/ 05 апреля 2012

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

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

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

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

Цель

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

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

Идея решения

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

Явные промежуточные шаги для 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») равен (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)

где 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). 

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

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

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

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

Вектор альфа-единицы равен(потому что альфа, грех альфа).Для альфа = 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)

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

16 голосов
/ 05 марта 2012

ОБНОВЛЕНИЕ 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

3 голосов
/ 17 сентября 2017

Генератор градиентных фильтров Собеля

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

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

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

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

Однако на самом деле целочисленноеЦенные представления непрактичны для фильтров размером более 5 * 5, как утверждается.Используя 64-битные целые, можно точно выразить размеры фильтра Собеля до 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:
# /6993526/yadro-filtra-sobelya-bolshogo-razmera


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}"
2 голосов
/ 27 ноября 2017

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

2 голосов
/ 23 марта 2016

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

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

1 голос
/ 13 мая 2018

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

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'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]

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

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

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

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

0 голосов
/ 21 сентября 2016

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

...