53

I'm trying to convert a black & white .jpg image into a list which I can then modulate into an audio signal.

I have imported the PIL module and am trying to call the built-in function: list(im.getdata()). When I call it, python crashes. Is there some way of breaking down the image (always 320x240) into 240 lines to make the computations easier? Or am I just calling the wrong function?

starball
  • 20,030
  • 7
  • 43
  • 238

9 Answers9

75

Python shouldn't crash when you call getdata(). The image might be corrupted or there is something wrong with your PIL installation. Try it with another image or post the image you are using.

This should break down the image the way you want:

from PIL import Image
im = Image.open('um_000000.png')

pixels = list(im.getdata())
width, height = im.size
pixels = [pixels[i * width:(i + 1) * width] for i in xrange(height)]
Nadia Alramli
  • 111,714
  • 37
  • 173
  • 152
  • 8
    *`list(im.getdata())`. When I call it, python crashes.* – SilentGhost Jul 10 '09 at 14:06
  • 2
    When I call list(im.getdata()), not only does python crash (presumably), but my entire linux system becomes unresponsive. Hard reboot fixes this problem. – Stephen Cagle Nov 15 '11 at 20:09
  • it crashes on both, windows and linux. i'm started to think about stability of PIL. – Mirat Can Bayrak Mar 17 '13 at 18:07
  • Crashes? Are you using PIL or Pillow? – Kyle Kelley Nov 16 '13 at 12:36
  • 4
    Problem seems to be the conversion to a standard python list. If I call just `pixels=im.getdata()` (which returns a special, simplified, list type) everything is fine. If I add the cast, however, python starts using a huge amount of RAM, and if there isn't enough available PC starts to swap and finally crashes. Perhaps using a python list to hold some million pixel values is a little bit overkill. – jesjimher Mar 19 '14 at 10:30
  • 1
    Could you please add the following lines? `import Image; im = Image.open('um_000000.png')` – Martin Thoma May 18 '15 at 15:33
  • Of course.. does anyone know what list() does in python? Because of the large data, the functions returns a kind of async value - namely an iterator. So, when you put into a for loop afterwards, it goes pixel by pixel nicely without using huge amounts of ram. BUT, when you are using list() it actually gets ALL of the data SYNCED. google what list() does. – ioan Mar 24 '17 at 10:00
  • In my case, the printing list was the problem. If I call print(list(img.getdata())), it crashes, but when calling only list(img.getdata()), everything is ok. – user3777939 Mar 28 '17 at 10:31
  • 2
    Thanks for this. I had no crashing problems but I offer the following addition for anyone else wanting a numpy array instead: `pixels = np.array(im.getdata()).reshape((im.size[1], im.size[0]))` – Bill Nov 04 '17 at 19:09
  • 1
    Actually, see @tom10's answer below to get a numpy array. – Bill Nov 04 '17 at 19:12
  • Yup, this method is devastating on memory. Just go with np.array, or just build the pixel 2d list by iterating over getdata() one by one. calling list(im.getdata()) is a no no! – rymanso Apr 29 '22 at 15:09
45

If you have numpy installed you can try:

data = numpy.asarray(im)

(I say "try" here, because it's unclear why getdata() isn't working for you, and I don't know whether asarray uses getdata, but it's worth a test.)

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
tom10
  • 67,082
  • 10
  • 127
  • 137
21

I assume you are getting an error like.. TypeError: 'PixelAccess' object is not iterable...?

See the Image.load documentation for how to access pixels..

Basically, to get the list of pixels in an image, using PIL:

from PIL import Image
i = Image.open("myfile.png")

pixels = i.load() # this is not a list, nor is it list()'able
width, height = i.size

all_pixels = []
for x in range(width):
    for y in range(height):
        cpixel = pixels[x, y]
        all_pixels.append(cpixel)

That appends every pixel to the all_pixels - if the file is an RGB image (even if it only contains a black-and-white image) these will be a tuple, for example:

