1
for y in range(image.getHeight()):
    for x in range(image.getWidth()):

def GetDistance(tuple1,tuple2):
    tuple1=(r,g,b)
    tuple2=(r2,g2,b2)
    import math
    math.sqrt(((r-r2)**2)+((g-g2)**2)+((b-b2)**2))
    return

I am trying to compare to pixel colors in an image. For the colors that are the closest to red I want to change to blue, the closest to blue I want to change to green, and the color in the image that are closest to green I want to change to red. How can I get the color pixels I am looking for in the image?

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
  • Please click [edit] and add a sample image and the expected output/result. Do you mean your output image will consist purely of 3 perfect colours - red, green and blue? – Mark Setchell Nov 06 '22 at 09:55

1 Answers1

1

Personally, I wouldn't approach this using for loops as they are slow, inefficient and error-prone in Python. However, I have implemented it with for loops so it matches your way of thinking and is easier to understand. If you look through some of my other answers you will find better ways of doing it.

I note a couple of things:

  • you don't need to take the square root of the distances, you can simply compare the squared distances. If a**2 > b**2, it follows a>b, as long as there are no negatives involved which will always be the case here because both a and b are themselves the sums of squares

  • as your output image will consist of just 3 colours, it can best be stored as a palette image - see here. So I did that.


#!/usr/bin/env python3

from PIL import Image

def closestColour(px):
    """Returns palette index for whichever colour (out of R, G and B) is nearest"""
    # Unpack the tuple into its constituent parts
    R, G, B = thispx

    # Calculate distance to solid Red, solid Green and solid Blue corners of colour cube
    # No need for sqrt(), if a**2 < b**2 we know a>b as long as no negative numbers are involved and there aren't because a amd b are sums of squares
    RdistSquared = (255-R)**2 + G**2       + B**2
    GdistSquared = R**2       + (255-G)**2 + B**2
    BdistSquared = R**2       + G**2       + (255-B)**2

    # Find index of minimum value in list
    values = [RdistSquared, GdistSquared, BdistSquared]
    mi = values.index(min(values))

    # Map red to blue, blue to green and green to red
    lkup = [2, 0, 1]
    return lkup[mi]


# Open input image and load pixels
im = Image.open('billiards.jpg')
px = im.load()

# List of output pixels
outpx = []
for y in range(im.height):
    for x in range(im.width):
        thispx = px[x,y]
        outpx.append(closestColour(thispx))

# Create palettised output image, same size as original
out = Image.new('P', im.size)

# Stuff in the list of pixels
out.putdata(outpx) 

# Stuff in the palette, index 0 = Red, index 1 = Green, index 2 = Blue
out.putpalette([255,0,0, 0,255,0, 0,0,255])

out.save('result.png')

That transforms this:

enter image description here

Photo by Sandesh Sharma on Unsplash

into this:

enter image description here


By the way, there is no real need to write any Python to do this, you can just do it in the Terminal with ImageMagick, like this:

# Make a small palette of the colours we want to remap to
magick xc:red xc:lime xc:blue +append PNG8:rgb-palette.png

enter image description here

# Remap to the new palette, without dithering and then re-order colour channels
magick billiards.jpg +dither -remap rgb-palette.png -channel-fx ',1,2,0' result.png
Mark Setchell
  • 191,897
  • 31
  • 273
  • 432