I have a (540, 960, 1) shaped image with values ranging from [0..255] which is black and white. I need to convert it to a "heatmap" representation. As an example, pixels with 255 should be of most heat and pixels with 0 should be with least heat. Others in-between. I also need to return the heat maps as Numpy arrays so I can later merge them to a video. Is there a way to achieve this?
Here are two methods, one using Matplotlib and one using only OpenCV
Method #1: OpenCV + matplotlib.pyplot.get_cmap
To implement a grayscale (1-channel) -> heatmap (3-channel) conversion, we first load in the image as grayscale. By default, OpenCV reads in an image as 3-channel, 8-bit BGR.
We can directly load in an image as grayscale using cv2.imread() with the cv2.IMREAD_GRAYSCALE parameter or use cv2.cvtColor() to convert a BGR image to grayscale with the cv2.COLOR_BGR2GRAY parameter. Once we load in the image, we throw this grayscale image into Matplotlib to obtain our heatmap image. Matplotlib returns a RGB format so we must convert back to Numpy format and switch to BGR colorspace for use with OpenCV. Here's a example using a scientific infrared camera image as input with the inferno colormap. See choosing color maps in Matplotlib for available built-in colormaps depending on your desired use case.
Input image:
Output heatmap image:
Code
import matplotlib.pyplot as plt
import numpy as np
import cv2
image = cv2.imread('frame.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()
Method #2: cv2.applyColorMap()
We can use OpenCV's built in heatmap function. Here's the result using the cv2.COLORMAP_HOT heatmap
Code
import cv2
image = cv2.imread('frame.png', 0)
heatmap = cv2.applyColorMap(image, cv2.COLORMAP_HOT)
cv2.imshow('heatmap', heatmap)
cv2.waitKey()
Note: Although OpenCV's built-in implementation is short and quick, I recommend using Method #1 since there is a larger colormap selection. Matplotlib has hundreds of various colormaps and allows you to create your own custom color maps while OpenCV only has 12 to choose from. Here's the built in OpenCV colormap selection:
You need to convert the image to a proper grayscale representation. This can be done a few ways, particularly with imread(filename, cv2.IMREAD_GRAYSCALE). This reduces the shape of the image to (54,960) (hint, no third dimension).
Related
I've created a mozaic image, such that a large picture consists of many tiny pictures. I can view this image just fine in the matplotlib viewer and I can zoom in to see all the tiny pictures. But when I save the image, no matter the extension, the image loses the zoom ability such that the tiny images become blurred when zooming in. Is there a way to save the image in full resolution?
I have the image in rgb in a numpy array so if there are other libraries more suitable that would work too.
This should work :
from PIL import Image
Image.fromarray(numpy_img).save("img_path.png")
I think you're being mislead by Windows's Photos Application here, which applies blur automatically if you zoom too much.
The image is being saved correctly with all pixel values by Matplotlib, you can check it by zooming again on the pixels of the loaded image.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
# save image
array = np.random.random((4000, 4000, 3))
plt.imsave("save.png", array)
# load image
img = mpimg.imread("save.png")
plt.imshow(img)
plt.show()
Another option is the small library that I wrote called numpngw. If the numpy array is, say, img (an array of 8-bit or 16-bit unsigned integers with shape (m, n, 3)), you could use:
from numpngw import write_png
write_png('output.png', img)
(If the array is floating point, you'll have to convert the values to unsigned integers. The PNG file format does not store floating point values.)
You can try imageio library:
https://imageio.readthedocs.io/en/stable/userapi.html#imageio.imwrite
from imageio import imwrite
imwrite("image.png", array)
When I use this method with an 1 channel image, it's shown in grayscale. Is there a way to use other color maps (not grayscale) when showing an 1 channel image? If there is, it means grayscale is the default for imshow when working with an image with only 1 channel? Thanks in advance
As far as I know, you cannot apply a color map in Python/OpenCV cv2.imshow like you can with Mathplotlib pyplot. But you can create a color map and apply it to your grayscale image using cv2.LUT to change your grayscale image into a colored image that you can display with cv2.imshow.
Please see the documentation for cv2.imshow at https://docs.opencv.org/4.1.1/d7/dfc/group__highgui.html#ga453d42fe4cb60e5723281a89973ee563
I have a uint16 3-dim numpy array reppresenting an RGB image, the array is created from a TIF image.
The problem is that when I import the original image in QGIS for example is displayed correctly, but if I try to display within python (with plt.imshow) the result is different (in this case more green):
QGIS image:
Plot image:
I think it is somehow related to the way matplotlib manages uint16 but even if I try to divide by 255 and convert to uint8 I can't get good results.
Going by your comment, the image isn't encoded using an RGB colour space, since the R, G and B channels have a value range of [0-255] assuming 8 bits per channel.
I'm not sure exactly which colour space the image is using, but TIFF files generally use CMYK which is optimised for printing.
Other common colour spaces to try include YCbCr (YUV) and HSL, however there are lots of variations of these that have been created over the years as display hardware and video streaming technologies have advanced.
To convert the entire image to an RGB colour space, I'd recommend the opencv-python pip package. The package is well documented, but as an example, here's how you would convert a numpy array img from YUV to RGB:
img_bgr = cv.cvtColor(img, cv.COLOR_YUV2RGB)
When using plt.imshow there's the colormap parameter you can play with, try adding cmap="gray" so for example
plt.imshow(image, cmap="gray")
source:
https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.imshow.html
If I try to normalize the image I get good results:
for every channel:
image[i,:,:] = image[i,:,:] / image[i,:,:].max()
However, some images appear darker than others:
different images
When I tried to convert an RGB image with OpenCV function cv2.cvtColor(), I got a green-like image.
I've converted the raw image read by OpenCV to RGB-format, again converted it to gray scale using cv2.cvtColor(), and tried to display it using pyplot.imshow() function.
image = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
plt.imshow(image)
plt.imgshow() uses a color map for a single-channel images. You have two possible solutions, convert your grayscale to rgb (basically duplicate the grayscale 3 times), or choose a proper colormap as explained here: https://matplotlib.org/3.1.0/tutorials/colors/colormaps.html
I have two black and white images that I would like to merge with the final image showing the lighter/ white pixel at each pixel location in both images. I tried the following code but it did not work.
background=Image.open('ABC.jpg').convert("RGBA")
overlay=Image.open('DEF.jpg').convert("RGBA")
background_width=1936
background_height=1863
background_width,background_height = background.size
overlay_resize= overlay.resize((background_width,background_height),Image.ANTIALIAS)
background.paste(overlay_resize, None, overlay_resize)
overlay=background.save("overlay.jpg")
fn=np.maximum(background,overlay)
fn1=PIL.Image.fromarray(fn)
plt.imshow(fnl)
plt.show()
The error message I get is cannot handle this data type. Any help or advice anyone could give would be great.
I think you are over-complicating things. You just need to read in both images and make them greyscale numpy arrays, then choose the lighter of the two pixels at each location.
So starting with these two images:
#!/usr/local/bin/python3
import numpy as np
from PIL import Image
# Open two input images and convert to greyscale numpy arrays
bg=np.array(Image.open('a.png').convert('L'))
fg=np.array(Image.open('b.png').convert('L'))
# Choose lighter pixel at each location
result=np.maximum(bg,fg)
# Save
Image.fromarray(result).save('result.png')
You will get this:
Keywords: numpy, Python, image, image processing, compose, blend, blend mode, lighten, lighter, Photoshop, equivalent, darken, overlay.