Plot RGB image with matshow failed - Invalid shape - python

I have array with 12 bands:
array
(53, 44, 12)
I want to create RGB image from this array.
In order to do that I know that I need to slice the 3,2 and the 1 channels from the array and to create an image from them. In order to take those bands I have done this:
red=array[:,:,3]
green=array[:,:,2]
blue=array[:,:,1]
#stack together:
rgb=np.stack((red,green,blue))
The problem is that whenever I want to display the image as rgb I get error:
fig, ax = plt.subplots(figsize=(20,10))
ax.matshow(rgb)
TypeError: Invalid shape (3, 44, 12) for image data
My first idea to solve it was to change the order of the axis as I thought that it takes the 3 as number of rows, I have used np.moveaxis:
rgb=np.moveaxis(rgb, 0, -1).shape
rgb.shape
>>>
(44,12,3)
but then when I tried again with matshow I have got error again:
TypeError: Invalid shape (3,) for image data
I'm not sure where is my mistake as in the past using matshow similarly worked.
My end goal is to be able to plot the three bands as rgb image.

You need to stack along the 3rd axis:
rgb = np.dstack((red,green,blue))
or
rgb = np.stack((red,green,blue), axis=2)
As Mark Setchell already pointed out you can do the same much easier by standard slicing. If the 12 bands of your array in fact represent 4 images (3 channels each) you could also split the array along axis 2 in 4 arrays like this:
a1, a2, a3, a4 = np.split(array, 4, 2)

If you have an image with 12 bands like this:
image[53,44,12]
and you want the first three bands, just use:
b0b1b2 = image[..., :3]
Check what you have with:
print(b0b1b2.shape)
(53, 44, 3)
This is Numpy slicing.
If you want bands 0, 4 and 5, use
b0b4b5 = image[..., [0,4,5]]

Related

Convert 3 Dimensional Numpy Array to 4 Dimensional Numpy Array

I want to make a simple Program which outputs a video as an Webcam, but the Cam wants a RGBA Numpy Array but I only have RGB from the video. How can I convert the 3 dimensional array to 4 dimensions?
You're actually not converting a 3-dimensional array to a 4-dimensional array. You're changing the size of one of the dimensions from three to four.
Lets say you have a NxMx3 image. You then need to:
temp = np.zeros((N, M, 4))
temp[:,:,0:3] = image
temp[:,:,3] = whatever default alpha you choose to use.
Generalize as you see fit.
Assuming your existing array is shaped (xsize, ysize, 3) and you want to create alpha as a 4th entry all filled with 1, you should be able to do something like
alpha = np.ones((*rgb.shape[0:2], 1))
rgba = np.concatenate((rgb, alpha), axis=2)
If you wanted a different uniform alpha value you could use np.full with that value instead of np.ones, but normally when converting RGB to RGBA you want fully opaque.
You can np.dstack your original im with np.ones(im.shape[:2])
new_im = np.dstack((im, np.ones(im.shape[:2])))
update: this is equivalent to #hobbs solution np.concatenate(..., axis=2)
Maybe try something like these: (import numpy as np)
arr # shape (n_bands, y_pixels, x_pixels)
swapped = np.moveaxis(arr, 0, 2) # shape (y_pixels, x_pixels, n_bands)
arr4d = np.expand_dims(swapped, 0) # shape (1, y_pixels, x_pixels, n_bands)

I want to get the SSIM when comparing two images in python

I'm trying to use the "compare_ssim" function. I currently have two 2xN matrices of x,y coordinates where the first row is all the x coordinates and the second row is all the y coordinates of each of the two images. How can I calculate the SSIM for these two images (if there is a way to do so)
For example I have:
X = np.array([[1,2,3], [4,5,6]])
Y = np.array([[3,4,5],[5,6,7]])
compare_ssim(X,Y)
But I am getting the error
ValueError: win_size exceeds image extent. If the input is a multichannel (color) image, set multichannel=True.
I'm not sure if I am missing a parameter or if I should convert the matrices in such a way that this function works. Or if there is a way that I am supposed to convert my coordinates to a grayscale matrix? I'm a bit confused on what the matrices for the parameters of the function should look like. I know that they are supposed to be ndarrays but the type(Y) and type(Y) are both numpy.ndarray.
Since you haven't mentioned which framework/library you are using, I am going with the assumption that you are using skimage's compare_ssim.
The error in question is due to the shape of your inputs. You can find more details here.
TL;DR: compare_ssim expects images in (H, W, C) dimensions but your input images have a dimension of (2, 3). So the function is confused which dimension to treat as the channel dimension. When multichannel=True, the last dimension is treated as the channel dimension.
There are 3 key problems with your code,
compare_image expects Images as input. So your X and Y matrices should be of the dimensions (H, W, C) and not (2, 3)
They should of float datatype.
Below I have shown a bit of demo code (note: Since skimage v1.7, compare_ssim has been moved to skimage.metrics.structural_similarity)
import numpy as np
from skimage.metrics import structural_similarity
img1 = np.random.randint(0, 255, size=(200, 200, 3)).astype(np.float32)
img2 = np.random.randint(0, 255, size=(200, 200, 3)).astype(np.float32)
ssim_score = structural_similarity(img1, img2, multichannel=True) #score: 0.0018769083894301646
ssim_score = structural_similarity(img1, img1, multichannel=True) #score: 1.0

Understand python image arrays

