Change Palette color index in Python - python

I got this image.
The image is PNG, in mode P, palette is mode RGB.
I need to stay with 16 colors, as I want the image as 4bpp.
And I need to change his palette, making the color pink (255, 192, 203) its first index.
The image palette is:
{(255, 255, 232): 0, (255, 192, 203): 1, (210, 204, 147): 2, (62, 214, 108): 3, (59, 193, 95): 4, (209, 174, 99): 5, (194, 164, 92): 6, (130, 186, 185): 7, (180, 148, 83): 8, (95, 152, 121): 9, (49, 161, 88): 10, (157, 123, 59): 11, (118, 97, 55): 12, (52, 128, 119): 13, (73, 80, 63): 14, (63, 59, 47): 15}
And I want:
{(255, 192, 203): 0, (255, 255, 232): 1, (210, 204, 147): 2, (62, 214, 108): 3, (59, 193, 95): 4, (209, 174, 99): 5, (194, 164, 92): 6, (130, 186, 185): 7, (180, 148, 83): 8, (95, 152, 121): 9, (49, 161, 88): 10, (157, 123, 59): 11, (118, 97, 55): 12, (52, 128, 119): 13, (73, 80, 63): 14, (63, 59, 47): 15}
I made this code for it:
def change_palette(im):
colors = im.palette.colors #get colors from palette
if(PINK not in colors): #check there is pink (could be possible that there is not)
return
first = list(colors.keys())[list(colors.values()).index(0)] #colors is a dict so I get the key of the first index
colors[first] = colors[PINK]
colors[PINK] = 0 #change the value of the colors
newcolors = {}
newcolors[PINK] = colors.pop(PINK)
for key in colors:
newcolors[key] = colors[key] #reorder the dict so it is in order
colors = newcolors
newcolors = []
for key in colors:
for c in key:
newcolors.append(c) #make it a list
im.putpalette(newcolors) #change palette
But it makes the image look like this.
I tried it with other methods, like not using the putpalette and changing only the colors of the palette, but that does not make any change when I save the image.
I understand the problem, but I can't seem to find a solution.
I want it to look exactly the same, but with the color pink (255, 192, 203) in the first index.

If I understand correctly, you want to keep the image unchanged, and replace the index of the pink color to be 0.
When we modify the palette, we are switching between the two colors:
All the pixels with color (255, 255, 232) are switched to pink color (255, 192, 203), and all the pixels with pink color (255, 192, 203) are switched to color (255, 255, 232).
The reason is that the (index) values of the pixels are not changed.
After applying the new palette, all the pixels with (index) value 0 turned to be pink, and all the (index) value 1 turned to be (255, 255, 232).
For fixing that, we have to switch the image data as well (assuming pink color index equals 1):
All the pixels with (index) value 0 should be modified to be value 1.
All the pixels with (index) value 1 should be modified to be value 0.
For convenience we may convert the data to NumPy array, and convert back to PIL Image:
pink_index = colors[PINK] # Original index of pink color
...
indexed = np.array(im) # Convert to NumPy array to easier access.
new_indexed = indexed.copy() # Make a copy of the NumPy array
new_indexed[indexed == 0] = pink_index # Replace all original 0 pixels with pink_index
new_indexed[indexed == pink_index] = 0 # Replace all original pink_index pixels with 0
new_im = Image.fromarray(new_indexed) # Convert from NumPy array to Image.
new_im.putpalette(newcolors) # Set the palette
Complete code sample:
from PIL import Image
import numpy as np
PINK = (255, 192, 203)
def change_palette(im):
colors = im.palette.colors #get colors from palette
if(PINK not in colors): #check there is pink (could be possible that there is not)
return
first = list(colors.keys())[list(colors.values()).index(0)] #colors is a dict so I get the key of the first index
pink_index = colors[PINK]
colors[first] = colors[PINK]
colors[PINK] = 0 #change the value of the colors
newcolors = {}
newcolors[PINK] = colors.pop(PINK)
for key in colors:
newcolors[key] = colors[key] #reorder the dict so it is in order
colors = newcolors
newcolors = []
for key in colors:
for c in key:
newcolors.append(c) #make it a list
#im.putpalette(newcolors) #change palette
indexed = np.array(im) # Convert to NumPy array to easier access https://stackoverflow.com/a/33023875/4926757
new_indexed = indexed.copy() # Make a copy of the NumPy array
new_indexed[indexed == 0] = pink_index # Replace all original 0 pixels with pink_index
new_indexed[indexed == pink_index] = 0 # Replace all original pink_index pixels with 0
new_im = Image.fromarray(new_indexed) # Convert from NumPy array to Image https://stackoverflow.com/a/39258561/4926757
new_im.putpalette(newcolors) # Set the palette
return new_im
img = Image.open('original_image.png')
new_image = change_palette(img)
new_image.save('changed_image.png')
Result:

Related

How to generate random values that are distant from a list of values?

I have the following transparent images.
What I want to do is to paste them on an image with a background of a specific color. The color of the background is randomized like this:
rand1, rand2, rand3 = (random.randint(0, 255),
random.randint(0, 255),
random.randint(0, 255))
background = Image.new('RGBA', png.size, (rand1, rand2, rand3))
alpha_composite = Image.alpha_composite(background, png)
Unfortunately, some of the logos don't go well with their background colors. The background color sometimes comes close to color(s) inside the logo, which makes the logo either partially or completely invisible. Here is an example where the background color is almost identical to the orange color in the Ubuntu logo:
What I did was to get all of the colors from each logo and save them in a list of tuples like this. This is actually a list of lists of tuples. I've just edited it now to highlight which nested list of tuples belong to which logo:
Intel = [(0, 113, 197)]
Corsair = [(4, 7, 7)]
Google = [(66, 133, 244), (234, 67, 53), (251, 188, 5), (52, 168, 83), (0, 255, 255), (255, 128, 0), (255, 255, 0)]
Riot = [(209, 54, 57), (255, 255, 255), (226, 130, 132), (0, 0, 0)]
What I want to do is to use the above ^ information to randomly choose background colours so that no part of a logo is made invisible. I'm asking for suggestions on strategies to go about this..
This is the function that adds a background color to the logos:
def logo_background(path):
rand1, rand2, rand3 = (random.randint(0, 255),
random.randint(0, 255),
random.randint(0, 255))
png = Image.open(path).convert('RGBA')
colors = extcolors.extract_from_path(path)
background = Image.new('RGBA', png.size, (rand1, rand2, rand3))
alpha_composite = Image.alpha_composite(background, png)
return alpha_composite
>>> extcolors.extract_from_path(path)
[((0, 113, 197), 25727), 56235]
# for the intel logo, which is just blue with transparent background
Some logos are completely black. The corsair logo is an all black logo with transparent background but the code did not select the right background.
I think using a thre component vektor like rgb is difficult for randome choice. I would convert it to the hsv-color-system (hue, saturation, lightness) first. Then we only need to worry about hue and can use random.choice to choose a value from a 1D list of possible values.
Google = [(66, 133, 244), (234, 67, 53), (251, 188, 5), (52, 168, 83), (0, 255, 255), (255, 128, 0), (255, 255, 0)]
threshold = 0.1 #No hue value closer than threshold to a logo-color
# convert to hsv
GoogleHsv = [colorsys.rgb_to_hsv(r/255.0, g/255.0, b/255.0) for r,g,b in Google]
print("GoogleHsv:", GoogleHsv)
# list of possible hue-values that are at least threshold away from all logo-hue-values
choices = [x for x in np.linspace(0,1,201) if min([abs(x-h) for h,l,s in GoogleHsv]) > threshold]
print("choices:", choices)
h = random.choice(choices)
l = random.random() # random lightness
s = random.random() # random saturation
# you could also use constants for l,s to alway get a vibrant/dark color.
color = [int(x*255) for x in colorsys.hsv_to_rgb(h, l, s)] # converting back to rbg
print("color:", color)
Hope this helps. Have a nice day.
EDIT
For your function, it would look like this:
def logo_background(path):
threshold = 0.1
png = Image.open(path).convert('RGBA')
used_colors_rgb = extcolors.extract_from_path(path)[0]
used_hsv = [colorsys.rgb_to_hsv(r/255.0, g/255.0, b/255.0) for (r,g,b), _ in used_colors_rgb]
choices = [x for x in np.linspace(0,1,201) if min([abs(x-h) for h,l,s in used_hsv]) > threshold]
h, l, s = random.choice(choices), (random.random()+1) / 2, (random.random()+1) / 2
color = [int(x*255) for x in colorsys.hsv_to_rgb(h, l, s)]
background = Image.new('RGBA', png.size,tuple(color))
alpha_composite = Image.alpha_composite(background, png)
return alpha_composite
logo_background("google.png")