(255, 255, 255)

To convert the image to monochrome, you just average the three values - so, the last three lines of code would become..

cpixel = pixels[x, y]
bw_value = int(round(sum(cpixel) / float(len(cpixel))))
# the above could probably be bw_value = sum(cpixel)/len(cpixel)
all_pixels.append(bw_value)

Or to get the luminance (weighted average):

cpixel = pixels[x, y]
luma = (0.3 * cpixel[0]) + (0.59 * cpixel[1]) + (0.11 * cpixel[2])
all_pixels.append(luma)

Or pure 1-bit looking black and white:

cpixel = pixels[x, y]
if round(sum(cpixel)) / float(len(cpixel)) > 127:
    all_pixels.append(255)
else:
    all_pixels.append(0)

There is probably methods within PIL to do such RGB -> BW conversions quicker, but this works, and isn't particularly slow.

If you only want to perform calculations on each row, you could skip adding all the pixels to an intermediate list.. For example, to calculate the average value of each row:

from PIL import Image
i = Image.open("myfile.png")

pixels = i.load() # this is not a list
width, height = i.size
row_averages = []
for y in range(height):
    cur_row_ttl = 0
    for x in range(width):
        cur_pixel = pixels[x, y]
        cur_pixel_mono = sum(cur_pixel) / len(cur_pixel)
        cur_row_ttl += cur_pixel_mono
    
    cur_row_avg = cur_row_ttl / width
    row_averages.append(cur_row_avg)

print "Brighest row:",
print max(row_averages)
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
dbr
  • 165,801
  • 69
  • 278
  • 343
  • [Inform] The "Load" function link: https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.load – Cloud Cho Jul 28 '22 at 19:33
3

Or if you want to count white or black pixels

This is also a solution:

from PIL import Image
import operator

img = Image.open("your_file.png").convert('1')
black, white = img.getcolors()

print black[0]
print white[0]
Martijn van Wezel
  • 1,120
  • 14
  • 25
3

Not PIL, but scipy.misc.imread might still be interesting:

import scipy.misc
im = scipy.misc.imread('um_000000.png', flatten=False, mode='RGB')
print(im.shape)

gives

(480, 640, 3)

so it is (height, width, channels). So you can iterate over it by

for y in range(im.shape[0]):
    for x in range(im.shape[1]):
        color = tuple(im[y][x])
        r, g, b = color
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
3
pixVals = list(pilImg.getdata())

output is a list of all RGB values from the picture:

[(248, 246, 247), (246, 248, 247), (244, 248, 247), (244, 248, 247), (246, 248, 247), (248, 246, 247), (250, 246, 247), (251, 245, 247), (253, 244, 247), (254, 243, 247)]
Andronicus
  • 25,419
  • 17
  • 47
  • 88
Linda
  • 31
  • 1
2
data = numpy.asarray(im)

Notice:In PIL, img is RGBA. In cv2, img is BGRA.

My robust solution:

def cv_from_pil_img(pil_img):
    assert pil_img.mode=="RGBA"
    return cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGBA2BGRA)
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
sunshine
  • 21
  • 2
1

As I commented above, problem seems to be the conversion from PIL internal list format to a standard python list type. I've found that Image.tostring() is much faster, and depending on your needs it might be enough. In my case, I needed to calculate the CRC32 digest of image data, and it suited fine.

If you need to perform more complex calculations, tom10 response involving numpy might be what you need.

jesjimher
  • 1,175
  • 1
  • 12
  • 17
1

Looks like PILlow may have changed tostring() to tobytes(). When trying to extract RGBA pixels to get them into an OpenGL texture, the following worked for me (within the glTexImage2D call which I omit for brevity).

from PIL import Image
img = Image.open("mandrill.png").rotate(180).transpose(Image.FLIP_LEFT_RIGHT)

# use img.convert("RGBA").tobytes() as texels
Dr. D.
  • 11
  • 1