Python e PGM a 16 bit
-
28-10-2019 - |
Domanda
Ho immagini PGM a 16 bit che sto cercando di leggere in Python. Sembra (?) Come PIL non supporta questo formato?
import Image
im = Image.open('test.pgm')
im.show()
Mostra all'incirca l'immagine, ma non è giusto. Ci sono bande scure in tutto e si dice che IMG abbia mode=L
. Penso che questo sia legato a una domanda iniziale di cui ho avuto File TIFF a 16 bit. 16 bit è così raro che PIL non lo supporta? Qualche consiglio su come posso leggere file PGM a 16 bit in Python, usando PIL o un'altra libreria standard o codice coltivato in casa?
Soluzione
Quanto segue dipende solo da numpy Per caricare l'immagine, che può essere PGM RAW/PPM RAW a 8 bit. Mostro anche un paio di modi diversi per visualizzare l'immagine. Quello che usa PIL (import Image
) richiede che i dati vengano prima convertiti in 8 bit.
#!/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()
Note di utilizzo
Alla fine ho capito "come decide di endianness"-sta effettivamente immagazzinando l'immagine in memoria come Big-Endian (piuttosto che nativo). Questo schema potrebbe rallentare qualsiasi elaborazione delle immagini non banali, sebbene altri problemi di prestazioni con Python possano relegare questa preoccupazione a una banalità (vedi sotto).
Ho fatto una domanda relativa alla preoccupazione di Endianness qui. Mi sono anche imbattuto in una confusione interessante legata all'endianness con questo perché stavo testando preelaborando l'immagine con
pnmdepth 65535
Il che non è buono (di per sé) per testare l'endianness poiché i byte bassi e alti potrebbero finire per essere lo stesso (non me ne sono accorto subito perchéprint(array)
output decimale). Avrei dovuto anche applicarepnmgamma
per salvarmi un po 'di confusione.Perché Python è così lento,
numpy
cerca di esseresubdolointelligente su come applica determinate operazioni (vedi trasmissione). La prima regola empirica per l'efficienza connumpy
è Lascia che Numpy gestisca l'iterazione per te (o metti in altro modo Non scrivere il tuofor
Loop). La cosa divertente nel codice sopra è che segue solo parzialmente questa regola quando si fa "Esempio di elaborazione delle immagini", e quindi le prestazioni di quella linea hanno una dipendenza estrema dai parametri a cui sono stati datireshape
.Il prossimo grande
numpy
Mistero di endianness: perché lo fanewbyteorder()
sembra restituire un array, quando è documentato per restituire adtype
. Questo è rilevante se si desidera convertire in endian nativodst.pixels=dst.pixels.byteswap(True).newbyteorder()
.Suggerimenti sul porting a Python 3: Input binaria con un'intestazione di testo ASCII, letta da stdin
Altri suggerimenti
Hai bisogno di una modalità di "L;16"
; Tuttavia sembra che PIL abbia una modalità di "L"
hardcoded in file.c Quando si carica un PGM. Dovresti Scrivi il tuo decodificatore Se vuoi essere in grado di leggere un PGM a 16 bit.
Tuttavia, il supporto delle immagini a 16 bit sembra ancora traballante:
>>> 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
Penso che Pil sia capace lettura Immagini con 16 bit, ma in realtà memorizzare e manipolarle sono ancora sperimentali.
>>> 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)]
Vedi, ha appena interpretato il 0xCAFE
valore come 0xFE
, che non è esattamente corretto.
Ecco un generico Pnm/Pam lettore basato su Numpy e una funzione non documentata in 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)
Certo scrivere Questo formato di immagine generalmente non richiede l'assistenza di una biblioteca ...