17

I have a program that's supposed to change the contrast, but I feel like it's not really changing the contrast.It changes some areas to red whereas I don't want it to. If you could tell me how to remove them, thank you. Here is the code:

from PIL import Image


def change_contrast(img, level):

    img = Image.open("C:\\Users\\omar\\Desktop\\Site\\Images\\obama.png")
    img.load()

    factor = (259 * (level+255)) / (255 * (259-level))
    for x in range(img.size[0]):
        for y in range(img.size[1]):
            color = img.getpixel((x, y))
            new_color = tuple(int(factor * (c-128) + 128) for c in color)
            img.putpixel((x, y), new_color)

    return img

result = change_contrast('C:\\Users\\omar\\Desktop\\Site\\Images\\test_image1.jpg', 100)
result.save('C:\\Users\\omar\\Desktop\\Site\\Images\\test_image1_output.jpg')
print('done')

And here is the image and its result:

obama.png obama modified

If this is the actual contrast method, feel free to tell me

Megzari Nassim
  • 399
  • 2
  • 5
  • 24
  • I can't reproduce this. I tried your code and the output image is looks ok. https://i.stack.imgur.com/QhR3n.jpg – Håken Lid Feb 05 '17 at 14:18
  • Your code contains two different attempts at loading an input image, though. You `test_image1.jpg` will not be used, since you have `obama.png` hard coded in the function body. Are you sure you are using the input file you think you are using? – Håken Lid Feb 05 '17 at 14:22

2 Answers2

24

I couldn't reproduce your bug. On my platform (debian) only the Pillow fork is available, so if you are using the older PIL package, that might be the cause.

In any case, there's a built in method Image.point() for doing this kind of operation. It will map over each pixel in each channel, which should be faster than doing three nested loops in python.

def change_contrast(img, level):
    factor = (259 * (level + 255)) / (255 * (259 - level))
    def contrast(c):
        return 128 + factor * (c - 128)
    return img.point(contrast)

change_contrast(Image.open('barry.png'), 100)

output

Your output looks like you have a overflow in a single channel (red). I don't see any reason why that would happen. But if your level is higher than 259, the output is inverted. Something like that is probably the cause of the initial bug.

def change_contrast_multi(img, steps):
    width, height = img.size
    canvas = Image.new('RGB', (width * len(steps), height))
    for n, level in enumerate(steps):
        img_filtered = change_contrast(img, level)
        canvas.paste(img_filtered, (width * n, 0))
    return canvas

change_contrast_multi(Image.open('barry.png'), [-100, 0, 100, 200, 300])

another output

A possible fix is to make sure the contrast filter only return values within the range [0-255], since the bug seems be caused by negative values overflowing somehow.

def change_contrast(img, level):
    factor = (259 * (level + 255)) / (255 * (259 - level))
    def contrast(c):
        value = 128 + factor * (c - 128)
        return max(0, min(255, value))
    return img.point(contrast)
Håken Lid
  • 22,318
  • 9
  • 52
  • 67
  • Ok that's exactly what I was looking for I wanted to make a scale dépendant contrast modifier. I'm gonna try it out and see if it works. if it does I'll accept it as an answer. – Megzari Nassim Feb 05 '17 at 16:29
  • 1
    Why does that same function not work when I build it in plain Numpy and use it on an image that is represented as a Numpy array? – Alex Mar 07 '18 at 00:26
  • That's explained in my answer. point is a method on the Image class. – Håken Lid Mar 07 '18 at 00:36
  • @Alex I found this trouble: ```python (128 + (img_arr - 128) * 1 != 128 + (img_arr - 128) * 1.0).sum() Out[14]: 21893 ``` it is because of types convertion – Demetry Pascal Sep 23 '21 at 09:22
  • @Alex use conversion to int16 ```python (128 + (img_arr - 128) * 1 != 128 + (img_arr - 128) * 1.0).sum() Out[17]: 21893 img_arr = img_arr.astype(dtype = np.int16) (128 + (img_arr - 128) * 1 != 128 + (img_arr - 128) * 1.0).sum() Out[19]: 0 ``` – Demetry Pascal Sep 23 '21 at 09:27
23

There's already built a class called contrast in PIL module. You can simply use it.

from PIL import Image, ImageEnhance
image = Image.open(':\\Users\\omar\\Desktop\\Site\\Images\\obama.png')
scale_value=scale1.get()
image = ImageEnhance.Contrast(image).enhance(scale_value)
image.show()
orvi
  • 3,142
  • 1
  • 23
  • 36
  • 1
    Yes but I can't ajust the level of contrast. I want to have a scale that defines the level of contrast applied to the photo, and this command cannot be ajusted, or I don't know how to. If you need the other program I'm working on that applies what I'm talking about tell me I'll put it in the post. – Megzari Nassim Feb 04 '17 at 21:40
  • 1
    do you want to enhance the level of contrast ? o – orvi Feb 04 '17 at 21:52
  • Yes depending on the level the user chooses, the photo is more contrasted from the original one. Should I post the other program ? – Megzari Nassim Feb 04 '17 at 21:57
  • dont need. Use `contrast.enhance()` method for more contratsed then original one. Pass the user chooses into enhance method. Let me know if it work . – orvi Feb 04 '17 at 21:59
  • I found out that I could use `contrast = ImageEnhance.Contrast(image)` then to this I apply: `contrast_applied=contrast._Enhance(scale_value)` and use the `.get()` function on the scale like `scale_value=scale1.get()` And it should work. I'll update once I'm done. – Megzari Nassim Feb 05 '17 at 13:25
  • I update my answer based on your reply . Give me a upvote please :D – orvi Feb 05 '17 at 13:45
  • 1
    image = ImageEnhance.Contrast(image).enhance(scale_value) – ezChx Nov 17 '18 at 16:54
  • What if the image is already in a good contrast ? Is there a way to quantify the existing contrast value of the image and then enhance it if the value is low ? – Resham Wadhwa Jan 23 '19 at 08:07
  • This is the best. Thanks a lot – Trect Nov 26 '19 at 14:38