I can declare a 3D array like this:
a 3D array, shape-(2, 2, 2)
3D_array = np.array([[[0, 1],[2, 3]], [[4, 5],[6, 7]]])
So if I have an image 10*10(pixels) 3 rgb channels, image.shape would be (3x10x10).
But i see all the time image.shape equal to (10x10x3), i don't understand why?
Thanks for you attention.
Usually in numpy and matplotlib the rgb channels are in the last axis. This is just a convention, so you can do little about this. If you use a program that uses the other convention (channels first), you can transform the image with:
channels_first_im = np.moveaxis(channels_last_im, 0, 1)
and the other way:
channels_last_im = np.moveaxis(channels_first_im, 0, -1)
If you're confused about why the convention image arrays would be of shape (N, M, 3) instead of (3, N, M), let's look at how indexing would work in both of those scenarios.
Let's assume we have an image called image_array, that represents a random colored with a width and height of 100 pixels, and let's try to index it to access the value of the pixel at index (50, 50).
Channels First
import numpy as np
image = np.random.random((3, 100, 100)) #image.shape == (3, 100, 100)
pixel = image[:, 50, 50] #pixel.shape == (3,)
Channels Last
import numpy as np
image = np.random.random((100, 100, 3)) #image.shape == (100, 100, 3)
pixel = image[50, 50] #pixel.shape == (3,)
Having the channels as the last dimension of the array, means that the individual pixel information is easier to index to find. Where as in the first case, we need to specify that we want the entire first dimension every time. These are inherently the same thing, but leaving the channels last allows us to be less verbose as to how we index the array.

Convert a one dimensional dataframe into a 3 dimensional for RGB Image

I have a data frame of 2304 columns , as it is a 48*48 image pixels, when I convert it into one channel using this code
x = (df.iloc[:,1:].values).astype('float32')
x = x.reshape(-1,48,48,1)
its perfectly output of shape
(48*48*1)
with generating exact image by this code:
plt.imshow(x[0][:,:,0])
I want to make it into a 3Dimentional like in three channels. I try to merged the df 3 times and do this (48*48*3) it successfully change the df shape but I cannot generate the image again,
If you essentially want to convert a single channel image (which should essentially be a greyscale image) into a 3 channel greyscale image, its the same as concatenating the same image array thrice along the last axis. You can use np.concatenate to achieve the desired result.
import numpy as np
a = np.zeros((2304), dtype = np.uint8) #Just a dummy array representing a single pic
single_channel = a.reshape(48, 48, 1)
result = np.concatenate([single_channel,single_channel,single_channel], axis = -1)
print(result.shape) #(48, 48, 3)
At this point you should have an array that can be accepted by any image library. Just throwing a sample code to show how you may proceed to create the image from the array.
import cv2
cv2.imwrite("hi.jpg", result)
As stated earlier, use numpy instead of pandas for image manipulation.
EDIT: If you were unfortunately starting with a dataframe in the first place, you can always convert it to a numpy array with an extra dimension representing each image.
import pandas as pd
import cv2
import numpy as np
a = np.zeros((2304), dtype = np.uint8) #dummy row
dummy_df = pd.DataFrame(np.concatenate([a.reshape(1,-1)]*10)) #dummy df with 10 rows.
print(dummy_df.shape) #(10, 2304)
arr_images = np.array(dummy_df, dtype = np.uint8)
print(arr_images.shape) #(10, 2304)
multiple_single_channel = arr_images.reshape(-1, 48, 48, 1)
print(multiple_single_channel.shape) #(10, 48, 48, 1)
result = np.concatenate([multiple_single_channel] * 3, axis = -1)
print(result.shape) #(10, 48, 48, 3)
for i,img in enumerate(result):
print(i)
cv2.imwrite("{}.jpg".format(i), img)
#do something with image. you PROBABLY don't want to run this for 35k images though.
The bottom line really is that you should not need to use a dataframe, even for multiple images.
1)Dont use pandas
2) you cant transform 1channel image into 3 channels,
3) Dont use float32, images are usually 8bit (np.uint8)
4) use numpy in combination with OpenCV or with Pillow.
5) Dont use matplotlib to generate images. use libraries mentioned in 4.
6) if you have array with shape (x,y,3) there is nothing more simply than generate image with opencv cv2.imshow('image',array)

Python add one more channel to image

I'm trying to add channel because of below error
ValueError: could not broadcast input array from shape (48,48) into shape (48,48,1)
Code:
img = cv2.imread(f,0)
resized = cv2.resize(img, (48,48), interpolation = cv2.INTER_AREA)
print(resized.shape)
(48, 48)
But I need a channel image like (48,48,1).
How can I solve this?
y = np.expand_dims(x, axis=-1) # Equivalent to x[:,:,np.newaxis]
As the function says, it will add an extra dimension as the new Last Channel
Edit
axis will be -1 instead of 1
You can do this by using split and merge operations:
First, split your 2-channel image into two arrays using split. Then, create the array which gives you the third channel, separately. Finally, merge the three arrays to get one 3-channel Mat.
This is an example:
c1,c2 = cv2.split(img)
merged = cv2.merge((c1,c2,arr))
Where img is your 2-channel image, arr is the array containing the channel to add, and the merged image contains the three channels merged.
Modifying Aditya's answer like this:
y = np.expand_dims(x, axis=1)
axis = 1 will insert new dimension at the beginning, so you could simply change the value of the axis to be = 3. It worked for me.
Another workaround might be creating a placeholder and populating it.
ph = np.ones((resized.shape[0], resized.shape[1], 1), dtype='uint8')
ph[:,:,0] = resized
very easy! on your interactive shell, just do
>>> y = image.resize(48, 48, 1)
>>> y.shape
>>> (48, 48, 1)

Categories

Resources