Search code examples
pythonopencvimage-processingcolorscontrast

How extract contrast level of a photo - opencv


I need to return the (average) contrast value of an image. Is this possible and if so, please show how? More than just the code, (obviously the code is welcome), in what color space should I work? Is HSV appropriate or best? Can the contrast value of a single pixel be computed?


Solution

  • Contrast can be computed from any intensity (I) like channel such as L in LAB, or I in HSI or V in HSV or Y in YCbCr (or even a grayscale version of the image from desaturation) using the max and min values either globally or an average of some region surrounding every pixel. LAB colorspace is often used, but I do not know if there is any general consensus about which would be "best".

    One formula is:

    contrast=(Imax - Imin)/(Imax + Imin)

    See here

    1) Convert the image to say LAB and get the L channel
    2) Compute the max for an NxN neighborhood around each pixel
    3) Compute the min for an NxN neighborhood around each pixel
    4) Compute the contrast from the equation above at each pixel.
    5) Compute the average for all pixels in the result of step 4)
    


    where N is some small integer such as 5 or 7, for example.

    Using ImageMagick (unix syntax), this would be:

    magick zelda1.jpg -colorspace LAB -channel 0 -separate +channel \
    \( -clone 0 -statistic maximum 5x5 \) \
    \( -clone 0 -statistic minimum 5x5 \) \
    \( -clone 1 -clone 2 +swap -define compose:clamp=off -compose minus -composite \) \
    \( -clone 1 -clone 2 +swap -define compose:clamp=off -compose plus -composite \) \
    -delete 0-2 \
    +swap -define compose:clamp=off -compose divide -composite \
    -scale 1x1! -format "%[fx:100*u]\%" info:
    
    17.4745%
    


    The contrast for 1 pixel would actually be the formula above for a 3x3 neighborhood around the given pixel. The neighborhood could contain all 8 surrounding pixels or just the top, bottom, left, right pixels around the give pixel.

    A single pixel by itself cannot have a contrast. Contrast is a relative (difference) concept, so at least between two pixels.

    Note that erode and dilate for an NxN structure element are equivalent to minimum and maximum.

    Here is code in OpenCV:

    #!/bin/python3.7
    
    import cv2
    import numpy as np
    
    # read image
    img = cv2.imread("zelda1.jpg")
    
    # convert to LAB color space
    lab = cv2.cvtColor(img,cv2.COLOR_BGR2LAB)
    
    # separate channels
    L,A,B=cv2.split(lab)
    
    # compute minimum and maximum in 5x5 region using erode and dilate
    kernel = np.ones((5,5),np.uint8)
    min = cv2.erode(L,kernel,iterations = 1)
    max = cv2.dilate(L,kernel,iterations = 1)
    
    # convert min and max to floats
    min = min.astype(np.float64) 
    max = max.astype(np.float64) 
    
    # compute local contrast
    contrast = (max-min)/(max+min)
    
    # get average across whole image
    average_contrast = 100*np.mean(contrast)
    
    print(str(average_contrast)+"%")
    
    17.481959221048086%