Problems in displaying uint16 np.array as image - python

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

Related

Smooth conversion between color image to greyscale

I would like to smoothly convert an RGB image to greyscale as a function of some continuous parameter. I have seen plenty of posts on how to convert 3-channel to 1-channel, but that would not work for me, I would like the output to still be 3-channels. Is this possible?
I would like to have a function
f(image, parameter)
that does more or less the following: if paramater is zero, the function returns the original image, and if the parameter is one it returns a greyscale image. Therefore, I would have the ability to smoothly tune the color between on and off via parameter.
If there already is a coded solution, in Python is strongly preferred.
Thanks!
It's quite easy to do with PIL/Pillow.
from PIL import Image
im = Image.open(r'c:\temp\temp.jpg')
gray = im.copy().convert('L').convert('RGB')
im2 = Image.blend(im, gray, 0.75)

OpenCV's BGR and PyPlot's RGB

I learned that OpenCV color order is BGR while that of Matpotlib's Pyplot is RGB. So I started experimenting with reading and displaying an image using both libraries. Here is the Image I experimented with:
It's just a Black and white image with red color in some parts. Now, when I used pyplot.imshow() to display the image copy read by OpenCV, the tie's and the shoes' color changes to blue. The same happened when I used cv2.imshow() to display a copy of the image read by pyplot.imread(). However, the color remains the same when I use cv2.imshow() to display the image copy read by cv2.imread() and use plt.imshow() to display a copy read by plt.imread().
I am just curious and would like to know about the things that go behind the scenes when such operations are performed. Can Anyone help me with that?
Assume you have a vector like this: [0, 0, 255].
You know have two different color encodings: RGB and BGR. So, in the first case you have Blue, in the second system you have Red.
Now, Let's call RGB_Reader and BGR_Reader two systems to open the number and display it.
If I open the image with BGR_Reader, I have [0, 0, 255]. I pass it on to RGB_Reader, still is [0, 0, 255]. I see Blue. When I pass it around, I would pass [0, 0, 255]. I open it again with RGB_Reader, it is blue, again.
The same happens the other way around.
Does it make sense to you? The vector doesn't change, but the way it is decoded does.
Now introduce another thing, called jpg_encoder. That one is telling people where to put Blue, Red and Green, and will probably re-order things.
That's basically dependent upon the color convention. OpenCV follows BGR convention, which means that it interprets a triplet (0, 150, 255) as B, G and R values respectively. And all other libraries follow the more obvious RGB convention. The reason for OpenCV to follow BGR convention is legacy I guess(since 1991, maybe).
I would recommend you to use OpenCV methods only such as cv2.imread(), cv2.imshow() or cv2.imwrite(), etc. to perform any operation on image(s). Because writing code in this way you will never have to worry about the underlying BGR or RGB stuff, everything will just work fine.
The problem would arise when you want to use OpenCV with matplotlib or pillow etc. In those cases you need to take extra care while passing on your image matrix to respective libraries. Since OpenCV holds the data in BGR format, while matplotlib or pillow would be expecting RGB format, so you explicitly need to convert the color order using cv2.cvtColor(img, cv2.COLOR_BGR2RGB), or you may use numpy slicing as well to swap the first and third channel as well.
You may consult this answer for a demo code which converts OpenCV images to PIL(another python image processing module) format images.

How to convert a grayscale image to heatmap image with Python OpenCV

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).

overlay an image and show lighter pixel at each pixel location

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.

Strange bug while combining images in Python

I have a hundred 10x10 px images, and I want to combine them into a big 100x100 image. I'm using the Image library to first create a blank image and then paste in the smaller images:
blank = Image.new('P',(100,100))
blank.paste(im,box)
The smaller images are in color, but the resulting image turns out in all grayscale. Is there a fix or workaround for this?
It's probably something to do with using a palette type image (mode P). Is there a specific reason you are doing this? If not, try passing 'RGB' as the first argument.

Categories