Using OpenCv to detect an image and count some basic color of it

import numpy as np
import collections
def getColorList():
dict = collections.defaultdict(list)
# black
lower_black = np.array([0, 0, 0])
upper_black = np.array([180, 255, 46])
color_list = []
color_list.append(lower_black)
color_list.append(upper_black)
dict['black'] = color_list
# #gray
# lower_gray = np.array([0, 0, 46])
# upper_gray = np.array([180, 43, 220])
# color_list = []
# color_list.append(lower_gray)
# color_list.append(upper_gray)
# dict['gray']=color_list
# white
lower_white = np.array([0, 0, 221])
upper_white = np.array([180, 30, 255])
color_list = []
color_list.append(lower_white)
color_list.append(upper_white)
dict['white'] = color_list
#red
lower_red = np.array([156, 43, 46])
upper_red = np.array([180, 255, 255])
color_list = []
color_list.append(lower_red)
color_list.append(upper_red)
dict['red']=color_list
# red
lower_red = np.array([0, 43, 46])
upper_red = np.array([10, 255, 255])
color_list = []
color_list.append(lower_red)
color_list.append(upper_red)
dict['red2'] = color_list
#orange
lower_orange = np.array([11, 43, 46])
upper_orange = np.array([25, 255, 255])
color_list = []
color_list.append(lower_orange)
color_list.append(upper_orange)
dict['orange'] = color_list
#yellow
lower_yellow = np.array([26, 43, 46])
upper_yellow = np.array([34, 255, 255])
color_list = []
color_list.append(lower_yellow)
color_list.append(upper_yellow)
dict['yellow'] = color_list
#green
lower_green = np.array([35, 43, 46])
upper_green = np.array([77, 255, 255])
color_list = []
color_list.append(lower_green)
color_list.append(upper_green)
dict['green'] = color_list
#cyan
lower_cyan = np.array([78, 43, 46])
upper_cyan = np.array([99, 255, 255])
color_list = []
color_list.append(lower_cyan)
color_list.append(upper_cyan)
dict['cyan'] = color_list
#blue
lower_blue = np.array([100, 43, 46])
upper_blue = np.array([124, 255, 255])
color_list = []
color_list.append(lower_blue)
color_list.append(upper_blue)
dict['blue'] = color_list
# purple
lower_purple = np.array([125, 43, 46])
upper_purple = np.array([155, 255, 255])
color_list = []
color_list.append(lower_purple)
color_list.append(upper_purple)
dict['purple'] = color_list
return dict
if __name__ == '__main__':
color_dict = getColorList()
print(color_dict)
num = len(color_dict)
print('num=',num)
for d in color_dict:
print('key=',d)
print('value=',color_dict[d][1])
I wanted to put an image in the colorlist which could help me detect the basic color in the other image. So when I put an image which have red and white included it will show me the results.
Thank you very much.
I assume you want to check if any of the colors in the list are present in the image.
The color bounds in your list seem to be HSV, that is good.
First convert the input imge to HSV.
Then you can use inrange to get a mask: a grayscale image where the pixels in the color range are white (value 255) and all others are black (value 0).
Next you can use numpy's sum to sum up all the values.
If the result is larger than 0, there is at least 1 pixel inside the color range.
Here is an example with image displayed for clarity:
import cv2
import numpy as np
# load image
img = cv2.imread("img.jpg")
# Convert to HSV
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
# define range of wanted color in HSV
# you should get these values from the dict => here I use green
lower_val = np.array([37,42,0])
upper_val = np.array([84,255,255])
# Threshold the HSV image - any green color will show up as white
mask = cv2.inRange(hsv, lower_val, upper_val)
# if there are any white pixels on mask, sum will be > 0
hasGreen = np.sum(mask)
if hasGreen > 0:
print('Green detected!')
print(f'Amount: {hasGreen}')
# display images
cv2.imshow("Input", img)
cv2.imshow("Mask", mask)
cv2.waitKey(0)
cv2.destroyAllWindows()
RESULT:
Green detected!
Amount: 1190085
The example above will return positive when 1 pixel is in range. Depending on your input you may wish to use a threshold. The sum of pixel values can get very large (because each white pixel is 255), dividing by 255 yields the number of pixels in the color range, which may be easier to set a threshold:
# set the minimum number of pixel that must be in range
pixelThreshold = 1000
# sum all values of mask, if there are any white pixels on mask, sum will be > 0
hasGreen = np.sum(mask)
# divide by 255 to get the number of white pixels
greenPixels = hasGreen/255
# threshold value
if greenPixels > pixelThreshold:
print('Green detected!')
print(f'Number of pixels: {int(greenPixels)}')
Result:
Green detected!
Number of pixels: 4667
For ease of use you should put this in a function that you can use when looping over the color list:
def hasColor(hsv_img,lower_val,upper_val,pixelThreshold):
mask = cv2.inRange(hsv_img, lower_val, upper_val)
pixelsFound = np.sum(mask)/255
if pixelsFound > pixelThreshold:
return True
else:
return False

