Normalizing images in OpenCV

136,103

Solution 1

If you want to change the range to [0, 1], make sure the output data type is float.

image = cv2.imread("lenacolor512.tiff", cv2.IMREAD_COLOR)  # uint8 image
norm_image = cv2.normalize(image, None, alpha=0, beta=1, norm_type=cv2.NORM_MINMAX, dtype=cv2.CV_32F)

Solution 2

The other answers normalize an image based on the entire image. But if your image has a predominant color (such as black), it will mask out the features that you're trying to enhance since it will not be as pronounced. To get around this limitation, we can normalize the image based on a subsection region of interest (ROI). Essentially we will normalize based on the section of the image that we want to enhance instead of equally treating each pixel with the same weight. Take for instance this earth image:

Input image -> Normalization based on entire image

If we want to enhance the clouds by normalizing based on the entire image, the result will not be very sharp and will be over saturated due to the black background. The features to enhance are lost. So to obtain a better result we can crop a ROI, normalize based on the ROI, and then apply the normalization back onto the original image. Say we crop the ROI highlighted in green:

This gives us this ROI

The idea is to calculate the mean and standard deviation of the ROI and then clip the frame based on the lower and upper range. In addition, we could use an offset to dynamically adjust the clip intensity. From here we normalize the original image to this new range. Here's the result:

Before -> After

Code

import cv2
import numpy as np

# Load image as grayscale and crop ROI
image = cv2.imread('1.png', 0)
x, y, w, h = 364, 633, 791, 273
ROI = image[y:y+h, x:x+w]

# Calculate mean and STD
mean, STD  = cv2.meanStdDev(ROI)

# Clip frame to lower and upper STD
offset = 0.2
clipped = np.clip(image, mean - offset*STD, mean + offset*STD).astype(np.uint8)

# Normalize to range
result = cv2.normalize(clipped, clipped, 0, 255, norm_type=cv2.NORM_MINMAX)

cv2.imshow('image', image)
cv2.imshow('ROI', ROI)
cv2.imshow('result', result)
cv2.waitKey()

The difference between normalizing based on the entire image vs a specific section of the ROI can be visualized by applying a heatmap to the result. Notice the difference on how the clouds are defined.

Input image -> heatmap

Normalized on entire image -> heatmap

Normalized on ROI -> heatmap

Heatmap code

import matplotlib.pyplot as plt
import numpy as np
import cv2

image = cv2.imread('result.png', 0)
colormap = plt.get_cmap('inferno')
heatmap = (colormap(image) * 2**16).astype(np.uint16)[:,:,:3]
heatmap = cv2.cvtColor(heatmap, cv2.COLOR_RGB2BGR)

cv2.imshow('image', image)
cv2.imshow('heatmap', heatmap)
cv2.waitKey()

Note: The ROI bounding box coordinates were obtained using how to get ROI Bounding Box Coordinates without Guess & Check and heatmap code was from how to convert a grayscale image to heatmap image with Python OpenCV

Solution 3

When you normalize a matrix using NORM_L1, you are dividing every pixel value by the sum of absolute values of all the pixels in the image. As a result, all pixel values become much less than 1 and you get a black image. Try NORM_MINMAX instead of NORM_L1.

Share:
136,103
N.Chandimali
Author by

N.Chandimali

Updated on July 09, 2022

Comments

  • N.Chandimali
    N.Chandimali almost 2 years

    I wrote the following code to normalize an image using NORM_L1 in OpenCV. But the output image was just black. How to solve this?

    import cv2
    import numpy as np
    import Image
    
    img = cv2.imread('img7.jpg')
    gray_image = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
    a = np.asarray(gray_image)
    
    
    dst = np.zeros(shape=(5,2))
    
    b=cv2.normalize(a,dst,0,255,cv2.NORM_L1)
    
    
    im = Image.fromarray(b)
    
    im.save("img50.jpg")
    
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    
    • Utkarsh Sinha
      Utkarsh Sinha over 7 years
      What's the motivation behind normalizing an image with L1?
  • ckirksey3
    ckirksey3 over 7 years
    Python required me to specify a dst input parameter. In this case, you may want to initialize norm_image to a copy of image and pass that in as dst.
  • Jeru Luke
    Jeru Luke over 7 years
    so what exactly happens in NORM_MINMAX ?
  • rsaxvc
    rsaxvc over 6 years
    The minimum pixel value will be mapped to the minimum output value(alpha), and the maximum pixel value will be mapped to the maximum output value(beta). I believe with linear scaling for everything in between.
  • Dan Mašek
    Dan Mašek over 6 years
    @ckirksey3 You just pass None as dst. No point adding an extra line of code.
  • bincob
    bincob over 4 years
    NORM_MINMAX calculates along the lines of ((pixel_value - alpha)/(beta - alpha)) * beta. This is in line with @rsaxvc
  • fmw42
    fmw42 over 4 years
    Excellent answer. Just a minor point of interest. I note that one should be able to combine the np.clip and cv2.normalize in one command using skimage rescale_intensity. See scikit-image.org/docs/dev/api/…