0

I need to write a function that get an image and return a mirror image (the Y coordinate never change), without using any mirror or flip module from PIL.
I have to write it myself. I'm getting an error msg:

IndexError: image index out of range

what am I missing here?

thank you.

from os.path import exists
import tkMessageBox, tkFileDialog
from PIL import Image, ImageTk, ImageFilter

def flip(im):
    '''Flips a picutre horizontally, and returns a new image that is a mirror view of the original'''
    org=Image.open(im)
    new=Image.new("RGB",org.size)   
    for x in range(org.size[0]):
        a=org.size[0]
        for y in range(org.size[1]):
            pixel=org.getpixel((x,y))
            new.putpixel((a,y),pixel)
            a-=1
    new.save("new.bmp","bmp")
4

2 回答 2

16

Use ImageOps.mirror(image) to flip the image horizontally.

import Image
import ImageOps

img = Image.open(FILENAME)
mirror_img = ImageOps.mirror(img)

Your code is very close to working. You have an "off-by-one" error.

a=org.size[0]
...
new.putpixel((a,y),pixel)

Let's say the org is 100 pixels wide. Then the first time through the loop you are placing a pixel at (100,y). But the pixels are indexed from 0--99. This is why (and where) you are getting a IndexError: image index out of range exception.


Also, be careful about where in the loops you are initializing and decrementing a. In fact, I would suggest not using a at all.

Rather, use

w,h = org.size
for x in range(w):
    for y in range(h):
        pixel = org.getpixel((x, y))
        new.putpixel(EXPRESSION, pixel)

and just replace EXPRESSION with some formula for the location of pixel you wish to color.

于 2013-01-06T13:48:27.047 回答
3

You're mixing up 0-based and 1-based indexing. x ranges from 0 to org.size[0]-1. But a ranges from org.size[0] to 1, because you start with a=org.size[0] and use that, without first subtracting.

Most likely the error comes from somewhere inside this line:

new.putpixel((a,y),pixel)

… on your very first call, when you try to write pixel (280, 0) in an image that only runs from (0-279, 0-279).

So, instead of this:

for x in range(org.size[0]):
    a=org.size[0]
    for y in range(org.size[1]):
        pixel=org.getpixel((x,y))
        new.putpixel((a,y),pixel)
        a-=1

Do this:

for x in range(org.size[0]):
    a=org.size[0]-1
    for y in range(org.size[1]):
        pixel=org.getpixel((x,y))
        new.putpixel((a,y),pixel)
        a-=1

But once you fix this, you're going to run into another problem. You set a to org.size[0] every time through the outer loop, instead of just the first time. And then you decrement a each time through the inner loop, instead of the outer loop. So, you're going to end up copying each line in the original image to the diagonal running from (279,0) to (0,279).

So, you need to do this:

a=org.size[0]-1
for x in range(org.size[0]):
    for y in range(org.size[1]):
        pixel=org.getpixel((x,y))
        new.putpixel((a,y),pixel)
    a-=1

This kind of thing is exactly why you should try to avoid modifying indices manually like this. You never get it right on your first try. On your third try, you get something that looks right, but crashes as soon as you try the first image that wasn't in your test suite. It's better to calculate the values instead of counting them down. For example:

for x in range(org.size[0]):
    flipped_x = org.size[0] - x - 1
    for y in range(org.size[1]):
        pixel=org.getpixel((x,y))
        new.putpixel((flipped_x,y),pixel)

While we're at it, if you can require/depend on PIL 1.1.6 or later, using the array returned by Image.load() is significantly simpler, and much more efficient, and often easier to debug:

orgpixels, newpixels = org.load(), new.load()
for x in range(org.size[0]):
    flipped_x = org.size[0] - x - 1
    for y in range(org.size[1]):
        pixel=orgpixels[x, y]
        newpixels[flipped_x, y] = pixel

If you can't rely on 1.1.6, you can use getdata to get a iterable sequence of pixels, use that to generate a new list or other sequence (which means you can use a list comprehension, map, even feed them into numpy via, e.g., np.array(org.getdata()).reshape(org.size)), and then use putdata to create the new image from the result.

Of course getdata and putdata deal with a 1D sequence, while you want to treat it as a 2D sequence. Fortunately, the grouper function in the itertools docs—which you can copy and paste, or just pip install more-itertool—is usually exactly what you want:

orgrows = more_itertools.grouper(org.size[0], org.getdata())
newrows = [list(reversed(row)) for row in orgrows]
new.putdata(newrows)

One thing to watch out for is that Image.open(im) might not necessarily return you an image in RGB mode. If you just copy pixels from, say, an XYZ or P image to an RGB, you'll end up with discolored garbage or just the red channel, respectively. You may want to print org.mode, and possibly print org.pixel((0, 0)) to make sure it actually has 3 channels (and they look like RGB)`.

The easiest way around that is to convert org before you do anything else:

org=Image.open(im).convert('RGB')

Of course some formats either don't have direct conversions or require an explicit matrix or color palette. If you get a ValueError, you'll have to read up on converting your input type.

于 2013-01-06T14:10:26.733 回答