how to seprate black curve fromblack texts in opencv with python

import numpy as np
import cv2
im = cv2.imread("goldstandard.png")
nemo = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)
hsv_nemo = cv2.cvtColor(nemo, cv2.COLOR_RGB2HSV)
dictionaryHSV = {
"greenCombo": [[30, 126, 87], [70, 255, 250]],
'red': [[0, 92, 212], [10, 265, 255]],
'blue': [[110, 7, 214], [130, 255, 255]],
'black': [[0, 0, 0], [10, 10, 40]],
'another1': [[20, 245, 151], [40, 255, 231]],
'pink': [[140, 126, 215], [160, 146, 255]]
}
for r1, r2 in dictionaryHSV.values():
lower = np.array(r1)
upper = np.array(r2)
mask = cv2.inRange(hsv_nemo, lower, upper)
# cv2.imshow("masked",mask)
# cv2.waitKey(0)
nm = np.ones((nemo.shape[0], nemo.shape[1], nemo.shape[2]), dtype=np.uint8)
for i in range(nm.shape[0]):
for j in range(nm.shape[1]):
nm[i][j] = (255, 255, 255)
result = cv2.bitwise_and(nm, nm, mask=mask)
cv2.imshow("mappped", result)
cv2.waitKey(0)
i have curve plot images and i want to separate all curves based on color i am getting a problem when i come across black curve i get black curve along with black text in the plot i want to only get the curve not the text. I used color ranges in "H.S.V" color-space to recognize colors. Thanks in advance.
Extract region inside square.
Remove all non black pixels.
Find all contours.
Select a biggest contour - it will be your curve.

How to decode color mapping in matplotlib's Colormap?

