Preserve 3-dimensionality when converting PIL image to numpy array - python

I have a list of PIL images: p0, p1, ..., p85999 (a total of 86000 of them). They are all RGB, of size 30x30px.
I need to convert them to normalized numpy arrays, I did the following:
[np.asarray(r).astype('float32') / 255.0) for r in images]
where r is a PIL image.
This gives an array of numpy arrays.
However, these arrays are sometimes of shape (30,30,3) and sometimes of shape (30,30).
I want them always to be of shape (30,30,3).
I'm guessing numpy does this for performance reasons (when RGB is not
needed, eg. white images?).
Anyway, how to get the desired result - get all numpy arrays to be of size (30,30,3)?
Also, ideally I would want my final numpy array to be of size (30, 30, 3, 86000). Is there a shortcut to create such an array straight from PIL images?

I'm guessing numpy does this for performance reasons
Numpy has nothing to do with it, this is your PIL Image having one channel only.
The simplest solution is to just convert everything to RGB:
ims = [np.asarray(r.convert('RGB')).astype('float32') / 255.0) for r in images]
If you then call np.asarray(ims), you'll obtain an array of shape [N,30,30,3] where N is the number of images, which you can then transpose to your desired ordering.

Related

numpy.resize throws valueError despite image matrix product equaling the total image size

