Python y PGM de 16 bits
-
28-10-2019 - |
Pregunta
Tengo imágenes PGM de 16 bits que estoy tratando de leer en Python. ¿Parece (?) Como PIL no apoya este formato?
import Image
im = Image.open('test.pgm')
im.show()
Muestra aproximadamente la imagen, pero no está bien. Hay bandas oscuras en todo momento y se informa que IMG tiene mode=L
. Creo que esto está relacionado con una pregunta temprana que tenía sobre Archivos TIFF de 16 bits. ¿Es de 16 bits tan raro que PIL simplemente no lo apoye? ¿Algún consejo de cómo puedo leer archivos PGM de 16 bits en Python, usando PIL u otra biblioteca estándar, o código local?
Solución
Lo siguiente depende solo de numpy Para cargar la imagen, que puede ser PGM/PPM de 8 bits o 16 bits. También muestro un par de formas diferentes de ver la imagen. El que usa pil (import Image
) requiere que los datos se conviertan primero en 8 bits.
#!/usr/bin/python2 -u
from __future__ import print_function
import sys, numpy
def read_pnm_from_stream( fd ):
pnm = type('pnm',(object,),{}) ## create an empty container
pnm.header = fd.readline()
pnm.magic = pnm.header.split()[0]
pnm.maxsample = 1 if ( pnm.magic == 'P4' ) else 0
while ( len(pnm.header.split()) < 3+(1,0)[pnm.maxsample] ): s = fd.readline() ; pnm.header += s if ( len(s) and s[0] != '#' ) else ''
pnm.width, pnm.height = [int(item) for item in pnm.header.split()[1:3]]
pnm.samples = 3 if ( pnm.magic == 'P6' ) else 1
if ( pnm.maxsample == 0 ): pnm.maxsample = int(pnm.header.split()[3])
pnm.pixels = numpy.fromfile( fd, count=pnm.width*pnm.height*pnm.samples, dtype='u1' if pnm.maxsample < 256 else '>u2' )
pnm.pixels = pnm.pixels.reshape(pnm.height,pnm.width) if pnm.samples==1 else pnm.pixels.reshape(pnm.height,pnm.width,pnm.samples)
return pnm
if __name__ == '__main__':
## read image
# src = read_pnm_from_stream( open(filename) )
src = read_pnm_from_stream( sys.stdin )
# print("src.header="+src.header.strip(), file=sys.stderr )
# print("src.pixels="+repr(src.pixels), file=sys.stderr )
## write image
dst=src
dst.pixels = numpy.array([ dst.maxsample-i for i in src.pixels ],dtype=dst.pixels.dtype) ## example image processing
# print("dst shape: "+str(dst.pixels.shape), file=sys.stderr )
sys.stdout.write(("P5" if dst.samples==1 else "P6")+"\n"+str(dst.width)+" "+str(dst.height)+"\n"+str(dst.maxsample)+"\n");
dst.pixels.tofile( sys.stdout ) ## seems to work, I'm not sure how it decides about endianness
## view using Image
import Image
viewable = dst.pixels if dst.pixels.dtype == numpy.dtype('u1') else numpy.array([ x>>8 for x in dst.pixels],dtype='u1')
Image.fromarray(viewable).show()
## view using scipy
import scipy.misc
scipy.misc.toimage(dst.pixels).show()
Notas de uso
Eventualmente descubrí "cómo decide sobre la endianness": en realidad está almacenando la imagen en la memoria como grande (en lugar de nativa). Este esquema podría ralentizar cualquier procesamiento de imágenes no trivial, aunque otros problemas de rendimiento con Python pueden relegar esta preocupación a una trivialidad (ver más abajo).
Hice una pregunta relacionada con la preocupación de endianness aquí. También me encontré con una confusión interesante relacionada con Endianness con esto porque estaba probando preprocesando la imagen con
pnmdepth 65535
lo cual no es bueno (por sí mismo) para probar la endianness, ya que los bytes bajos y altos podrían terminar siendo el mismo (no me di cuenta de inmediato porqueprint(array)
sale decimal). Debería haber aplicado tambiénpnmgamma
para ahorrarme una confusión.Porque Python es muy lento,
numpy
intenta serfurtivointeligente acerca de cómo aplica ciertas operaciones (ver radiodifusión). La primera regla general de la eficiencia connumpy
es Deja que Numpy maneje la iteración para ti (o poner otro camino No escribas el tuyofor
bucles). Lo curioso en el código anterior es que solo sigue parcialmente esta regla al hacer el "procesamiento de imágenes de ejemplo" y, por lo tanto, el rendimiento de esa línea tiene una dependencia extrema de los parámetros que se dieron areshape
.El próximo gran
numpy
Endianness Mystery: ¿Por qué lo hace?newbyteorder()
parece que devolver una matriz, cuando es documentado Para devolver undtype
. Esto es relevante si desea convertir a nativo endian condst.pixels=dst.pixels.byteswap(True).newbyteorder()
.Sugerencias sobre Porting to Python 3: Entrada binaria con un encabezado de texto ASCII, lea desde Stdin
Otros consejos
Necesitas un modo de "L;16"
; Sin embargo, parece que PIL tiene un modo de "L"
codificado en file.c al cargar un PGM. Tendrías que Escribe tu propio decodificador Si desea poder leer un PGM de 16 bits.
Sin embargo, el soporte de imagen de 16 bits todavía parece escamoso:
>>> im = Image.fromstring('I;16', (16, 16), '\xCA\xFE' * 256, 'raw', 'I;16')
>>> im.getcolors()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python2.6/dist-packages/PIL/Image.py", line 866, in getcolors
return self.im.getcolors(maxcolors)
ValueError: image has wrong mode
Creo que PIL es capaz de lectura Las imágenes con 16 bits, pero en realidad almacenarlas y manipularlas todavía son experimentales.
>>> im = Image.fromstring('L', (16, 16), '\xCA\xFE' * 256, 'raw', 'L;16')
>>> im
<Image.Image image mode=L size=16x16 at 0x27B4440>
>>> im.getcolors()
[(256, 254)]
Mira, solo interpretó el 0xCAFE
valor 0xFE
, que no es exactamente correcto.
Aquí hay un genérico PNM/Pam lector basado en Numpy y una función indocumentada en Pypng.
def read_pnm( filename, endian='>' ):
fd = open(filename,'rb')
format, width, height, samples, maxval = png.read_pnm_header( fd )
pixels = numpy.fromfile( fd, dtype='u1' if maxval < 256 else endian+'u2' )
return pixels.reshape(height,width,samples)
por supuesto escritura Este formato de imagen generalmente no requiere la ayuda de una biblioteca ...