Using the Python Imaging Library, I can call
img.convert("P", palette=Image.ADAPTIVE)
or
img.convert("P", palette=Image.WEB)
but is there a way to convert to an arbitrary palette?
p = []
for i in range(0, 256):
p.append(i, 0, 0)
img.convert("P", palette=p)
where it'll map each pixel to the closest colour found in the image? Or is this supported for Image.WEB and nothing else?
While looking through the source code of convert() I saw that it references im.quantize.
quantize can take a palette argument. If you provide an Image that has a palette, this function will take that palette and apply it to the image.
Example:
src = Image.open("sourcefilewithpalette.bmp")
new = Image.open("unconvertednew24bit.bmp")
converted = new.quantize(palette=src)
converted.save("converted.bmp")
The other provided answer didn't work for me (it did some really bad double palette conversion or something,) but this solution did.
The ImagePalette module docs's first example shows how to attach a palette to an image, but that image must already be of mode "P" or "L". One can, however, adapt the example to convert a full RGB image to a palette of your choice:
from __future__ import division
import Image
palette = []
levels = 8
stepsize = 256 // levels
for i in range(256):
v = i // stepsize * stepsize
palette.extend((v, v, v))
assert len(palette) == 768
original_path = 'original.jpg'
original = Image.open(original_path)
converted = Image.new('P', original.size)
converted.putpalette(palette)
converted.paste(original, (0, 0))
converted.show()
Related
I am trying to take a set of 256x256px 8-bit grayscale .pngs (with transparency) and convert the grayscale .png to a color .png of the same size, still retaining transparency. The palette I want to use is Zissou1 from the R wesanderson package, which I have gotten into a Python dictionary where each key corresponds to a greyscale value and each value a HEX color.
import os
from PIL import Image, ImageColor
### dic = the dictionary containing the palette in format {grayscale pixel value: "HEX COLOR"},
### created earlier in the script
with Image.open("3.png") as im:
fin = Image.new("RGBA", im.size)
px = im.load()
px1 = fin.load()
for x in range(0,256):
for y in range(0,256):
px1.putpixel(x,y,ImageColor.getcolor(dic[px.getpixel(x,y)[1]],"RGBA"))
fin.show()
I am getting the error:
px1.putpixel(x,y,ImageColor.getcolor(dic[px.getpixel(x,y)[1]],"RGBA"))
AttributeError: 'PixelAccess' object has no attribute 'putpixel'
To extend on Jason's answer:
The lookup as given by PIL
With Image.point(lookup_table, mode = 'L') you can lookup and transpose the colors of your image.
lookup_table = ...
with Image.open("3.png") as orig:
image = orig.point(lookup_table, mode = 'L')
image.show()
To see an example for using the Image.point method with the lookup_table:
Using the Image.point() method in PIL to manipulate pixel data
Your own implementation (fixed with improved naming)
or implement the lookup against your_dic yourself:
your_dic = ...
with Image.open("3.png") as orig:
image = colored_from_map(orig, your_dic)
image.show()
with this alternative function (you almost did):
def colored_from_map(orig, map_to_color):
image_in = orig.load()
image = Image.new("RGBA", im.size)
image_out = image.load()
for x in range(0,256):
for y in range(0,256):
coords = (x,y)
greyscale = image_in.getpixel(x,y)[1]
color_name = map_to_color[greyscale]
image_out.putpixel(coords, ImageColor.getcolor(color_name,"RGBA"))
return image
Preserving the alpha-channel (transparency)
See the source-code of ImageColor.getColor()
at the begin and end of its method body:
color, alpha = getrgb(color), 255 # default to no-transparency
if len(color) == 4: # if your mapped color has 4th part as alpha-channel
color, alpha = color[0:3], color[3] # then use it
# omitted lines
else:
if mode[-1] == "A": # if the last char of `RGBA` is `A`
return color + (alpha,) # then return with added alpha-channel
return color
(comments mine)
So you could simply set the fourth element of the returned color-tuple to the previous value of the original gray-scale image:
greyscale = image_in.getpixel(x,y)[1] # containing original alpha-channel
color_name = map_to_color[greyscale] # name or hex
mapped_color = ImageColor.getcolor(color_name,"RGB") # removed the A
transposed_color = mapped_color[:2] + (greyscale[3],) # first 3 for RGB + original 4th for alpha-channel (transparency)
image_out.putpixel(coords, transposed_color)
Note: because the A(lpha-channel) is provided from original image, I removed the A from the getColor invocation's last argument. Technically, you can also remove the slicing from mapped_color[:2] to result in mapped_color + (greyscale[3],).
The first parameter to PIL's PixelAccess.putpixel method expects the pixel's coordinates to be passed as a (x,y) tuple:
px1.putpixel((x,y),ImageColor.getcolor(dic[px.getpixel(x,y)[1]],"RGBA"))
Alternatively, consider using the Image.point method which takes a look up table similar to the one you already created to map an image based on pixel values. See the answer at Using the Image.point() method in PIL to manipulate pixel data for more details
I get an image stored as an object from a camera that look like this (here reduced to make it understandable):
image = np.array([['#49312E', '#4A3327', '#493228', '#472F2A'],
['#452C29', '#49312E', '#4B3427', '#49312A'],
['#473026', '#472F2C', '#48302B', '#4C342B']])
is it possible to 'import' it as an 'image' in opencv?
I tried to look at the documentation of cv2.imdecode but could get it to work.
I could preprocess this array to get it to another format but I am not sure what could 'fit' to opencv.
Thank you for your help
This is a very succinct and pythonic (using NumPy) way to implement a conversion from your hexadecimal values matrix to an RGB matrix that could be read by OpenCV.
image = np.array([['#49312E', '#4A3327', '#493228', '#472F2A'],
['#452C29', '#49312E', '#4B3427', '#49312A'],
['#473026', '#472F2C', '#48302B', '#4C342B']])
def to_rgb(v):
return np.array([np.int(v[1:3],16), np.int(v[3:5],16) , np.int(v[5:7],16)])
image_cv = np.array([to_rgb(h) for h in image.flatten()]).reshape(3, 4, 3)
cv2.imwrite('result.png', image_cv)
OpenCV requires either a RGB or a BGR input, which is to say you need to give the values of Red Green Blue or Blue Green Red on a scale from 0-255 (8 bit). I have shared with you the code to convert your array to an image.
Initially, I count the number of rows to find the height in terms of pixels. Then I count the number of items in a row to find the width.
Then I create an empty array of the given dimensions using np.zeros.
I then go to each cell and convert the hex code to its RGB equivalent, using the following formula #RRGGBB, R = int(RR,16), G = int(GG, 16), B = int(BB, 16). This converts the hexadecimal string to int.
#!/usr/bin/env python3
import numpy as np
import re
import cv2
# Your image
image = np.array([['#49312E', '#4A3327', '#493228', '#472F2A'],
['#452C29', '#49312E', '#4B3427', '#49312A'],
['#473026', '#472F2C', '#48302B', '#4C342B']])
# Enter the image height and width
height = int(len(image[0]))
width = int(len(image[0][0]))
# Create numpy array of BGR triplets
im = np.zeros((height,width,3), dtype=np.uint8)
for row in range (height):
for col in range(width):
hex = image[row, col][1:]
R = int(hex[0:2],16)
G = int(hex[2:4],16)
B = int(hex[4:6],16)
im[row,col] = (B,G,R)
# Save to disk
cv2.imwrite('result.png', im)
I have this depth image:
that I load with PIL like:
depth_image = Image.open('stereo.png')
If I print the mode of the image it shows mode I, that is (32-bit signed integer pixels) according to the documentation.
This is correct since the image values range from 0 to 255. I'd like to colorize this depth image for better visualization so I tried to convert it to P mode with a palette like:
depth_image = depth_image.convert('P', palette=custom_palette)
depth_image.save("colorized.png")
But the result is a black and white image like this:
I'm sure the palette is ok, since there are 256 colors in int format all in a single array.
I've tried to convert it to RGB before saving like:
depth_image = depth_image.convert('RGB')
Also I tried adding the palette afterwards like:
depth_image = depth_image.putpalette(custom_palette)
And if I try to save it without converting it to RGB I get a:
depth_image.save("here.png")
AttributeError: 'NoneType' object has no attribute 'save'
So far I'll try converting the image to a numpy array and then map the colors from there, but I was wondering what was I missing out regarding PIL. I was looking around the documentation but didn't find much regarding I to P conversion.
I think the issue is that your values are scaled to the range 0..65535 rather than 0..255.
If you do this, you will see the values are larger than you expected:
i = Image.open('depth.png')
n = np.array(i)
print(n.max(),n.mean())
# prints 32257, 6437.173
So, I quickly tried:
n = (n/256).astype(np.uint8)
r = Image.fromarray(n)
r=r.convert('P')
r.putpalette(custom_palette) # I grabbed this from your pastebin
Is it possible to get the RGB color of a pixel using PIL?
I'm using this code:
im = Image.open("image.gif")
pix = im.load()
print(pix[1,1])
However, it only outputs a number (e.g. 0 or 1) and not three numbers (e.g. 60,60,60 for R,G,B). I guess I'm not understanding something about the function. I'd love some explanation.
Thanks a lot.
Yes, this way:
im = Image.open('image.gif')
rgb_im = im.convert('RGB')
r, g, b = rgb_im.getpixel((1, 1))
print(r, g, b)
(65, 100, 137)
The reason you were getting a single value before with pix[1, 1] is because GIF pixels refer to one of the 256 values in the GIF color palette.
See also this SO post: Python and PIL pixel values different for GIF and JPEG and this PIL Reference page contains more information on the convert() function.
By the way, your code would work just fine for .jpg images.
With numpy :
im = Image.open('image.gif')
im_matrix = np.array(im)
print(im_matrix[0][0])
Give RGB vector of the pixel in position (0,0)
GIFs store colors as one of x number of possible colors in a palette. Read about the gif limited color palette. So PIL is giving you the palette index, rather than the color information of that palette color.
Edit: Removed link to a blog post solution that had a typo. Other answers do the same thing without the typo.
An alternative to converting the image is to create an RGB index from the palette.
from PIL import Image
def chunk(seq, size, groupByList=True):
"""Returns list of lists/tuples broken up by size input"""
func = tuple
if groupByList:
func = list
return [func(seq[i:i + size]) for i in range(0, len(seq), size)]
def getPaletteInRgb(img):
"""
Returns list of RGB tuples found in the image palette
:type img: Image.Image
:rtype: list[tuple]
"""
assert img.mode == 'P', "image should be palette mode"
pal = img.getpalette()
colors = chunk(pal, 3, False)
return colors
# Usage
im = Image.open("image.gif")
pal = getPalletteInRgb(im)
Not PIL, but imageio.imread might still be interesting:
import imageio
im = scipy.misc.imread('um_000000.png', flatten=False, mode='RGB')
im = imageio.imread('Figure_1.png', pilmode='RGB')
print(im.shape)
gives
(480, 640, 3)
so it is (height, width, channels). So the pixel at position (x, y) is
color = tuple(im[y][x])
r, g, b = color
Outdated
scipy.misc.imread is deprecated in SciPy 1.0.0 (thanks for the reminder, fbahr!)
Is it possible to get the RGB color of a pixel using PIL?
I'm using this code:
im = Image.open("image.gif")
pix = im.load()
print(pix[1,1])
However, it only outputs a number (e.g. 0 or 1) and not three numbers (e.g. 60,60,60 for R,G,B). I guess I'm not understanding something about the function. I'd love some explanation.
Thanks a lot.
Yes, this way:
im = Image.open('image.gif')
rgb_im = im.convert('RGB')
r, g, b = rgb_im.getpixel((1, 1))
print(r, g, b)
(65, 100, 137)
The reason you were getting a single value before with pix[1, 1] is because GIF pixels refer to one of the 256 values in the GIF color palette.
See also this SO post: Python and PIL pixel values different for GIF and JPEG and this PIL Reference page contains more information on the convert() function.
By the way, your code would work just fine for .jpg images.
With numpy :
im = Image.open('image.gif')
im_matrix = np.array(im)
print(im_matrix[0][0])
Give RGB vector of the pixel in position (0,0)
GIFs store colors as one of x number of possible colors in a palette. Read about the gif limited color palette. So PIL is giving you the palette index, rather than the color information of that palette color.
Edit: Removed link to a blog post solution that had a typo. Other answers do the same thing without the typo.
An alternative to converting the image is to create an RGB index from the palette.
from PIL import Image
def chunk(seq, size, groupByList=True):
"""Returns list of lists/tuples broken up by size input"""
func = tuple
if groupByList:
func = list
return [func(seq[i:i + size]) for i in range(0, len(seq), size)]
def getPaletteInRgb(img):
"""
Returns list of RGB tuples found in the image palette
:type img: Image.Image
:rtype: list[tuple]
"""
assert img.mode == 'P', "image should be palette mode"
pal = img.getpalette()
colors = chunk(pal, 3, False)
return colors
# Usage
im = Image.open("image.gif")
pal = getPalletteInRgb(im)
Not PIL, but imageio.imread might still be interesting:
import imageio
im = scipy.misc.imread('um_000000.png', flatten=False, mode='RGB')
im = imageio.imread('Figure_1.png', pilmode='RGB')
print(im.shape)
gives
(480, 640, 3)
so it is (height, width, channels). So the pixel at position (x, y) is
color = tuple(im[y][x])
r, g, b = color
Outdated
scipy.misc.imread is deprecated in SciPy 1.0.0 (thanks for the reminder, fbahr!)