expand a selection of pixels in opencv 2

Question:

I have a selection mask on an image in cv2 and I want to expand the selection of pixels such that every pixel within a radius R of an already selected pixel can be added to the selection.

I would like for it to work identical to the expand function in Photoshop.

The only way I can imagine to do this is to look at each pixel in the image and if it is in the selection then change every pixel within radius R to be part of the selection.

The big problem with this is that it has runtime O(R^2 * # of pixels).

This is really slow, and I know there must be a better way because the Photoshop expand selection method works almost instantly even for large pictures.
So I would like a way to change my method in cv2 or in numpy to make it faster.
(maybe there’s a way to vectorize it, but I don’t know)

Asked By: Zubair Ahmed

||

Answers:

I figured out how to expand a selection the only problem that it can have some bugs at the edge of an image. It’s actually quite simple, assuming you have a mask of boolean values. Except it doesn’t actually matter too much it still works if the mask is of zeros representing non selected regions and positive numbers representing selected regions.

def expand(selection, radius):
    cop = np.copy(selection)
    for x in range(-radius,radius+1):
        for y in range(-radius,radius+1):
            if (y==0 and x==0) or (x**2 + y**2 > radius **2):
                continue
            shift = np.roll(np.roll(selection, y, axis = 0), x, axis = 1)
            cop += shift

    return cop
    

Here is a quick example that works quite well

sel = np.array([[False, False, False, False, False],
                [False, False, False, False, False],
                [False, False, True, False, False],
                [False, False, False, False, False],
                [False, False, False, False, False]])
expand(sel, 2)

This runs much faster, I also believe that it is O(R^2) which is quite fast. It also gives similar to results to the photoshop expand function for selections. I believe that the only difference is that my method selects pixels that fall within a circle of radius R, but Photoshop selects pixels within a hexagon of radius R, this is a slight difference that can be added to the if statement.

Answered By: Zubair Ahmed

Trying the other solution it still felt very slow,

def expand(selection, radius):
    kernel = np.array([[x**2+y**2 for x in range(-radius,radius+1)]
                       for y in range(-radius,radius+1)]) < (radius+1)**2
    kernel = np.uint8(kernel)
    return cv2.dilate(selection, kernel, iterations=1)

This seems to be much faster, with nearly identical results (8 seconds vs. 0.15 seconds, testing on colab w/ a radius of 30).

Answered By: James Pickersgill