1

I've already opened my image and can access the individual pixels' RGB values, but what I'm trying to do now is apply a function to the RGB values of each of the pixels individually. That is, I don't want to apply it the same way across all the pixels in the entire image; I want to apply it differently depending on whether, for each individual pixel, the blue value is > red > green (rather than green > red > blue, etc, etc).

So my question is, how do I access the individual RGB elements within each pixel (as opposed to accessing all of the red, green, and blue values across the entire image at once)? Eventually my question will be "what's the fastest way to do this?" since it's obviously going to take a while to apply a function across each pixel individually but for now I'd be happy just to have any solution at all.

Thanks for any suggestions.

EDIT for clarity/more specificity:

I'm actually trying to apply a different set of instructions depending the ordering of 127.5 - abs(127.5 - red/green/blue)), not simply on the order of red>green>blue (as stated initially above, bc I was trying to simplify). Once that ordering is determined for a given pixel, then the appropriate set of instructions is applied. Again, this is pixel-by-pixel -- I'm not ordering things based on ALL red values across the image, just the rgbs of the individual pixels. So what I'm trying to do would look something like this (here I'm playing out just one of the six possible orders; I've omitted the five other possibilities for brevity):

def rgb_manip(red,green,blue):
    r_max = int(127.5 - abs(127.5 - red))
    g_max = int(127.5 - abs(127.5 - green))
    b_max = int(127.5 - abs(127.5 - blue))
    if r_max >= g_max >= b_max:
        if r_max >= g_max  +  b_max:
            new_red = red + g_max + b_max
            new_green = green - g_max
            new_blue = blue - b_max
        else:
            new_red = red + r_max
            new_green = green - r_max + b_max
            new_blue = blue - b_max
    # elif... And so on, with a different set of instructions for each of the 6 possibilities depending on the order of the r_max, g_max, b_max values (e.g., r_max >= b_max >= g_max or g_max >= r_max >= b_max, etc, etc)
Ellerochelle
  • 13
  • 1
  • 5
  • I would make six masks, one for each ordering of the RGB values, then apply your six functions to those six masks. – askewchan Apr 02 '14 at 02:37

1 Answers1

2

If you convert your image into an array, you can access the RGB values for one pixel, or one of the R, G, or B values for all pixels:

from __future__ import division
import numpy as np
from PIL import Image

im = Image.open(imfile)
arr = np.asarray(im)

arr[..., 0]  # All Red values
arr[..., 1]  # All Green values
arr[..., 2]  # All Blue values
arr[0, 0]    # RGB for first corner pixel
arr[m, n]    # RGB for pixel at [m, n]
arr[m, n, 0] # R value for pixel [m, n]
arr[m, n, c] # value for color c at pixel [m, n]

You can get a ranking for each pixel using argsort, as in:

def transform(a, c=255/2):
    return c - np.abs(c - a)

ranking = transform(arr).argsort(axis=-1)

which ranks the criterion values from smallest to largest value along the last (color) axis. So this gives a new array where each 'color' array instead of being the RGB values are the sorting of the transformed R, B, and G values (call them "R', B', G'"), so if the corner pixel had G' > B' > R', then ranking[0, 0] would be [0, 2, 1] because R' (0) is smallest, then next is B' (2), finally the largest is G' (1).

The advantage of doing the above is that you have an array that says which method to use on which pixel. It can have at most six orderings of the transformed channels. I suggest defining a separate function like so for each of orderings. Then, only one decision must be made within the function (the second nested if/else in your example), and it can be done with np.where which applies one thing to parts of an array where a condition is met, and another thing to the rest. This only works for two options, but if there are multiple options (if/elif/else), other techniques can work equally well.

def bgr(a):
    """ for when B' < G' < R'
    """
    t = transform(a)
    red, green, blue = a.transpose([2,0,1])
    # same as: red, green, blue = a[..., 0], a[..., 1], a[..., 2] 
    r_max, g_max, b_max = t.transpose([2,0,1])
    assert np.all((b_max <= g_max) & (g_max <= r_max)), "doesn't match rank"
    condition = r_max >= g_max + b_max
    new_red = np.where(condition, red + g_max + b_max, red + r_max)
    new_green = np.where(condition, green - g_max, green - r_max + b_max)
    new_blue = blue - b_max
    return np.dstack([new_red, new_green, new_blue])

this function only works for the first if in yours. I would make a new function for each of those six things, and fill them into a dict like so:

functions = {
        (0, 1, 2) : rgb, # for R'<G'<B'
        (0, 2, 1) : rbg, # for R'<B'<G'
        #etc...
        }

If your output has RGB values too:

out = np.empty_like(arr)

Then loop through all six rankings/functions:

