Search code examples
pythonpython-3.ximage-processingimagefilter

Why is my Speckle (Lee Filter) adding noise instead of reducing noise?


I researched the lee filter according to this: https://pro.arcgis.com/en/pro-app/3.0/help/analysis/raster-functions/speckle-function.htm.

I want it to reduce noise, but it is adding instead.

I have this python function to apply the lee filter to an image:

from scipy.ndimage.filters import uniform_filter
from scipy.ndimage.measurements import variance
def leeFilter(img, size):
  img_mean = uniform_filter(img, (size, size))
  img_sqr_mean = uniform_filter(img**2, (size, size))
  img_variance = img_sqr_mean - img_mean**2
  overall_variance = np.var(img)

  img_weights = img_variance / (img_variance + overall_variance)
  img_output = img_mean + img_weights * (img - img_mean)
  return img_output

Then, I'm applying it to my image as such:

currImg = cv2.imread(os.path.join(currPath,f'{i}.png'))
# Get red, green and blue channels
red = currImg[:,:,0]
green = currImg[:,:,1]
blue = currImg[:,:,2]
# Apply the filter
red = lee_filter(red,15)
green = lee_filter(green,15)
blue = lee_filter(blue,15)
# merge channels
currImg[:,:,0] = red
currImg[:,:,1] = green
currImg[:,:,2] = blue
cv2.imwrite(os.path.join(newPath,f'{i}.png'),currImg)

The problem is that using a window size of 15, it looks like it is adding and not lowering the noise of the images. Below is a comparison with the before and after images.

enter image description here


Solution

  • Cris is right about the estimated noise level, but the main issue is arithmetic overflow - result of applying the computations to uint8 elements.

    We may convert the input to float32 before lee_filter, and convert the output back to uint8 after lee_filter.


    Code sample:

    import cv2
    import numpy as np
    from scipy.ndimage.filters import uniform_filter
    from scipy.ndimage.measurements import variance
    
    def lee_filter(img, size):
        img_mean = uniform_filter(img, (size, size))
        img_sqr_mean = uniform_filter(img**2, (size, size))
        img_variance = img_sqr_mean - img_mean**2
        overall_variance = np.var(img)
    
        img_weights = img_variance / (img_variance + overall_variance)
        img_output = img_mean + img_weights * (img - img_mean)
        return img_output
    
    
    currImg = cv2.imread('before.png')
    # Get red, green and blue channels
    red = currImg[:,:,0].astype(np.float32)  # Convert dtype from np.uint8 to np.float32
    green = currImg[:,:,1].astype(np.float32)
    blue = currImg[:,:,2].astype(np.float32)
    # Apply the filter
    red = lee_filter(red, 15)
    green = lee_filter(green, 15)
    blue = lee_filter(blue, 15)
    # merge channels
    currImg[:,:,0] = red
    currImg[:,:,1] = green
    currImg[:,:,2] = blue
    currImg = currImg.round().clip(0, 255).astype(np.uint8)  # Convert from np.float32 to np.uint8 with rounding and clipping
    cv2.imwrite('after.png', currImg)
    

    Before (before.png):
    enter image description here

    After (after.png):
    enter image description here