skip to Main Content

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)

3

Answers


  1. Chosen as BEST ANSWER

    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.


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

    Login or Signup to reply.
  3. This is asking for morphology operations.

    cv.dilate is perfectly happy to enlarge a binary mask’s areas of positive values. Also, it’s fast.

    For different kernels/structuring elements, there is getStructuringElement. It can give you squares/rectangles, circles/ellipses, crosses, …

    Example input and dilated:

    input dilated

    Read more: https://docs.opencv.org/4.x/d9/d61/tutorial_py_morphological_ops.html

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search