I'm writing a generic algorithm for the pictures so I started with Image class of PIL library and created a numpy array of input image. So now I want to draw some figures and the easiest way is to use ImageDraw, but then again I should use arrays for the next evolution so I need to convert ImageDraw object either to the Image object or to a numpy array.
Any suggestions how can I do that?
I tried to use a numpy conversion which worked on the Image objects. Tried to find included methods of conversion
from PIL import Image, ImageDraw
import numpy
input_image = Image.open("i2.jpg")
width, height = input_image.size
num_weights = width * height
image_draw = ImageDraw.Draw(Image.new('RGB', (width, height), 'WHITE'))
input_image = numpy.array(input_image.getdata())
#Do some staff with image_draw using information from input_image
#And try to convert image_draw to input_image
I want to have as the output a numpy array or Image object
I think you want to process an image both as a PIL Image so you can draw on it, and also as a Numpy array so you can do processing on it.
So, here is an example of how to draw on an image with PIL, then convert it to a Numpy array and do some processing on it, then convert it back to a PIL Image.
#!/usr/bin/env python3
from PIL import Image, ImageDraw
# Create a black 600x200 image
img = Image.new('RGB', (600, 200))
# Get a drawing handle
draw = ImageDraw.Draw(img)
# Draw on image
draw.rectangle(xy=[10,20,300,80], fill='red')
# Save as "result1.png"
img.save('result1.png')
# Convert PIL Image to Numpy array for processing
na = np.array(img)
# Make mask selecting red pixels then make them blue
Rmask =(na[:, :, 0:3] == [255,0,0]).all(2)
na[Rmask] = [0,0,255]
# Convert Numpy array back to PIL Image
img = Image.fromarray(na)
# Save as "result2.png"
img.save('result2.png')
The two images are "result1.png":
and "result2.png":
Related
I used to use scipy which would load an image from file straight into an ndarray.
from scipy import misc
img = misc.imread('./myimage.jpg')
type(img)
>>> numpy.ndarray
But now it gives me a DeprecationWarning and the docs say it will be removed in 1.2.0. and I should use imageio.imread instead. But:
import imageio
img = imageio.imread('./myimage.jpg')
type(img)
>>> imageio.core.util.Image
I could convert it by doing
img = numpy.array(img)
But this seems hacky. Is there any way to load an image straight into a numpy array as I was doing before with scipy's misc.imread (other than using OpenCV)?
The result of imageio.imread is already a NumPy array; imageio.core.util.Image is an ndarray subclass that exists primarily so the array can have a meta attribute holding image metadata.
If you want an object of type exactly numpy.ndarray, you can use asarray:
array = numpy.asarray(img)
Unlike numpy.array(img), this will not copy img's data.
If it was a bitmap or even jpeg, you can do:
import matplotlib.pyplot as plt
import numpy as np
# 'pip install pillow' but import PIL
from PIL import Image
png_filepath = 'somepng.png'
png_pil_img = Image.open(png_filepath)
# this will print info about the PIL object
print(png_pil_img.format, png_pil_img.size, png_pil_img.mode)
png_np_img = np.asarray(png_pil_img)
plt.imshow(png_np_img) # this will graphit in a jupyter notebook
# or if its grayscale plt.imshow(png_np_img, cmap='gray')
# FWIW, this will show the np characteritics
print("shape is ", png_np_img.shape)
print("dtype is ", png_np_img.dtype)
print("ndim is ", png_np_img.ndim)
print("itemsize is ", png_np_img.itemsize) # size in bytes of each array element
print("nbytes is ", png_np_img.nbytes) # size in bytes of each array element
If you have a jpg, it works the same. PIL.image will decode the compressed JPG, and convert it to an array for you. Literally it will do all this for you. Perhaps you could load the raw bitmap with file io skipping the header, yadda yadda, but PIL is popular for a reason.
The output for a grayscale png will look like this:
PNG (3024, 4032) L
shape is (4032, 3024)
dtype is uint8
ndim is 2
itemsize is 1
nbytes is 12192768
The output for a color jpeg will look like this:
JPEG (704, 480) RGB
shape is (480, 704, 3)
dtype is uint8
ndim is 3
itemsize is 1
nbytes is 1013760
In either case, the pixel values range 0-255 as ints. They are not floats. The color image has three channels corresponding to red green and blue. The grayscale image is much greater resolution and the jpg.
In my code, I am creating a RGB array (256 * 256 * 3) and I need to show it.
I am having trouble creating a PIL image from a RGB array.
I wrote this code to explain:
import numpy as np
from PIL import Image
image = Image.open('img_test.png')
image.thumbnail((256, 256))
image = image.convert("RGB")
image = np.asarray(image, dtype=np.float32) / 255
PIL.Image.fromarray(image, "RGB").show()
I am getting this image back:
If I am using
import matplotlib.pyplot as plt
plt.imshow(image)
plt.show()
Then I am getting this image:
What am I doing wrong with this line?
PIL.Image.fromarray(image, "RGB").show()
You are expecting PIL to handle a 32-bit floating point RGB image, which it cannot - see here.
It can handle, amongst others:
RGB as three 8-bit integer values (RGB888), or
greyscale float32.
Just do
Image.fromarray(np.array(img).astype(np.uint8).transpose(1,2,0))
The line you mention seems fine, however, I wonder why you do this:
image = np.asarray(image, dtype=np.float32) / 255
If you replace that line by the following, it works for me, using either PIL or matplotlib to show the image:
image = np.asarray(image)
I want to be able to detect a certain area of pixels based on their RGB values and change them to some other color (not black/white).
I have tried changing these values in the code, but my resulting images always show black pixels replacing the specified locations:
pixelMap[i,j]= (255,255,255)
from PIL import Image
im = Image.open('Bird.jpg')
pixelMap = im.load()
img = Image.new(im.mode, im.size)
pixelsNew = img.load()
for i in range(img.size[0]):
for j in range(img.size[1]):
toup = pixelMap[i,j]
if(int(toup[0]>175) and int(toup[1]<100 and int(toup[2])<100) ):
pixelMap[i,j]= (255,255,255)
else:
pixelsNew[i,j] = pixelMap[i,j]
img.show()
You will find that iterating over images with Python loops is really slow and should get in the habit of using Numpy or optimised OpenCV or skimage code.
So, starting with this image:
from PIL import Image
import numpy as np
# Open image
im = Image.open('bird.jpg')
# Make into Numpy array
imnp = np.array(im)
# Make all reddish pixels white
imnp[(imnp[:,:,0]>170) & (imnp[:,:,1]<100) & (imnp[:,:,2]<100)] = [255,255,255]
# Convert back to PIL and save
Image.fromarray(imnp).save('result.jpg')
It looks like a tiny bug:
Instead of: pixelMap[i,j]= (255,255,255)
Use: pixelsNew[i,j] = (255,255,255)
I'm trying to add an image that was processed by scikit-image and scipy to a tkinter gui. To add it to the canvas it needs to be either saved as a png, or converted to a PIL image. However, when I try to use ImageTk's Image.fromarray() it distorts the image a lot. I would prefer not to save it as a png, because it's just an intermediate step for generating data labels.
I tried checking the shapes of the arrays, and they're the same. I tried printing out the images, and the filled_objects is the correct image, while im is distorted. So it's not problem in the Tkinter gui. Also, if I don't use np.asarray() it produces the same output.
def generateCanny(imageName):
#imagename should be a path to the image, created with os path join
img = skimage.io.imread(imageName)
print('orig {}'.format(img.shape))
gray = np.sqrt((img*img).sum(-1))
#converts the image to greyscale
edges = skimage.feature.canny(gray, sigma=3)
fill = scipy.ndimage.binary_fill_holes(edges)
return fill
imageName = os.path.join(imagePath, imageStr)
filled_objects = generateCanny(imageName)
a = np.asarray(filled_objects)
im = PIL.Image.fromarray(a)
Here are the two images, im is on the left and filled_objects is on the right
I would think that you could just convert it easily because filled_objects is just an array, but Image.fromarray() must be doing some processing.
The problem is that fromarray isn't interpreting the boolean array a correctly. If you convert a back to RGB with:
# Extend the array into 3 dimensions, repeating the data:
a = np.repeat(a[...,None],3,axis=2).astype(np.uint8)
# Scale to 0-255:
a = 255*a
im = PIL.Image.fromarray(a)
then im.show() will display the correct image.
Converting the result to NumPy's uint8 will do the trick:
from skimage import data, color, feature, util
import tkinter as tk
import numpy as np
from PIL import ImageTk, Image
from scipy.ndimage import binary_fill_holes
rgb = data.hubble_deep_field()
gray = color.rgb2grey(rgb)
edges = feature.canny(gray, sigma=3)
filled_objects = binary_fill_holes(edges)
img_bool = Image.fromarray(filled_objects)
img_uint8 = Image.fromarray(util.img_as_ubyte(filled_objects))
root = tk.Tk()
photo_bool = ImageTk.PhotoImage(img_bool)
photo_uint8 = ImageTk.PhotoImage(img_uint8)
label_bool = tk.Label(root, image=photo_bool).grid(row=1, column=1)
label_uint8 = tk.Label(root, image=photo_uint8).grid(row=1, column=2)
root.mainloop()
When I try to convert PNG type PIL Image into OpenCV Image, transparent background at PNG turns into black background. How can I keep the transparent background in OpenCV Image object.
Here is the code piece :
# PIL Image object which holds a transparent background png image.
pil_img = Image.open(ioFile).convert('RGBA')
pil_img.show()
# I use numpy to convert the pil_image into a numpy array
numpy_image = np.array(pil_img)
# I convert to a openCV2 image, notice the COLOR_RGB2BGR which means that
# the color is converted from RGBA to BGR format
opencvImage = cv2.cvtColor(numpy_image, cv2.COLOR_RGBA2BGRA)
#
#(I commented below lines, to show that I tried them but did not work.)
#
# opencvImage = cv2.cvtColor(numpy_image, cv2.IMREAD_UNCHANGED)
# opencvImage = cv2.cvtColor(numpy_image, cv2.COLOR_RGB2BGR)
showImage(opencvImage)
The last line of code piece shows an image with black background. I probably choose the wrong convert method and, could not find the proper one.
You can use this code for save transparency when converting.
To convert (with Alpha) from Pillow image to OpenCv image:
You can manually change the color order.
import cv2
from PIL import Image
import numpy as np
pillowImage = Image.open('picturePath.png').convert('RGBA')
img = np.array(pillowImage) # 'img' Color order: RGBA
red = img[:,:,0].copy() # Copy R from RGBA
img[:,:,0] = img[:,:,2].copy() # Copy B to first order. Color order: BGBA
img[:,:,2] = red # Copy R to the third order. Color order: BGRA
opencvImage = img # img is OpenCV variable