Search code examples
pythonnumpyopencvcolorscontrast

Blending images without color change?


While studying OpenCV, I realized that whenever I blend two images the colors of scr2 have changed in some way(depends on the colors of scr1).

I know this is not an informative and clear way to explain my issue, however; I don't know how to describe this issue since I have no expertise with colors so I would like to show you what I meant with images and code.

The input image: Input image

import cv2
import numpy as np

img=cv2.imread("inputImage.png")

scr2=255*np.ones((img.shape[0],img.shape[1],3),dtype=np.uint8)


#adding lines 

cv2.line(scr2,(100,0),(100,img.shape[1]),(0,255,0),3) 
cv2.line(scr2,(300,0),(300,img.shape[1]),(255,0,0),3)
cv2.line(scr2,(500,0),(500,img.shape[1]),(0,0,255),3)

#blending

blend=cv2.addWeighted(img,0.7,scr2,0.3,0)
crop=blend[60:100,460:530]

cv2.imwrite("crop.png",crop)

cv2.imwrite("line.png",blend)

output image

cropped in red line As you can see, even though I added a single color for each line, the colors of lines have changed depends on the background color. The red line does not seem to be red in the cropped image.

Can you elaborate on why this happening and how can I avoid this problem? I mean, I don't want the color change on lines.

Thank you for your attention.


Solution

  • In case you want the blend to resembled perceptual color blending (closer to the expected blend color by humans), you may apply the blending in LAB color space.

    Advantage:

    Unlike the RGB and CMYK color models, CIELAB is designed to approximate human vision.

    I have demonstrated the principal in my following answer (at the bottom).

    • Convert img and scr2 from BGR to LAB color space.
    • Apply addWeighted in LAB color space.
    • Convert the result from LAB to BGR color space.

    Since the blending of white background of scr2 bothers me, I also copy the original pixels from img to blend where pixels in scr2 are white.


    Here is a complete code sample:

    import cv2
    import numpy as np
    
    img = cv2.imread("inputImage.png")
    
    scr2 = np.full_like(img, 255)
    
    img_lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)  # Convert color space from BGR to LAB color space
    
    # adding lines
    cv2.line(scr2, (100, 0), (100, img.shape[1]), (0, 255, 0), 3)
    cv2.line(scr2, (300, 0), (300, img.shape[1]), (255, 0, 0), 3)
    cv2.line(scr2, (500, 0), (500, img.shape[1]), (0, 0, 255), 3)
    
    scr2_lab = cv2.cvtColor(scr2, cv2.COLOR_BGR2LAB)  # Convert color space from BGR to LAB color space
    
    # blending
    #blend = cv2.addWeighted(img, 0.7, scr2, 0.3, 0)
    blend_lab = cv2.addWeighted(img_lab, 0.7, scr2_lab, 0.3, 0)  # Blend images in LAB color space
    blend = cv2.cvtColor(blend_lab, cv2.COLOR_LAB2BGR)  # Convert color space from LAB to BGR color space
    
    # Replace blended "background" white pixels of scr2 with original pixel values from img:
    mask = np.all(scr2 == 255, 2).astype(np.uint8)  # Mask value is 1 where 3 color channels are 255
    cv2.copyTo(img, mask, blend)  # Copy pixels from img to blend where mask != 0.
    
    
    crop = blend[60:100, 460:530]
    
    cv2.imwrite("crop2.png", crop)
    cv2.imwrite("line2.png", blend)
    
    # Show the results
    cv2.imshow('mask', mask*255)
    cv2.imshow('img', img)
    cv2.imshow('blend', blend)
    cv2.imshow('crop', crop)
    cv2.waitKey()
    cv2.destroyAllWindows()
    

    Result:
    left: crop blended in LAB space.
    right: crop blended in BGR space.
    enter image description here enter image description here

    Note:

    • It's more accurate but doesn't look very helpful in this case...

    Copy the luminance from src2 to blend:

    According to your comment, it looks like you want to keep the luminance of the colored lines from src2, and not blend the luminance with the background.

    In LAB color space, the L color channel is the luminance.

    You may copy the luminance from src2 to blend where src2 is not white.

    Here is the relevant part of the code:

    blend_lab = cv2.addWeighted(img_lab, 0.7, scr2_lab, 0.3, 0) # Blend images in LAB color space

    # Get the luminance channel (get NumPy slice)
    scr2_luminance = scr2_lab[:, :, 0]
    blend_luminance = blend_lab[:, :, 0]
    blend_luminance[np.any(scr2 != 255, 2)] = scr2_luminance[np.any(scr2 != 255, 2)]  # Copy the luminace from scr2_lab to blend_lab where scr2 is not white.
    
    blend = cv2.cvtColor(blend_lab, cv2.COLOR_LAB2BGR)  # Convert color space from LAB to BGR color space
    

    Result:
    crop:
    enter image description here

    blend:
    enter image description here

    Note:

    • The luminance looks correct, but we are loosing the color saturation.

    We may copy the saturation channel from scr2 to blend in HSV color space:

    scr2_hsv = cv2.cvtColor(scr2, cv2.COLOR_BGR2HSV)
    blend_hsv = cv2.cvtColor(blend, cv2.COLOR_BGR2HSV)
    blend_hsv[:,:,1][np.any(scr2 != 255, 2)] = scr2_hsv[:,:,1][np.any(scr2 != 255, 2)]  # Copy the saturation channel.
    blend = cv2.cvtColor(blend_hsv, cv2.COLOR_HSV2BGR)
    

    I am not sure that it makes sense...