Suppose you need to create images pixel by pixel, and they are large images, and you want to do it quickly... How do you do it? with PIL putpixel()? with wxImage SetRGB()? No, they are to slow, you need to use Numpy.
You might be ok with smaller images, but when you start generating images that are around 5000,5000 pixels large, speed starts to matter.
if setting each pixel individually using an image library is too slow, how can you do it? You build your image as an array and then load it into an image tool in bulk.
So whats the catch?
The catch is understanding how PIL's frombuffer() method works. It expects raw data. You can think of it as a single integer for each pixel. But how do you store all 4 color values (Red,Green,Blue + Alpha transparency) in a single integer value? You will need to do a little hex editing.
If you are familiar with writing colors as hex values (ie #FF0000 for red) you know that the first two characters are for red, the second two are for green and the last two are for blue. You could also think of that as the three separate hex values 0xFF 0x00 0x00.
In a python prompt it's easy to convert from Hex to Decimal
We need to add another value for alpha transparency, as you can see above, 0x80 will give us 50% transparency.
Now, you can make a single integer out of those 4 values. The only trick is to now put them together in reverse order (apha,blue,green,red) or in the format 0xAABBGGRR).
Now we have an integer which represents 50% transparent red to set in our numeric array.
Now we get to creating the array for our image, we only need to know what type of array to create. Our integers for a RGBA image are unsigned 32bit integers. If we were creating a RGB image we would use unsigned 16bit integers.
Now img is a numpy array we can use to set the pixels to whatever value we want. By default it is already a Black image with 100% alpha transparency which is convenient. It may seem odd to set the shape to height,width; and well, it is odd. I'm not quite sure why that seems backwards.
Here is how to set the top left pixel (the one at position 0,0) to our semi-transparent red color.
With numpy you can also use a fancy notation to set multiple contingent pixels to the same value. This is where we get a huge speed improvement over setting pixels individually. To set the right half of the image (pixels 512-1024) of rows 10-20 solid blue, we would do this:
The generic format is img[startrow:finishrow+1,starx:endx]
Now a few more fun rows
The last step is to convert the array into an image and save it as a png image.
And there you have it
2 years, 6 months ago
Here’s another way to accomplish what you wanted, using
Image.fromarrayinstead of the cryptic looking frombuffer:import numpy
w,h = 1024,768
img = numpy.zeros((h,w,4), numpy.uint8)
img[0,0]=[255,0,0,128]
img[10:21,512:,2:4]=[255,255]
img[21:401,758:769,2:4]=[255,255]
img[401:411,512:769,2:4]=[255,255]
from PIL import Image
pilImage = Image.fromarray(img, ‘RGBA’)
pilImage.save(’my.png’)
This version also sets the array size to h x w x 4 so that you can set individual color components easily without the hex codes.
As far as being backwards for dimensions, it’s because we think of cartesian as (x,y), but the arrays (like matrices) are dimmed as (nrows,cosl) which maps to (y,x) for images.
2 years, 4 months ago
great!
this is just what I needed.
just started messing with numpy, after spending a bit of time with PIL, and wondered how to speed things up.
many thanks
miska <- (dane, of czech parents, living in finland)
2 years, 2 months ago
Another note:
If you have a PIL image, and want to get a numpy array, you can use:
arr = numpy.asarray(PIL_image)
that will create an array that shares the PIL_image data buffer.
11 months, 3 weeks ago
Hi,
Thanks for the above posts!
I’m trying to use the code above to get images from numpy arrays. All images are greyscale. I keep getting regularly spaced dots, not images.
Here’s a simple script that illustrates the problem. If anyone knows how to fix this I’d be very much obliged. Thank you!
joe
#
import numpy
from Tkinter import *
import Image, ImageTk, ImageDraw
window = Tk()
canvas = Canvas(window, width = 512, height = 512, bg = ‘blue’)
joeMat = numpy.zeros([512,512])
for i in xrange (200):
for j in xrange (200):
joeMat[i,j] = 200
pilImage = Image.fromarray(joeMat,’L')
photo = ImageTk.PhotoImage(pilImage)
canvas.create_image(256,256,image = photo)
canvas.pack()
mainloop()
10 months, 1 week ago
joeMat = numpy.zeros([512,512])
… needs to be:
joeMat = numpy.zeros([512,512], numpy.uint8)
3 months, 3 weeks ago
joeMat = numpy.zeros([512,512], numpy.uint8)
Or
Don’t do this
for i in xrange (200):
for j in xrange (200):
joeMat[i,j] = 200
Even if you are using xrange, it is terribly slow. There’s a section in the numpy docs on how to iterate through an array much faster. Can’t think of it off hand.