for rank, func in functions.items():
    mask = np.all(transform(arr).argsort(-1) == rank, -1)
    out[mask] = func(arr[mask])
askewchan
  • 45,161
  • 17
  • 118
  • 134
  • Do the functions need to be *vectorized*? – wwii Apr 02 '14 at 04:00
  • Yes, the `mask` takes all the elements of the array that have the given ranking, then calling the function on `arr[rank_mask]` will pass an array with only those pixels to the function, so the functions must accept arrays. If you can't do that, you could loop `for pixel in arr[rank_mask]: function[rank](pixel)` If you need help doing either, just ask! – askewchan Apr 02 '14 at 14:50
  • 1
    Sorry, I know this is dumb but I'm just a beginner here- I'm still not sure I understand. Let's say I have an image that's just 4 pixels: ([200,150,220],[255,110,140],[20,0,130],[0,30,220]). And I have a function I've defined, e.g., rgb_function(R,G,B). How do I apply that function to all four pixels, but separately? *I should also note that I was actually just trying to simplify when I said I was trying to look at the order of R>G>B. In reality, I need to differentiate based on int(127.5 - abs(127.5-red/green/blue)) & then those values are also used in determining the new pixels I'm creating – Ellerochelle Apr 02 '14 at 15:30
  • @Ellerochelle, to apply something to each pixel, you use numpy functions, which in general apply "elementwise" (that is, if you have `f(x) = 2*x + 1`, then `f(arr)` will be the array with each element multiplied individually by `x`, then `1` added to each element. This is called a 'vectorised' function, because it acts on the entire array at once, instead of looping through and saying `for pixel in arr: pixel = pixel*2 + 1` – askewchan Apr 02 '14 at 18:06
  • To create a criteria for whichever function you want to choose, instead of using `argsort`, you can use `c = 255; c/2 - np.abs(c/2 - arr)`, which will then give you an array of that criteria for each pixel, with three values for each color (automatically, since it will do the same thing to the R, G and B channels). If you apply this to your 4-pixel image, you'd get: `[[ 55, 105, 35], [ 0, 110, 115], [ 20, 0, 125], [ 0, 30, 35]]` – askewchan Apr 02 '14 at 18:10
  • 1
    @askewchan The thing is, the function I'm trying to run for each pixel involves the use of the other color bands of that individual pixel, so I don't know if this can necessarily sorted into separate arrays according to color. I may literally need to apply the function on a pixel-by-pixel basis. For instance the formula for the red value of the new pixel I'm trying to create might be something like red + (127.5 - abs(127.5 - **green**)) - (127.5 - abs(127.5 - **blue**)) ...but, again, this is dependent on the individual values, so for a different pixel the new red might simply be red + 100 – Ellerochelle Apr 02 '14 at 18:41
  • @askewchan And thanks so much for your help, btw!! I really appreciate it. – Ellerochelle Apr 02 '14 at 18:42
  • 1
    Just to be clear (sorry again, total newbie here)... Step 1 is to sort out the order of 127.5 - abs(127.5-red/blue/green), and then according to that order (which has 6 possible outcomes), step 2 is to apply a different set of instructions depending on which of those six possibilities we're working with for each individual pixel, and these instructions sometimes involve the values of the other color bands of the same pixel (like in the example I gave above of the new red value for a pixel being equal to red + abs(127.5 - green)), etc, etc) – Ellerochelle Apr 02 '14 at 18:50
  • @Ellerochelle, Would you mind editing your question with the specifics you've written in these comments, and perhaps just a little bit more? I think everything you want to do is doable with a single call of the function as in my answer. – askewchan Apr 02 '14 at 19:20
  • @Ellerochelle, alternatively, you could loop through each pixel, and apply the appropriate function to each pixel, and if you post the code to do that, I (or someone else) could help you vectorise the loop. – askewchan Apr 02 '14 at 19:28
  • @askewchan Thanks, I just edited my original question above. Yeah, I was thinking this should be looped somehow but being a newbie I'm not quite sure how that would work in this case. – Ellerochelle Apr 02 '14 at 19:50
  • 1
    Not sure if this matters, but I should maybe mention that I need to create a new image here based on the new pixel values (as opposed to making changes within the original) – Ellerochelle Apr 02 '14 at 20:02
  • @Ellerochelle, Yes that does matter. My updated answer takes your example function as one, and works on the entire array at once. It does not modify the original array (it was the `+=`s in the earlier versions that modified the array in place). – askewchan Apr 02 '14 at 20:26
  • If you have any questions, just ask. Also, I suggest keeping around your old method if you can get it to work, now that you have some better understanding, to make sure that it gives the same result as the new version! – askewchan Apr 02 '14 at 20:34