4

I found this code on here but all it does is count black and red, and this would only work with a black and red image.

from PIL import Image
im = Image.open('oh.png')

black = 0
red = 0

for pixel in im.getdata():
    if pixel == (0, 0, 0, 255): # if your image is RGB (if RGBA, (0, 0, 0, 255) or so
        black += 1
    else:
        red += 1
print('black=' + str(black)+', red='+str(red))

How would I be able to check different colours and not have it so precise, for example black could be (0, 0, 0) to (40,40,40).

This may be too much to ask, just let me know. Thanks :)

15
  • What are you actually trying to do? There might be 16 million colours in an image... Commented May 26, 2018 at 17:17
  • I want it to look at a range of options, for example when looking for black it looks for pixels between (0, 0 ,0) and (40, 40, 40). It may be too complicated to do in python im not sure Commented May 26, 2018 at 17:20
  • It can be done in Python, I just wanted to understand what you plan to do with the list. Are there 32 distinct colours that interest you and you want to know which of them are present, for example? Or do you want to find the statistical variations in your image? Or are you trying to tell if some tone is present? Commented May 26, 2018 at 17:24
  • There will be a couple groups of data with example data already in it, the data that is collected from that point on will be sorted into the correct group depending on what its most similar to. Using it for machine learning. It will be on a very basic level for example guessing the fruit Commented May 26, 2018 at 17:28
  • So you want to map every pixel in each new image to the nearest colour in some pre-existing "reference" image? Or at least calculate the distance to the nearest pre-existing colour? Commented May 26, 2018 at 17:33

4 Answers 4

2

Here is an approach using a KDTree for efficient nearest color lookup. Note that while KDTrees may be pretty advanced, using them is actually quite simple.

import numpy as np
from matplotlib import colors
from scipy.spatial import cKDTree as KDTree
from scipy.misc import face

REDUCED_COLOR_SPACE = True

# borrow a list of named colors from matplotlib
if REDUCED_COLOR_SPACE:
    use_colors = {k: colors.cnames[k] for k in ['red', 'green', 'blue', 'black', 'yellow', 'purple']}
else:
    use_colors = colors.cnames

# translate hexstring to RGB tuple
named_colors = {k: tuple(map(int, (v[1:3], v[3:5], v[5:7]), 3*(16,)))
                for k, v in use_colors.items()}
ncol = len(named_colors)

if REDUCED_COLOR_SPACE:
    ncol -= 1
    no_match = named_colors.pop('purple')
else:
    no_match = named_colors['purple']

# make an array containing the RGB values 
color_tuples = list(named_colors.values())
color_tuples.append(no_match)
color_tuples = np.array(color_tuples)

color_names = list(named_colors)
color_names.append('no match')

# get example picture
img = face()

# build tree
tree = KDTree(color_tuples[:-1])
# tolerance for color match `inf` means use best match no matter how
# bad it may be
tolerance = np.inf
# find closest color in tree for each pixel in picture
dist, idx = tree.query(img, distance_upper_bound=tolerance)
# count and reattach names
counts = dict(zip(color_names, np.bincount(idx.ravel(), None, ncol+1)))

print(counts)

import pylab

pylab.imshow(img)
pylab.savefig('orig.png')
pylab.clf()
pylab.imshow(color_tuples[idx])
pylab.savefig('minimal.png' if REDUCED_COLOR_SPACE else 'reduced.png')

Output with full matplotlib named color space:

