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?

¿Fue útil?

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 porque print(array) sale decimal). Debería haber aplicado también pnmgamma para ahorrarme una confusión.

  • Porque Python es muy lento, numpy intenta ser furtivointeligente acerca de cómo aplica ciertas operaciones (ver radiodifusión). La primera regla general de la eficiencia con numpy es Deja que Numpy maneje la iteración para ti (o poner otro camino No escribas el tuyo for 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 a reshape.

  • El próximo gran numpy Endianness Mystery: ¿Por qué lo hace? newbyteorder() parece que devolver una matriz, cuando es documentado Para devolver un dtype. Esto es relevante si desea convertir a nativo endian con dst.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 ...

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top