Applying local contrast enhancement correctly

Hi,
I am trying to apply a local contrast enhancement technique given in a research paper but after observing the output, I am really not sure whether I am applying it correctly.
Following is a preprocessing technique taken from research paper-

Fundus images show significant variability in lighting within the image due to the curvature of the retina. To counter this unfavor- able characteristic, a local contrast enhancement is implemented. Prior to this, the ROI is padded using the technique described in Soares et al. (2006) to prevent the excessive contrast enhancement at the border. In the case of DRIVE, a Gaussian filter with kernel of 65 × 65 pixels, zero mean and standard deviation 􏰀 of size 10 is applied to the fundus image. For the HRF data set, 􏰀 is increased to 60, corresponding to the larger image size when compared to DRIVE. Finally, the output from the convolution is subtracted from the original image (Fig. 2):

N(w, h) = I(w, h) − G(w, h), (1)

where N is the normalized image, I is the original image, and G is the Gaussian blurred image.

I have padded the image using the technique described in Soares et al(2006),below is my image-

and the output, I am getting-

Below is the output from the research paper-

I really don’t think the second picture in the above image is in any way remotely similar to my output(even ignoring the output in padded region)
Also, I don’t know how to crop the FOV based on the mask as shown in the third picture so not posting that output.
Below is my code-

from PIL import Image
from skimage.filters import gaussian
def gaussian_blur(img):
    image = np.array(img)
    image_blur = cv2.GaussianBlur(a,(65,65),10)
    new_image = a- image_blur
    new_image = new_image*255
    return new_image

new_img = gaussian_blur(a)

Hi,

I can maybe comment a bit on the Gaussian subtraction which is some sort of background subtraction.

First in the function gaussian_blur, isn’t it rather
image_blur = cv2.GaussianBlur(image,(65,65),10) with image instead of a ?
Because a is not defined in this function ?..

Also I noticed recently that addition/subtraction of images using numpy behaves differently than with OpenCV.
In my case, I was doing addition, and by adding 2 images as numpy array by a simple addition, some pixel intensities would go above the bit depth (ex: 200 + 100 = 300 overcoming the 255 for 8-bit).
And it turns out that numpy handles it differently than OpenCV: OpenCV would clip to 255 if you overcome the bitdepth while numpy rollover so in my case in it would yield a pixel value of 5 !
So you better use OpenCV cv2.subtract(image, background) instead of
new_image = image - image_blur

Eventually because the new_image might have negative pixel intensities after subtraction you might want to use float32 type for the new_image, this can be changed by precising the parameter dtype in cv2.subtract.
After that you can shift new_image to positive values only by adding the absolute value of the minimal pixel value.

OH and you don’t need to multiply by 255,this might have cause the saturation of the signal.

Hoping I´m clear :sweat_smile:

1 Like

Hi,
Thanks a lot, you were super-clear.

Because a is not defined in this function ?..

Yes, it was a stupid mistake on my part.

On doing cv2.subtract(image,background) and changing the dtype to float32 and also taking the absolute value,now I am getting the below output-
29%20PM
Though, now it looks comparatively much better to my previous output but it is still not exactly there-
I multiplied it with 255 because I was getting the warning

Clipping input data to the valid range for imshow with RGB data ([0…1] for floats or [0…255] for integers).

Following is my code, after every modifications-

from PIL import Image
from skimage.filters import gaussian
def gaussian_blur(img):
    #image = np.array(img)
    image_blur = cv2.GaussianBlur(img,(65,65),10)
    new_image = cv2.subtract(img,image_blur).astype('float32')
    new_image = np.absolute(new_image)
    return new_image

new_img = gaussian_blur(a)
plt.imshow((new_img*255).astype(np.uint8))

Well 2 things you should not do:

  • np.absolute you convert the negative values that might have been created by subtraction to positive values. Not good, you have to shift the all image to positive by adding the min value instead
    something like out = image + np.abs(image.min()) but for RGB we wont use that.

  • Again the *255 in the plot probably distord the image

The right way to convert back to a 8 bit range for your RGB image is to use
out = cv2.normalize(image, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)

So your script becomes

from PIL import Image
from skimage.filters import gaussian
import cv2

def gaussian_blur(img):
    #image = np.array(img)
    image_blur = cv2.GaussianBlur(img,(65,65),10)
    # new_image = cv2.subtract(img,image_blur).astype('float32') # WRONG, the result is not stored in float32 directly
    new_image = cv2.subtract(img,image_blur, dtype=cv2.CV_32F)
    out = cv2.normalize(new_image, None, alpha=0, beta=255, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_8U)

    return out


plt.imshow(gaussian_blur(a))
1 Like

Thank you,


Though it’s not similarly bright but its I guess its almost close.
I have a doubt regarding contrast enhancement - when we say we are applying local contrast enhancement, are we trying to achieve maximum pixel in the mid range? Like the histogram, I obtained of the above image is this -

or we are trying to distribute the pixel frequency uniformly everywhere.

It looks much better that way !

For the contrast enhancement I would give it a try in ImageJ rather (Process>Enhance contrast)

1 Like

It looks much better that way !

Do you mean, the output we obtained or given in paper.

I checked ImageJ, I guess it is more of a GUI based tool rather than a library but I need something which I can embed in script form .:pensive: