Reputation: 75
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 :)
Upvotes: 4
Views: 8273
Reputation: 208003
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
:
and end up with this:
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)
Upvotes: 1
Reputation: 53099
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:
Reduced color version:
Basic color versioin:
Upvotes: 2
Reputation: 747
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
.
Upvotes: 0
Reputation: 2145
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
Upvotes: 0