{'aliceblue': 315, 'antiquewhite': 0, 'aqua': 0, 'aquamarine': 0, 'azure': 0, 'beige': 27, 'bisque': 0, 'black': 88584, 'blanchedalmond': 0, 'blue': 0, 'blueviolet': 0, 'brown': 0, 'burlywood': 76, 'cadetblue': 0, 'chartreuse': 0, 'chocolate': 0, 'coral': 0, 'cornflowerblue': 0, 'cornsilk': 0, 'crimson': 0, 'cyan': 0, 'darkblue': 0, 'darkcyan': 0, 'darkgoldenrod': 0, 'darkgray': 0, 'darkgreen': 4148, 'darkgrey': 71985, 'darkkhaki': 32907, 'darkmagenta': 0, 'darkolivegreen': 90899, 'darkorange': 0, 'darkorchid': 0, 'darkred': 0, 'darksalmon': 0, 'darkseagreen': 30171, 'darkslateblue': 134, 'darkslategray': 108608, 'darkslategrey': 0, 'darkturquoise': 0, 'darkviolet': 0, 'deeppink': 0, 'deepskyblue': 0, 'dimgray': 0, 'dimgrey': 108318, 'dodgerblue': 0, 'firebrick': 0, 'floralwhite': 0, 'forestgreen': 1, 'fuchsia': 0, 'gainsboro': 10438, 'ghostwhite': 736, 'gold': 0, 'goldenrod': 0, 'gray': 0, 'green': 0, 'greenyellow': 0, 'grey': 79835, 'honeydew': 0, 'hotpink': 0, 'indianred': 0, 'indigo': 0, 'ivory': 0, 'khaki': 1056, 'lavender': 4650, 'lavenderblush': 46, 'lawngreen': 0, 'lemonchiffon': 0, 'lightblue': 3, 'lightcoral': 0, 'lightcyan': 0, 'lightgoldenrodyellow': 0, 'lightgray': 11905, 'lightgreen': 2323, 'lightgrey': 0, 'lightpink': 0, 'lightsalmon': 0, 'lightseagreen': 0, 'lightskyblue': 0, 'lightslategray': 0, 'lightslategrey': 31920, 'lightsteelblue': 3590, 'lightyellow': 0, 'lime': 0, 'limegreen': 0, 'linen': 46, 'magenta': 0, 'maroon': 0, 'mediumaquamarine': 0, 'mediumblue': 0, 'mediumorchid': 0, 'mediumpurple': 15, 'mediumseagreen': 0, 'mediumslateblue': 0, 'mediumspringgreen': 0, 'mediumturquoise': 0, 'mediumvioletred': 0, 'midnightblue': 54, 'mintcream': 0, 'mistyrose': 19, 'moccasin': 0, 'navajowhite': 0, 'navy': 0, 'oldlace': 0, 'olive': 0, 'olivedrab': 30828, 'orange': 0, 'orangered': 0, 'orchid': 0, 'palegoldenrod': 1499, 'palegreen': 285, 'paleturquoise': 0, 'palevioletred': 0, 'papayawhip': 0, 'peachpuff': 0, 'peru': 21, 'pink': 0, 'plum': 0, 'powderblue': 0, 'purple': 0, 'rebeccapurple': 0, 'red': 0, 'rosybrown': 2831, 'royalblue': 0, 'saddlebrown': 0, 'salmon': 0, 'sandybrown': 0, 'seagreen': 0, 'seashell': 0, 'sienna': 5, 'silver': 35951, 'skyblue': 0, 'slateblue': 0, 'slategray': 7836, 'slategrey': 0, 'snow': 18, 'springgreen': 0, 'steelblue': 0, 'tan': 3925, 'teal': 0, 'thistle': 10274, 'tomato': 0, 'turquoise': 0, 'violet': 0, 'wheat': 21, 'white': 3, 'whitesmoke': 834, 'yellow': 0, 'yellowgreen': 9292, 'no match': 0}

Output with only basic colors:

{'red': 0, 'green': 403561, 'blue': 3262, 'black': 153782, 'yellow': 225827, 'no match': 0}

Original picture:

enter image description here

Reduced color version:

enter image description here

Basic color versioin:

enter image description here

Sign up to request clarification or add additional context in comments.

19 Comments