I am trying to resize a grayscale image into a numpy array like so:
return np.array(image.getdata()).reshape((im_height, im_width, 3)).astype(np.uint8)
and getting this error:
ValueError: cannot reshape array of size 1909760 into shape
(1024,1865,3)
I've read that the product of an images columns and rows (1024 x 1865) is supposed to equal the size of the array being reshaped - (1909760) which it does. I've also tried the same code on images with three channels and it works.
If you're using the PIL module for your image, you could try converting it to an RGB before getting the data. Something like this should work:
image = image.convert("RGB")
return np.array(image.getdata()).reshape((im_height, im_width, 3)).astype(np.uint8)
This works because when you convert from a grayscale to an RGB, PIL automatically sets each pixel to have three values, an R, G, and B.
Do not use .getdata(). That's pointless and a waste of effort. What'll happen is that a python list of integers is constructed as an intermediate. Directly converting to a numpy array is much more efficient.
Just use this:
# image = Image.open(...)
image_array = np.array(image)
Secondly you need to handle the conversion from grayscale to RGB, which you seem to want. Your PIL image appears to be grayscale, yet you want a numpy array with three channels (third dimension sized 3). You can either use PIL to convert, or you can use OpenCV.
PIL: image = image.convert("RGB") before converting to numpy (thanks Timmy Diehl, I don't use PIL that often)
OpenCV: image_array = cv.cvtColor(image_array, cv.COLOR_GRAY2BGR) after converting to numpy
Also note the order of color channels. PIL prefers RGB. OpenCV prefers BGR. What you need depends on what you'll do with the numpy array.

Convert 1D Numpy Array into a 1D image using PIL

PIL returns IndexError: tuple index out of range when converting a 1D numpy array into an PIL image object.
I am trying to covert a 1D Numpy Array of length 2048 having value between 0 and 255 into an image using PIL. I think this is an issue with my array being 1D. I have also tried converting a random 1D array integer to an image and I get the same error.
Random integer example:
from PIL import Image
import numpy as np
arr = np.random.randint(255, size=(2048))
arr = arr.astype('uint8')
img = Image.fromarray(arr, 'L')
img.show()
I would expect the code to show an image of a singe line of pixels having varying shades of gray.
When I tried to run your code, the problem was just that your array was a 1D array. So try:
arr2d = arr.reshape(-1,1)
Image.fromarray(arr2d,'L').show()
The input array has to be 2D, even if one dimension is 1. You just need to decide if you want the image to be a horizontal or vertical row of pixels, and add a dimension when creating your array.
arr = np.random.randint(255, size=(2048, 1)) # vertical image
arr = np.random.randint(255, size=(2048, 1)) # horizontal image

Subtract 2D array from 4D array

I have a greyscale image, represented by a 2D array of integers, shape (1000, 1000).
I then use sklearn.feature_extraction.image.extract_patches_2d() to generate an array of 3x3 'patches' from this image, resulting in an array of shape (1000000, 3, 3), as there are 1 million 3x3 arrays for each pixel value in the original image.
I reshape this to (1000, 1000, 3, 3), which is a 1000x1000 array of 3x3 arrays, one 3x3 array for each pixel in the original image.
I now want to effectively subtract the 2D array from the 4D array. I have already found a method to do this, but I would like to make one using vectorisation.
I currently iterate through each pixel and subtract the value there from the 3x3 array at the same index. This is a little bit slow.
This is what currently loads images, formats the arrays before hand, and then performs this subtraction.
from PIL import Image, ImageOps
from skimage import io
from sklearn.feature_extraction import image
import numpy
jitter = 1
patchsize = (jitter*2)+1
#load image as greyscale image using PIL
original = load_image_greyscale(filename)
#create a padded version of the image so that 1000x1000 patches are made
#instead of 998x998
padded = numpy.asarray(ImageOps.expand(original,jitter))
#extract these 3x3 patches using sklearn
patches = image.extract_patches_2d(padded,(patchsize,patchsize))
#convert image to numpy array
pixel_array = numpy.asarray(original)
#then reshape the array of patches so it matches array_image
patch_array = numpy.reshape(patches, (pixel_array.shape[0],pixel_array.shape[1],patchsize,patchsize))
#create a copy for results
patch_array_copy = numpy.copy(patch_array)
#iterate over each 3x3 array in the patch array and subtract the pixel value
#at the same index in the pixel array
for x in range(pixel_array.shape[0]):
for y in range(pixel_array.shape[1]):
patch_array_copy[x,y] = patch_array[x,y] - pixel_array[x,y]
I would like a way to perform the final step in the for loop using matrix operations.
I would also like to extend this at some point to work with RGB images, effectively making it a subtraction of an array with shape(1000,1000,3) from an array with shape(1000,1000,3,3,3). But i'm trying to go one step at a time here.
Any help or tips or suggestions or links to helpful resources would be greatly appreciated.

Converting an image into a vector of pixels

I am trying to convert an image into an array of pixels.
Here is my current code.
im = Image.open("beeleg.png")
pixels = im.load()
im.getdata() # doesn't work
print(pixels # doesn't work
Ideally, my end goal is to convert the image into a vector of just pixels, so for instance if I have an image of dimensions 100x100, then I want a vector of dimensions 1x10000, where each value is between [0, 255]. Then, divide each of the values in the array by 256 and add a bias of 1 in the front of the vector. However, I am not able to proceed with all this without being able to obtain an array. How to proceed?
Scipy's ndimage library is generally the go-to library for working with pixels as data (arrays). You can load an image from file (most common formats supported) using scipy.ndimage.imread into a numpy array which can be easily reshaped and mathematically operated on. The mode keyword can be used to specify a colorspace transformation upon load (convert an RGB image to black and white). In your case you asked for single color pixels from 0-255 (8bit grayscale) so you would use mode='L'. See The Documentation for usage / more useful functions.
If use OpenCV, gray=cv2.imread(image,0) will return a grayscale image with n rows x m cols single channel numpy array. rows, cols = gray.shape will return the height and width of the image.

Python - get white pixels of image

I'm would like to go from an image filename to a list of coordinates of the white pixels in the image.
I know it involves PIL. I have tried using Image.load() but this doesn't help because the output is not indexable (to use in a for loop).
You can dump an image as a numpy array and manipulate the pixel values that way.
from PIL import Image
import numpy as np
im=Image.open("someimage.png")
pixels=np.asarray(im.getdata())
npixels,bpp=pixels.shape
This will give you an array whose dimensions will depend on how many bands you have per pixel (bpp above) and the number of rows times the number of columns in the image -- shape will give you the size of the resulting array. Once you have the pixel values, it ought to be straightforward to filter out those whose values are 255
To convert a numpy array back to an image use:
im=Image.fromarray(pixels)

Categories

Resources