Создание сглаженной круглой маски эффективно - PullRequest
0 голосов
/ 23 июня 2018

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

radius = 3  # no. of pixels to be 1 on either side of the center pixel
            # shall be decimal as well; not the real radius
kernel_size = 9                
kernel_radius = (kernel_size - 1) // 2
x, y = np.ogrid[-kernel_radius:kernel_radius+1, -kernel_radius:kernel_radius+1]
dist = ((x**2+y**2)**0.5)
mask = (dist-radius).clip(0,1)
print(mask)

и вывод

array([[1.  , 1.  , 1.  , 1.  , 1.  , 1.  , 1.  , 1.  , 1.  ],
       [1.  , 1.  , 0.61, 0.16, 0.  , 0.16, 0.61, 1.  , 1.  ],
       [1.  , 0.61, 0.  , 0.  , 0.  , 0.  , 0.  , 0.61, 1.  ],
       [1.  , 0.16, 0.  , 0.  , 0.  , 0.  , 0.  , 0.16, 1.  ],
       [1.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 1.  ],
       [1.  , 0.16, 0.  , 0.  , 0.  , 0.  , 0.  , 0.16, 1.  ],
       [1.  , 0.61, 0.  , 0.  , 0.  , 0.  , 0.  , 0.61, 1.  ],
       [1.  , 1.  , 0.61, 0.16, 0.  , 0.16, 0.61, 1.  , 1.  ],
       [1.  , 1.  , 1.  , 1.  , 1.  , 1.  , 1.  , 1.  , 1.  ]])

Тогда мы можем сделать

mask = 1 - mask
print(mask)

чтобы получить

array([[0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  ],
       [0.  , 0.  , 0.39, 0.84, 1.  , 0.84, 0.39, 0.  , 0.  ],
       [0.  , 0.39, 1.  , 1.  , 1.  , 1.  , 1.  , 0.39, 0.  ],
       [0.  , 0.84, 1.  , 1.  , 1.  , 1.  , 1.  , 0.84, 0.  ],
       [0.  , 1.  , 1.  , 1.  , 1.  , 1.  , 1.  , 1.  , 0.  ],
       [0.  , 0.84, 1.  , 1.  , 1.  , 1.  , 1.  , 0.84, 0.  ],
       [0.  , 0.39, 1.  , 1.  , 1.  , 1.  , 1.  , 0.39, 0.  ],
       [0.  , 0.  , 0.39, 0.84, 1.  , 0.84, 0.39, 0.  , 0.  ],
       [0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  , 0.  ]])

Теперь я могу нормализовать и использовать это как мой круговой фильтр (ядро) в операциях свертки.

Примечание: радиус может быть десятичным. Например: get_circular_kernel(0.5,(5,5)) должен дать

array([[0.        , 0.        , 0.        , 0.        , 0.        ],
       [0.        , 0.08578644, 0.5       , 0.08578644, 0.        ],
       [0.        , 0.5       , 1.        , 0.5       , 0.        ],
       [0.        , 0.08578644, 0.5       , 0.08578644, 0.        ],
       [0.        , 0.        , 0.        , 0.        , 0.        ]])

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

Ответы [ 2 ]

0 голосов
/ 24 июня 2018

Поскольку вы хотите сгенерировать большое количество ядер с одинаковым размером , вы можете значительно повысить производительность, построив каждое ядро ​​за один шаг, а не одно за другим в цикле. Вы можете создать отдельный массив формы (num_radii, kernel_size, kernel_size) с учетом num_radii значений для каждого ядра. Ценой этой векторизации является память: вам нужно разместить все эти значения в ОЗУ, в противном случае вам следует объединить миллионы радиусов в несколько небольших пакетов и сгенерировать каждый пакет заново отдельно.

Единственное, что вам нужно изменить, - это взять массив радиусов (а не скалярный радиус) и ввести два конечных одноэлементных измерения, чтобы ваше создание маски вызвало broadcasting :

import numpy as np 

kernel_size = 9
kernel_radius = (kernel_size - 1) // 2
x, y = np.ogrid[-kernel_radius:kernel_radius+1, -kernel_radius:kernel_radius+1]
dist = (x**2 + y**2)**0.5 # shape (kernel_size, kernel_size)

# let's create three kernels for the sake of example
radii = np.array([3, 3.5, 4])[...,None,None] # shape (num_radii, 1, 1)
# using ... allows compatibility with arbitrarily-shaped radius arrays

masks = 1 - (dist - radii).clip(0,1) # shape (num_radii, kernel_size, kernel_size)

Теперь masks[0,...] (или masks[0] для краткости, но я предпочитаю явную версию) содержит пример маски в вашем вопросе, а masks[1,...] и masks[2,...] содержат ядра для радиусов 3.5 и 4 соответственно.

0 голосов
/ 24 июня 2018

Если вы хотите построить миллионы масок, вы должны предварительно вычислить один раз, что никогда не изменится, и вычислить только строгое значение, необходимое для каждого радиуса.

Вы можете попробовать что-то вроде этого:

class Circle:
    def __init__(self, kernel_size):
        self._kernel_size = kernel_size
        self._kernel_radius = (self._kernel_size - 1) // 2

        x, y = np.ogrid[
            -self._kernel_radius:self._kernel_radius+1,
            -self._kernel_radius:self._kernel_radius+1]
        self._dist = np.sqrt(x**2 + y**2)

    def __call__(self, radius):
        mask = self._dist - radius
        mask = np.clip(mask, 0, 1, out=mask)
        mask *= -1
        mask += 1
        return mask


circle = Circle(kernel_size=9)
for radius in range(1, 4, 0.2):
    mask = circle(radius)  
    print(mask)

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

...