I know how to map a number to a color from this post: Map values to colors in matplotlib
But I don't know how to decode the mapping to get my original color, assuming this is a one-to-one mapping, which it must be I figure.
I am encoding an image for visualization purposes, but I need to be able to decode it and read the original data values.
For reference, here are the Colormap docs: http://matplotlib.org/api/cm_api.html
Here's my try to the main answer below, which still isn't working right.
from PIL import Image
import numpy as np
import matplotlib
import matplotlib.cm as cm
values = [670, 894, 582, 103, 786, 348, 972, 718, 356, 692]
minima = 103
maxima = 972
norm = matplotlib.colors.Normalize(vmin=minima, vmax=maxima, clip=True)
mapper = cm.ScalarMappable(norm=norm, cmap=cm.gist_rainbow_r)
c = []
for i in range(10):
c.append(mapper.to_rgba(values[i], bytes=True))
print(c) # [(75, 255, 0, 255), (255, 77, 0, 255), (0, 255, 64, 255), (255, 0, 191, 255), (255, 250, 0, 255), (0, 72, 255, 255), (255, 0, 40, 255), (151, 255, 0, 255), (0, 83, 255, 255), (108, 255, 0, 255)]
def get_value_from_cm(color, cmap, colrange):
# color = matplotlib.colors.to_rgba(color)
r = np.linspace(colrange[0], colrange[1], 10) # there are 10 values
norm = matplotlib.colors.Normalize(colrange[0], colrange[1])
mapvals = cmap(norm(r))[:, :4] # there are 4 channels: r,g,b,a
distance = np.sum((mapvals - color) ** 2, axis=1)
return r[np.argmin(distance)]
decoded_colors = []
for i in range(10):
decoded_colors.append(get_value_from_cm(c[i], cm.gist_rainbow_r, colrange=[minima, maxima]))
print(decoded_colors) # [778.88888888888891, 778.88888888888891, 489.22222222222223, 103.0, 778.88888888888891, 392.66666666666669, 103.0, 778.88888888888891, 392.66666666666669, 778.88888888888891]
Inverting the colormapping is possible, if
(a) you know the data range it is mapping and
(b) if you know the colormap that has been used, and
(c) if the colormap is unambiguous.
The following function would return the value given a color, a colormap and the range over which the colormap has been used.
import numpy as np
import matplotlib.colors
import matplotlib.pyplot as plt
def get_value_from_cm(color, cmap, colrange=[0.,1.]):
color=matplotlib.colors.to_rgb(color)
r = np.linspace(colrange[0],colrange[1], 256)
norm = matplotlib.colors.Normalize(colrange[0],colrange[1])
mapvals = cmap(norm(r))[:,:3]
distance = np.sum((mapvals - color)**2, axis=1)
return r[np.argmin(distance)]
b = get_value_from_cm(plt.cm.coolwarm(0.5), plt.cm.coolwarm, [0.,1.])
c = get_value_from_cm(np.array([1,0,0]), plt.cm.coolwarm)
print b # 0.501960784314
print plt.cm.coolwarm(b)
# (0.86742763508627452, 0.86437659977254899, 0.86260246201960789, 1.0)
print plt.cm.coolwarm(0.5)
#(0.86742763508627452, 0.86437659977254899, 0.86260246201960789, 1.0)
Note that this method involves an error, so you only get the closest value from the colormap and not the value that has initially been used to create the color from the map.
In the updated code from the question, you have the color defined as integers between 0 and 255 for each channel. You therefore need to first map those to the range 0 to 1.
from PIL import Image
import numpy as np
import matplotlib
import matplotlib.cm as cm
values = [670, 894, 582, 103, 786, 348, 972, 718, 356, 692]
minima = 103
maxima = 972
norm = matplotlib.colors.Normalize(vmin=minima, vmax=maxima, clip=True)
mapper = cm.ScalarMappable(norm=norm, cmap=cm.gist_rainbow_r)
c = []
for i in range(10):
c.append(mapper.to_rgba(values[i], bytes=True))
print(c) # [(75, 255, 0, 255), (255, 77, 0, 255), (0, 255, 64, 255), (255, 0, 191, 255), (255, 250, 0, 255), (0, 72, 255, 255), (255, 0, 40, 255), (151, 255, 0, 255), (0, 83, 255, 255), (108, 255, 0, 255)]
def get_value_from_cm(color, cmap, colrange):
color = np.array(color)/255.
r = np.linspace(colrange[0], colrange[1], 256)
norm = matplotlib.colors.Normalize(colrange[0], colrange[1])
mapvals = cmap(norm(r))[:, :4] # there are 4 channels: r,g,b,a
distance = np.sum((mapvals - color) ** 2, axis=1)
return r[np.argmin(distance)]
decoded_colors = []
for i in range(10):
decoded_colors.append(get_value_from_cm(c[i], cm.gist_rainbow_r, colrange=[minima, maxima]))
print(decoded_colors)

