Search code examples
pythonimage-processingfilterscipygaussian

Python: perform blur only within a mask of image


I have a greyscale image and a binary mask of an ROI in that image. I would like to perform a blur operation on the greyscale image but only within the confines of the mask. Right now I'm blurring the whole image and than just removing items outside the mask, but I don't want pixels outside of the mask affecting my ROI. Is there a way to do this without building a custom blur function?

hoping for something like:

import scipy
blurredImage = scipy.ndimage.filters.gaussian_filter(img, sigma = 3, weight = myMask)

@stefan:

blur = 3
invmask = np.logical_not(mask).astype(int)

masked = img * mask
remaining = img * invmask

blurred = scipy.ndimage.filters.gaussian_filter(masked, sigma = blur)
blurred = blurred+remaining

Dilate approach:

blur = 3
invmask = np.logical_not(mask).astype(int)    
masked = img * mask
masked2 = scipy.ndimage.morphology.grey_dilation(masked,size=(5,5))
masked2 = masked2 *invmask
masked2 = masked + masked2
blurred = scipy.ndimage.filters.gaussian_filter(masked2, sigma = blur)

Solution

  • The right approach to apply a linear filter to a limited domain is to use Normalized Convolution. This method computes (weighted) means within each neighborhood, then normalizes by the (weighted) number of pixels present in that neighborhood. It does so using only two applications of the filter and some trivial per-pixel operations:

    # normalized convolution of image with mask
    filter = scipy.ndimage.gaussian_filter(img * mask, sigma = blur)
    weights = scipy.ndimage.gaussian_filter(mask, sigma = blur)
    filter /= weights
    # after normalized convolution, you can choose to delete any data outside the mask:
    filter *= mask
    

    Note that mask doesn't need to be just 0 and 1, it can contain intermediate values indicating how "certain" you are of the correctness of that pixel's value. But typically it's just 0 for "missing data" and 1 for available data.

    gaussian_filter must do its computations in a floating-point format and return an floating-point-valued image. Integer operations will not do the correct thing here.


    Here's an example:

    enter image description here

    • 2nd image: Plain filtering, then removing the stuff outside the mask. This shows that the data outside the mask influences the result of the filtering.

    • 3rd image: Plain filtering, but setting stuff outside the mask to zero first. This shows that the zeros outside the mask influence the result of the filtering.

    • 4th image: Using normalized convolution: the data outside the masked area does not affect the filtering at all.