Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating anti-aliased circular mask efficiently

I am trying to create anti-aliased (weighted and not boolean) circular masks for making circular kernels for use in convolution.

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)

and the output is

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

Then we can do

mask = 1 - mask
print(mask)

to get

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

I can now normalize and use this as my circular filter (kernel) in convolution operations.

Note: Radius can be decimal. Eg: get_circular_kernel(0.5,(5,5)) should give

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

I want to generate a million of these at the very least, with the kernel_size fixed and radius changing, so is there a better or more efficient way to do this? (maybe without costly operations like sqrt and still stay accurate enough to arc integrals i.e., area covered by the curve in the particular pixel?)

like image 430
Saravanabalagi Ramachandran Avatar asked Sep 07 '25 16:09

Saravanabalagi Ramachandran


1 Answers

Since you want to generate a large number of kernels with the same size, you can greatly improve performance by constructing every kernel in one step rather than one after the other in a loop. You can create a single array of shape (num_radii, kernel_size, kernel_size) given num_radii values for each kernel. The price of this vectorization is memory: you'll have to fit all these values in RAM, otherwise you should chunk up your millions of radii into a handful of smaller batches and generate each batch again separately.

The only thing you need to change is to take an array of radii (rather than a scalar radius), and inject two trailing singleton dimensions so that your mask creation triggers 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)

Now masks[0,...] (or masks[0] for short, but I prefer the explicit version) contains the example mask in your question, and masks[1,...] and masks[2,...] contain the kernels for radii 3.5 and 4, respectively.

like image 161