Search code examples
pythonnumpyopencvcolormap

Set the values below a certain threshold of a CV2 Colormap to transparent


I'm currently trying to apply an activation heatmap to a photo.

Currently, I have the original photo, as well as a mask of probabilities. I multiply the probabilities by 255 and then round down to the nearest integer. I'm then using cv2.applyColorMap with COLORMAP.JET to apply the colormap to the image with an opacity of 25%.

img_cv2 = cv2.cvtColor(np_img, cv2.COLOR_RGB2BGR)

heatmapshow = np.uint8(np.floor(mask * 255))

colormap = cv2.COLORMAP_JET

heatmapshow = cv2.applyColorMap(np.uint8(heatmapshow - 255), colormap)

heatmap_opacity = 0.25
image_opacity = 1.0 - heatmap_opacity

heatmap_arr = cv2.addWeighted(heatmapshow, heatmap_opacity, img_cv2, image_opacity, 0)

This current code successfully produces a heatmap. However, I'd like to be able to make two changes.

  1. Keep the opacity at 25% For all values above a certain threshold (Likely > 0, but I'd prefer more flexibility), but then when the mask is below that threshold, reduce the opacity to 0% for those cells. In other words, if there is very little activation, I want to preserve the color of the original image.

  2. If possible I'd also like to be able to specify a custom colormap, since the native ones are pretty limited, though I might be able to get away without this if I can do the custom opacity thing.

I read on Stackoverflow that you can possibly trick cv2 into not overlaying any color with NaN values, but also read that only works for floats and not ints, which complicates things since I'm using int8. I'm also concerned that this functionality could change in the future as I don't believe this is intentional design purposefully built into cv2.

Does anyone have a good way of accomplishing these goals? Thanks!


Solution

  • With regard to your second question:

    Here is how to create a simple custom two color gradient color map in Python/OpenCV.

    Input:

    enter image description here

    import cv2
    import numpy as np
    
    # load image as grayscale
    img = cv2.imread('lena_gray.png', cv2.IMREAD_GRAYSCALE)
    
    # convert to 3 equal channels
    img = cv2.merge((img, img, img))
    
    # create 1 pixel red image
    red = np.full((1, 1, 3), (0,0,255), np.uint8)
    
    # create 1 pixel blue image
    blue = np.full((1, 1, 3), (255,0,0), np.uint8)
    
    # append the two images
    lut = np.concatenate((red, blue), axis=0)
    
    # resize lut to 256 values
    lut = cv2.resize(lut, (1,256), interpolation=cv2.INTER_LINEAR)
    
    # apply lut
    result = cv2.LUT(img, lut)
    
    # save result
    cv2.imwrite('lena_red_blue_lut_mapped.png', result)
    
    # display result
    cv2.imshow('RESULT', result)
    cv2.waitKey(0)
    cv2.destroyAllWindows()
    

    Result of colormap applied to image:

    enter image description here



    With regard to your first question:

    You are blending the heat map image with the original image using a constant "opacity" value. You can replace the single opacity value with an image. You just have to do the addWeighted manually as heatmap * opacity_img + original * (1-opacity_img) where your opacity image is float in the range 0 to 1. Then clip and convert back to uint8. If your opacity image is binary, then you can use cv2.bitWiseAnd() in place of multiply.