Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PIL - apply the same operation to every pixel

I create an image and fill the pixels:

img = Image.new( 'RGB', (2000,2000), "black") # create a new black image
pixels = img.load() # create the pixel map

for i in range(img.size[0]):    # for every pixel:
    for j in range(img.size[1]):
      #do some stuff that requires i and j as parameter

Can this be done more elegant (and may be faster, since theoretically the loops are parallelizable)?

like image 256
vlad_tepesch Avatar asked Jun 16 '16 15:06

vlad_tepesch


1 Answers

Note: I will first answer the question, then propose an, in my opinion, better alternative

Answering the question

It is hard to give advice without knowing what changes you intend to apply and whether the loading of the image as a PIL image is part of the question or a given.

  • More elegant in Python-speak typically means using list comprehensions
  • For parallelization, you would look at something like the multiprocessing module or joblib

Depending on your method of creating / loading in images, the list_of_pixels = list(img.getdata()) and img.putdata(new_list_of_pixels) functions may be of interest to you.

An example of what this might look like:

from PIL import Image
from multiprocessing import Pool

img = Image.new( 'RGB', (2000,2000), "black")

# a function that fixes the green component of a pixel to the value 50
def update_pixel(p):
    return (p[0], 50, p[2])

list_of_pixels = list(img.getdata())
pool = Pool(4)
new_list_of_pixels = pool.map(update_pixel, list_of_pixels)
pool.close()
pool.join()
img.putdata(new_list_of_pixels)

However, I don't think that is a good idea... When you see loops (and list comprehensions) over thousands of elements in Python and you have performance on your mind, you can be sure there is a library that will make this faster.

Better Alternative

First, a quick pointer to the Channel Operations module, Since you don't specify the kind of pixel operation you intend to do and you clearly already know about the PIL library, I'll assume you're aware of it and it doesn't do what you want.

Then, any moderately complex matrix manipulation in Python will benefit from pulling in Pandas, Numpy or Scipy...

Pure numpy example:

import numpy as np
import matplotlib.pyplot as plt
#black image
img = np.zeros([100,100,3],dtype=np.uint8)
#show
plt.imshow(img)
#make it green
img[:,:, 1] = 50
#show
plt.imshow(img)

Since you are just working with a standard numpy.ndarray, you can use any of the available functionalities, such as np.vectorize, apply, map etc. To show a similar solution as above with the update_pixel function:

import numpy as np
import matplotlib.pyplot as plt
#black image
img = np.zeros([100,100,3],dtype=np.uint8)
#show
plt.imshow(img)
#make it green
def update_pixel(p):
    return (p[0], 50, p[2])
green_img = np.apply_along_axis(update_pixel, 2, img)
#show
plt.imshow(green_img)

One more example, this time calculating the image content directly from the indexes, instead of from existing image pixel content (no need to create an empty image first):

import numpy as np
import matplotlib.pyplot as plt

def calc_pixel(x,y):
    return np.array([100-x, x+y, 100-y])

img = np.frompyfunc(calc_pixel, 2, 1).outer(np.arange(100), np.arange(100))    
plt.imshow(np.array(img.tolist()))
#note: I don't know any other way to convert a 2D array of arrays to a 3D array...

And, low and behold, scipy has methods to read and write images and inbetween, you can just use numpy to manipulate them as "classic" mult-dimensional arrays. (scipy.misc.imread depends on PIL, by the way)

More example code.

like image 118
Dirk Avatar answered Oct 12 '22 23:10

Dirk