Python Pil Change GreyScale Tif To RGB

I have a greyscale TIF File. I need to convert it to RGB/ read from it in a way I can work with it.
img = Image.open(GIF_FILENAME)
rgbimg = img.convert('RGB')
for i in range(5):
print rgbimg.getpixel((i, 0))
The convert.("RGB") will automatically make everything (255,255,255) even though the picture is a really dark mostly black picture.
If I just read the greyscale numbers I get numbers from around 1400 to 1900.
I need to also save a copy of the picture as a RGB Jpeg.
Picture in question: [Here]: http://imgur.com/kEwfFs3
How would I go along doing this?
what about:
img = Image.open(GIF_FILENAME)
rgbimg = Image.new("RGBA", img.size)
rgbimg.paste(img)
rgbimg.save('foo.jpg')
[EDIT]
created a test:
from PIL import Image
from collections import defaultdict
import pprint
img = Image.open("kEwfFs3.png")
rgbimg = Image.new("RGBA", img.size)
rgbimg.paste(img)
found_colors = defaultdict(int)
for x in range(0, rgbimg.size[0]):
for y in range(0, rgbimg.size[1]):
pix_val = rgbimg.getpixel((x, y))
found_colors[pix_val] += 1
pprint.pprint(dict(found_colors))
rgbimg.save('kEwfFs3.jpg')
And that outputs:
{(0, 0, 0, 255): 747802,
(1, 1, 1, 255): 397,
(2, 2, 2, 255): 299,
(3, 3, 3, 255): 255,
(4, 4, 4, 255): 221,
(5, 5, 5, 255): 200,
(6, 6, 6, 255): 187,
(7, 7, 7, 255): 138,
(8, 8, 8, 255): 160,
(9, 9, 9, 255): 152,
(10, 10, 10, 255): 122,
(11, 11, 11, 255): 116,
(12, 12, 12, 255): 144,
(13, 13, 13, 255): 117,
(14, 14, 14, 255): 117,
(15, 15, 15, 255): 102,
(16, 16, 16, 255): 119,
(17, 17, 17, 255): 299641,
(18, 18, 18, 255): 273,
(19, 19, 19, 255): 233,
.................... etc .......
.................... etc .......
(249, 249, 249, 255): 616,
(250, 250, 250, 255): 656,
(251, 251, 251, 255): 862,
(252, 252, 252, 255): 1109,
(253, 253, 253, 255): 1648,
(254, 254, 254, 255): 2964175}
Which is what you would expect.
Is your output different?
I ran into the same problem with an I;16 (16-bit grayscale) tiff, converted to RGB. Some digging into the manual reveals the problem has to do with the lut PIL is using to convert grayscale images to RGB. It is working in an 8-bit color space; that is it clips all values above 255. So a quick and simple solution is to manually convert to RGB using your own lut which scales the values within range using the point method like so:
path = 'path\to\image'
img = Image.open(path)
img.point(lambda p: p*0.0039063096, mode='RGB')
img = img.convert('RGB')
img.show() # check it out!
I determined the "lut" formula by just dividing 256 by the 16-bit equivalent, 65535.

Categories

Resources