@MiniMiloe What's your Python version?
It is 3.4.3 I have problems with the latest 3.6 i think
@MiniMiloe ok, could you please try replacing (*map(int, (v[1:3], v[3:5], v[5:7]), 3*(16,)),) with tuple(map(int, (v[1:3], v[3:5], v[5:7]), 3*(16,)))?
Invalid sytax, the line now says: named_colors = {k: (*map(int, (v[1:3], v[3:5], v[5:7]), 3*(16,)), with tuple(map(int, (v[1:3], v[3:5], v[5:7]), 3*(16,))) correct?
@MiniMiloe no, everything from (* up to and including "with" should not be there.
|
1

Paul's answer is more elegant, but basically I think you can solve it by defining a function that gives you a "distance" between any two RGB colours something like this:

def distance(col1, col2):
    (r1,g1,b1) = col1
    (r2,g2,b2) = col2
    return (r1 - r2)**2 + (g1 - g2) ** 2 + (b1 - b2) **2

Now, all you need to do (in pseudo-code) is:

load your pre-existing reference colours into a list
load your new image
for each pixel in new image
    # Calculate distance from this pixel to first one in reference list
    mindist=distance(this pixel, first pixel in reference colours)
    nearest=first pixel in reference colours
    # Now see if any other colour in reference list is closer
    for index=1 to number of colours in reference list
        d=distance(this pixel, reference list[index])
        if d < mindist:
           mindist=d
           nearest=reference list[index]
    end
    replace pixel with nearest one from reference list as determined above
end

I am still learning Python, so my translation of the above may not be optimal but it works!

#!/usr/local/bin/python3
from PIL import Image
import numpy as np

# Calculate distance in RGB space between two RGB pixels
def distance(col1, col2):
    r1,g1,b1 = col1
    r2,g2,b2 = col2
    return (r1 - r2)**2 + (g1 - g2) ** 2 + (b1 - b2) **2

# All colours in the image will be forced to nearest one in this list
refColours=(
      [[255,   0,   0],    # red
       [  0, 255,   0],    # green
       [  0,   0, 255],    # blue
       [255, 255,   0],    # yellow
       [  0, 255, 255],    # cyan
       [255,   0, 255],    # magenta
       [  0,   0,   0],    # black
       [255, 255, 255]])   # white

# Load your new image and note its width and height
im = Image.open('colorwheel.png')
imrgb = im.convert("RGB")
(w,h)=im.size[0],im.size[1]

# Make a buffer for our output pixels
px=np.zeros(w*h,dtype=np.uint8)
idx=0

for pixel in list(imrgb.getdata()):
   # Calculate distance from this pixel to first one in reference list
   mindist = distance([pixel[0],pixel[1],pixel[2]],refColours[0])
   nearest = 0
   # Now see if any other colour in reference list is closer
   for index in range(1,len(refColours)):
       d=distance([pixel[0],pixel[1],pixel[2]],refColours[index])
       if d < mindist:
          mindist=d
          nearest=index
   # Set output pixel to nearest
   px[idx]=nearest
   idx += 1

# Reshape our output pixels to match input image
px=px.reshape(w,h)
# Make output image from our pixels
outimg=Image.fromarray(px).convert('P')
# Put our palette of favourite colours into the output image
palette=[item for sublist in refColours for item in sublist]
outimg.putpalette(palette)
outimg.save("result.png")

So, I start with this as colorwheel.png:

enter image description here

and end up with this:

enter image description here


Of course, the easier solution, like I suggested in the comments is to use a tool like ImageMagick to remap the colours in your new image to those in your "reference" image, which you do like this on the command-line:

convert colorwheel.png +dither -remap colormap.png result.png

as shown in my other answer here. So in Python, you could do that with the system() call or using the subprocess module:

cmd="https://stackoverflow.com/a/38328879/2836621"
system(cmd)

Comments

0

If it's only doing red & black, you can just check if the red value is less than or equal to 40.

if pixel[0] <= 40:

Edit:

colors = {}

for pixel in im.getdata():
    r = pixel[0]
    g = pixel[1]
    b = pixel[2]

    color = ''
    brightness = ''
    avg = (r + g + b) / 3

    if avg < 40 then brightness = 'black'
    else if avg < 80 then brightness = 'dark'
    else if avg > 220 then brightness = 'white'
    else if avg > 150 then brightness = 'light'

    if avg / r > 0.9 then hue = 'red'
    else if avg / r > 0.8 and avg / g > 0.6 then hue = 'orange'
    else if avg / r > 0.7 and avg / g > 0.7 then hue = 'yellow'
    else if avg / g > 0.8 and avg / r > 0.6 then hue = 'lime'
    else if avg / g > 0.9 then hue = 'green'
    else if avg / g > 0.7 and avg / b > 0.7 then hue = 'cyan'
    else if avg / b > 0.9 then hue = 'blue'
    else if avg / b > 0.8 and avg / r > 0.6 then hue = 'indigo'
    else if avg / b > 0.7 and avg / r > 0.7 then hue = 'magenta'

    color = brightness + hue
    if color not in colors:
        colors[color] = 1
    else
        colors[color] = colors[color] + 1

1 Comment

I want it to look at all colours though, not sure how to add it
0

You'll have to define your colour buckets. I'd recommend using hsv colour space.

from PIL import Image
import colorsys
im = Image.open('download.png')

NUM_BUCKETS = 6 # Just for example
colour_counts = [0] * NUM_BUCKETS

for pixel in im.getdata():
    hue, saturation, value = colorsys.hsv_to_rgb(pixel[0], pixel[1], pixel[2])
    hue_bucket = hue * NUM_BUCKETS // 255 # Using python3 to get an int
    colour_counts[hue_bucket] += 1

colour_names = ["red", "yellow", "green", "cyan", "blue", "magenta"]
for name, count in [x for x in zip(colour_names, colour_counts)]:
    print("{n} = {c}".format(n=name, c=count))

So, this just partitions the colour space into 6, but you can use any number you'd like (you'll just have to think of names for all of them). Black and white don't work well, because we're looking at hues. To capture black and white as well use the "value" and "saturation", i.e:

for pixel in im.getdata():
        hue, saturation, value = colorsys.hsv_to_rgb(pixel[0], pixel[1], pixel[2])
        if value < 32: # It's very dark
            blacks += 1
        elif saturation < 32 and value > 224: # it's basically white
            whites += 1
        else: # if it fails both of those, we can call it a colour
            hue_bucket = hue * NUM_BUCKETS // 255 # Using python3 to get an int
            colour_counts[hue_bucket] += 1

Black is characterized by a low value. White has a high value and a low saturation. Low saturation colours are grey. I generally find hsv to be way more comprehensible than rgb.

6 Comments

I get a syntax error at, print("{n} = "{c}".format(n=name, c=count")
Shoot, you're totally right, I added an extra quotation mark
It says list index out of range, line 14
Works fine for me, sure you copy/pasted accurately?
Yep, just retried. Do I need the botoom block of code too?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.