python - recorrer - manipulacion de imagenes con numpy
¿Cómo convierto cualquier imagen en una imagen paletizada de 4 colores usando la Biblioteca de imágenes de Python? (4)
John, encontré ese primer enlace también, pero no me ayudó directamente con el problema. Sin embargo, me hizo mirar más profundamente en la cuantización.
Ayer se me ocurrió esto antes de acostarme:
import sys
import PIL
import Image
PALETTE = [
0, 0, 0, # black, 00
0, 255, 0, # green, 01
255, 0, 0, # red, 10
255, 255, 0, # yellow, 11
] + [0, ] * 252 * 3
# a palette image to use for quant
pimage = Image.new("P", (1, 1), 0)
pimage.putpalette(PALETTE)
# open the source image
image = Image.open(sys.argv[1])
image = image.convert("RGB")
# quantize it using our palette image
imagep = image.quantize(palette=pimage)
# save
imagep.save(''/tmp/cga.png'')
TZ.TZIOY, tu solución parece funcionar según los mismos principios. Felicitaciones, debería haber dejado de trabajar en eso y esperé tu respuesta. El mío es un poco más simple, aunque definitivamente no más lógico que el tuyo. PIL es engorroso de usar. El tuyo explica qué está sucediendo para hacerlo.
Tengo un dispositivo que admite gráficos de 4 colores (muy parecido a CGA en los viejos tiempos).
Quería usar PIL para leer la imagen y convertirla usando mi paleta de 4 colores (de rojo, verde, amarillo, negro), pero no puedo entender si es siquiera posible. Encontré algunas publicaciones de listas de correo que parecen sugerir que otras personas han intentado hacerlo y han fallado.
¡Un simple ejemplo de pitón sería muy apreciado!
Puntos de bonificación si agrega algo que luego convierte la imagen en una cadena de bytes donde cada byte representa 4 píxeles de datos (con cada dos bits representando un color de 0 a 3)
Primero: su paleta de cuatro colores (negro, verde, rojo, amarillo) no tiene componente azul. Por lo tanto, debe aceptar que su imagen de salida apenas se aproximará a la imagen de entrada, a menos que no haya un componente azul para comenzar.
Prueba este código:
import Image
def estimate_color(c, bit, c_error):
c_new= c - c_error
if c_new > 127:
c_bit= bit
c_error= 255 - c_new
else:
c_bit= 0
c_error= -c_new
return c_bit, c_error
def image2cga(im):
"Produce a sequence of CGA pixels from image im"
im_width= im.size[0]
for index, (r, g, b) in enumerate(im.getdata()):
if index % im_width == 0: # start of a line
r_error= g_error= 0
r_bit, r_error= estimate_color(r, 1, r_error)
g_bit, g_error= estimate_color(g, 2, g_error)
yield r_bit|g_bit
def cvt2cga(imgfn):
"Convert an RGB image to (K, R, G, Y) CGA image"
inp_im= Image.open(imgfn) # assume it''s RGB
out_im= Image.new("P", inp_im.size, None)
out_im.putpalette( (
0, 0, 0,
255, 0, 0,
0, 255, 0,
255, 255, 0,
) )
out_im.putdata(list(image2cga(inp_im)))
return out_im
if __name__ == "__main__":
import sys, os
for imgfn in sys.argv[1:]:
im= cvt2cga(imgfn)
dirname, filename= os.path.split(imgfn)
name, ext= os.path.splitext(filename)
newpathname= os.path.join(dirname, "cga-%s.png" % name)
im.save(newpathname)
Esto crea una imagen de paleta PNG con solo las primeras cuatro entradas de paleta configuradas a sus colores. Esta imagen de muestra:
http://tzotzioy.googlepages.com/new_pic_baby2.jpg
se convierte
http://tzotzioy.googlepages.com/cga-new_pic_baby2.png
Es trivial tomar la salida de image2cga
(produce una secuencia de 0-3 valores) y empacar cada cuatro valores en un byte.
Si necesita ayuda sobre lo que hace el código, pregunte y lo explicaré.
EDIT1: No reinventar la rueda
Por supuesto, resulta que estaba demasiado entusiasta y, como Thomas descubrió, el método Image.quantize puede tomar una imagen de paleta como argumento y hacer la cuantificación con mejores resultados que mi método ad-hoc anterior:
def cga_quantize(image):
pal_image= Image.new("P", (1,1))
pal_image.putpalette( (0,0,0, 0,255,0, 255,0,0, 255,255,0) + (0,0,0)*252)
return image.convert("RGB").quantize(palette=pal_image)
EDIT1, cont: empaqueta los píxeles en bytes
Para "valor agregado", aquí sigue el código para producir la cadena empaquetada (4 píxeles por byte):
import itertools as it
# setup: create a map with tuples [(0,0,0,0)‥(3,3,3,3)] as keys
# and values [chr(0)‥chr(255)], because PIL does not yet support
# 4 colour palette images
TUPLE2CHAR= {}
# Assume (b7, b6) are pixel0, (b5, b4) are pixel1…
# Call it "big endian"
KEY_BUILDER= [
(0, 64, 128, 192), # pixel0 value used as index
(0, 16, 32, 48), # pixel1
(0, 4, 8, 12), # pixel2
(0, 1, 2, 3), # pixel3
]
# For "little endian", uncomment the following line
## KEY_BUILDER.reverse()
# python2.6 has itertools.product, but for compatibility purposes
# let''s do it verbosely:
for ix0, px0 in enumerate(KEY_BUILDER[0]):
for ix1, px1 in enumerate(KEY_BUILDER[1]):
for ix2, px2 in enumerate(KEY_BUILDER[2]):
for ix3, px3 in enumerate(KEY_BUILDER[3]):
TUPLE2CHAR[ix0,ix1,ix2,ix3]= chr(px0+px1+px2+px3)
# Another helper function, copied almost verbatim from itertools docs
def grouper(n, iterable, padvalue=None):
"grouper(3, ''abcdefg'', ''x'') --> (''a'',''b'',''c''), (''d'',''e'',''f''), (''g'',''x'',''x'')"
return it.izip(*[it.chain(iterable, it.repeat(padvalue, n-1))]*n)
# now the functions
def seq2str(seq):
"""Takes a sequence of [0..3] values and packs them into bytes
using two bits per value"""
return ''''.join(
TUPLE2CHAR[four_pixel]
for four_pixel in grouper(4, seq, 0))
# and the image related function
# Note that the following function is correct,
# but is not useful for Windows 16 colour bitmaps,
# which start at the *bottom* row…
def image2str(img):
return seq2str(img.getdata())
Estás tratando de hacer la cuantificación de la imagen. Aquí hay algunos consejos para ese tipo de cosas:
https://web.archive.org/web/20080825200550/http://nadiana.com/pil-tips-converting-png-gif
import sys
import PIL
from PIL import Image
def quantizetopalette(silf, palette, dither=False):
"""Convert an RGB or L mode image to use a given P image''s palette."""
silf.load()
# use palette from reference image
palette.load()
if palette.mode != "P":
raise ValueError("bad mode for palette image")
if silf.mode != "RGB" and silf.mode != "L":
raise ValueError(
"only RGB or L mode images can be quantized to a palette"
)
im = silf.im.convert("P", 1 if dither else 0, palette.im)
# the 0 above means turn OFF dithering
return silf._makeself(im)
if __name__ == "__main__":
import sys, os
for imgfn in sys.argv[1:]:
palettedata = [ 0, 0, 0, 0, 255, 0, 255, 0, 0, 255, 255, 0,]
palimage = Image.new(''P'', (16, 16))
palimage.putpalette(palettedata + [0, ] * 252 * 3)
oldimage = Image.open(sys.argv[1])
newimage = quantizetopalette(oldimage, palimage, dither=False)
dirname, filename= os.path.split(imgfn)
name, ext= os.path.splitext(filename)
newpathname= os.path.join(dirname, "cga-%s.png" % name)
newimage.save(newpathname)
Para aquellos que no querían Dithering para obtener colores sólidos. Modded: Convierta la imagen a una paleta específica usando PIL sin difuminado con las dos soluciones en este hilo. Aunque este hilo es antiguo, algunos de nosotros queremos esa información. Kudios