how to avoid reprocessing same image band and create separate images using PILLOW Image module

Question:

I’m trying to process the same image in 9 ways (2 variables: channel[R,G,B] and intensity of each specific channel[0.1, 0.5, 0.9]) for the purpose of creating a "contact sheet" (the same images shown several times on the same page in different color tones) using nested for-loops in Python (see below):

# split the image into individual bands
source = image.split()

# for channels 0 to 2 (0=red, 1=green, 2=blue), change channel value intensity
levels=[0.1, 0.5, 0.9] #levels of intensities to use for each channel
images=[] #new list to hold new images created
for chan in range(3):
    # for each channel/band, change the intensity of the particular band
    for int in levels:
        #process the particular band according to intensity
        newpix=source[chan].point(lambda i: i * int)
        
        #Paste the processed band back
        source[chan].paste(newpix)
    
        # merge back into a new multiband image
        newimg=Image.merge(image.mode, source)
        
        #append to images
        images.append(newimg)

The above is the portion of codes for creating the 9 separate images (arranging into rows is handled later) and it doesn’t work correctly, as it seems to be pasting each new processed image to the previous band, resulting in the each set (for each channel: "Red", "Green", and "Blue") of 3 images to be about the same shade and intensity. The first 3 was supposed to look greenish, with the red band changed to intensities less than normal, with the first one looking with the strongest green tone, and the 3rd looking closer to normal. Then the next 3 was supposed to look reddish, since the green channel was being manipulated. However, with this loop, the 2nd row looks dark blue. And the 3rd row was very dark, almost black (supposed to look yellowish).

If I switch the order of the elements in levels (i.e. [0.9, 0.5, 0.1], meaning the intensity of the particular band being manipulated gets less), then with each rendition, the images get progressively darker (from the other 2 bands) and the tone of the previous image stays on and not get rewritten. For example, the first image of the 2nd row now looks green as well, since the strong green tone of the image with red=0.1 intensity is now combined with the almost normal tone of the image with green=0.9 intensity).

I tested the algorithm without the loop and also by having only 1 level of intensity, say levels=[0.5], and both gave images with the right color tones, so I’m guessing that the paste function inside the loop just keeps getting the new image pasted on, or maybe the issue is in the next step with the merge. But, I can’t find in the documentation how to re-initialize the split-band file("source") each time, if that’s the solution. Obviously, I can simply not have the loop for the intensities, but having the loop would be more efficient.

Asked By: kerbe

||

Answers:

I’d suggest using Numpy for pixel level operations like this. It makes applying maths operations that operate on regular slices easy and very fast.

I’d note that you need to ensure that you need to make sure that you need to keep an original image around somewhere so that the operations on each channel remain independent.

Something like:

import requests
from PIL import Image
import numpy as np
from io import BytesIO

# grab a random image
res = requests.get("https://picsum.photos/200")
res.raise_for_status()

# load it into Python
image = Image.open(BytesIO(res.content))

# interpret image as 3d numpy array
imarr = np.array(image)

# create an image to store the output
imres = Image.new('RGB', (620, 620), 'white')

# loop over channels and multipliers
for x, chan in enumerate(range(3)):
    for y, mul in enumerate([0.1, 0.5, 0.9]):
        # make a fresh copy
        outarr = imarr.copy()
        # perform operation on channel
        outarr[:,:,chan] = outarr[:,:,chan] * mul
        # turn back into an image
        imout = Image.fromarray(outarr)
        # put it onto contact sheet
        imres.paste(imout, (x * 205 + 5, y * 205 + 5))

Which gave me something like:

contact sheet

Answered By: Sam Mason
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.