1

So I made this script that takes an image and turns it into a gray scale of itself.

I know that a lot of modules can do this automatically like .convert('grey') but I want to do it manually by myself to learn more about python programming.

It works ok but its very slow, for a 200pX200p image it takes 10 seconds so, what can I modify for making it go faster?

it works like this, it takes a pixel, calculates the averange of R, G and B values, set the three to the averange value, adds 40 to each one for more brightness and writes the pixel. Here is the code:

import imageio
import os
from PIL import Image, ImageDraw
from random import randrange


img = '/storage/emulated/0/DCIM/Camera/IMG_20190714_105429.jpg'
f = open('network.csv', 'a+')
pic = imageio.imread(img)
picture = Image.open(img)
draw = ImageDraw.Draw(picture)
f.write('\n')

def por():
    cien = pic.shape[0] * pic.shape[1]
    prog = pic.shape[1] * (h - 1) + w
    porc = prog * 100 / cien
    porc = round(porc)
    porc = str(porc)
    print(porc + '%')
rh = int(pic.shape[0])
wh = int(pic.shape[1])
for h in range(rh):
    for w in range(wh):
        prom = int(pic[h , w][0]) + int(pic[h, w][1]) + int(pic[h, w][2])
        prom = prom / 3
        prom = round(prom)
        prom = int(prom)
        prom = prom + 40
        por()
        draw.point( (w,h), (prom,prom,prom))
picture.save('/storage/emulated/0/DCIM/Camera/Modificada.jpg')
Vasu Deo.S
  • 1,820
  • 1
  • 11
  • 23
Nico Perez
  • 43
  • 1
  • 7
  • PIL already offers fast conversions to greyscale, why do it manually? – MisterMiyagi Jul 14 '19 at 14:58
  • 1
    Possible duplicate of [How can I convert an RGB image into grayscale in Python?](https://stackoverflow.com/questions/12201577/how-can-i-convert-an-rgb-image-into-grayscale-in-python) – MisterMiyagi Jul 14 '19 at 14:59

3 Answers3

3

PIL does this for you.

from PIL import Image
img = Image.open('image.png').convert('grey')
img.save('modified.png')
vekerdyb
  • 1,213
  • 12
  • 26
0

The Method you are using for conversion of RGB to greyscale, is called Averaging.

from PIL import Image

image = Image.open(r"image_path").convert("RGB")

mapping = list(map(lambda x: int(x[0]*.33 + x[1]*.33 + x[2]*.33), list(image.getdata())))

Greyscale_img = Image.new("L", (image.size[0], image.size[1]), 255)

Greyscale_img.putdata(mapping)

Greyscale_img.show()

The above method (Averaging) isn't recommended for conversion of an colored image into greyscale. As it treats each color channel equally, assuming human perceives all colors equally (which is not the truth).

You should rather use something like ITU-R 601-2 luma transform (method used by PIL for converting RGB to L) for the conversion. As it uses perceptual luminance-preserving conversion to grayscale.

For that Just replace the line

mapping = list(map(lambda x: int(x[0]*.33 + x[1]*.33 + x[2]*.33), list(image.getdata())))

with

mapping = list(map(lambda x: int(x[0]*(299/1000) + x[1]*(587/1000) + x[2]*(114/1000)), list(image.getdata())))

P.S.:- I didn't add 40 to each pixel value, as it doesn't really makes any sense in the conversion of the image to greyscale.

Vasu Deo.S
  • 1,820
  • 1
  • 11
  • 23
  • You could also consider doing the arithmetic ln linear space, it's a bit more accurate. See eg. https://stackoverflow.com/a/31904212/894763 – jcupitt Jul 15 '19 at 12:54
  • @jcupitt there is way too much intel, in your answer. So can you please explicitly define what you mean by *arithmetic in Linear Space*? – Vasu Deo.S Jul 15 '19 at 15:45
  • Hello, images usually have a gamma applied to them, so numbers in the file are not proportional to the number of photons, instead it's photons ** n, where n is eg. approximately 1.0 / 2.2 for sRGB. If you combine eg. a medium R and B with a bright G, you'll get a distorted luminance value. It's better to convert to a space where pixels are proportional to photons, combine channels, then convert back. – jcupitt Jul 15 '19 at 21:21
  • @jcupitt, Thanks for telling me this info!! Btw I have never used a sRGB image, so i am not familiar with what they are and how they work differently then RGB? Is it a separate color mode (like *RGBA*) or is it more of a property of RGB. Secondly, can you please provide a source from where I can learn about *sRGB* images, and the whole photon thing. – Vasu Deo.S Jul 16 '19 at 06:43
  • sRGB is just the most common sort of RGB -- the wiki page has the equations https://en.wikipedia.org/wiki/SRGB The section on the transfer function explains the relationship between photons per second and the sRGB numerical values. – jcupitt Jul 16 '19 at 08:00
  • @jcupitt From doing a little bit of search it turns out most of the images, that we do consider RGB are actually sRGB, and most people recommend it over RGB for most use cases. – Vasu Deo.S Jul 16 '19 at 08:05
0

Python is an interpreted language and not really fast enough for pixel loops. cython is a sister project that can compile Python into an executable and can be faster than plain Python for code like this.

You could also try using a Python math library like numpy or pyvips. These add array operations to Python: you can write lines like a += 12 * b where a and b are whole images and they'll operate on every pixel at the same time. You get the control of being able to specify every detail of the operation yourself combined with the speed of something like C.

For example, in pyvips you could write:

import sys
import pyvips

x = pyvips.Image.new_from_file(sys.argv[1], access="sequential")
x = 299 / 1000 * x[0] + 587 / 1000 * x[1] + 114 / 1000 * x[2]
x.write_to_file(sys.argv[2])

Copying the equation from Vasu Deo.S's excellent answer, then run with something like:

./grey2.py ~/pics/k2.jpg x.png

To read the JPG image k2.jpg and write a greyscale PNG called x.png.

You can approximate conversion in linear space with a pow before and after, assuming your source image is sRGB:

x = x ** 2.2
x = 299 / 1000 * x[0] + 587 / 1000 * x[1] + 114 / 1000 * x[2]
x = x ** (1 / 2.2)

Though that's not exactly correct since it's missing the linear part of the sRGB power function.

You could also simply use x = x.colourspace('b-w'), pyvips's built-in greyscale operation.

jcupitt
  • 10,213
  • 2
  • 23
  • 39