2011-09-09 4 views
5

У меня есть 16-битные изображения PGM, которые я пытаюсь прочитать на Python. Кажется (?), Как PIL не поддерживает этот формат?Python и 16-бит PGM

import Image 
im = Image.open('test.pgm') 
im.show() 

Показывает примерно изображение, но это неправильно. Есть темные полосы повсюду, и сообщается, что img имеет mode=L. Я думаю, что это связано с ранним вопросом, который у меня был о 16-bit TIFF files. 16-бит, что редко, что PIL просто не поддерживает его? Любой совет, как я могу читать 16-битные файлы PGM в Python, используя PIL или другую стандартную библиотеку, или домашний код?

ответ

1

Следующая зависит только от numpy, чтобы загрузить изображение, которое может быть 8-бит или 16-битный необработанный PGM/PPM. Я также показываю пару различных способов просмотра изображения. Тот, который использует PIL (import Image), требует, чтобы данные сначала были преобразованы в 8-битные.

#!/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() 

Замечания по использованию

  • В конце концов я понял, «как он решает вопрос о байтов» - это на самом деле сохранения изображения в памяти в виде обратным порядком байтов (а не родной). Эта схема может замедлить любую нетривиальную обработку изображений, хотя другие проблемы с производительностью с Python могут отнести эту проблему к тривиальности (см. Ниже).

  • Я задал вопрос, связанный с контентом here. Я также столкнулся с какой-то интересной путаницей, связанной с контентом, потому что я тестировал путем предварительной обработки изображения с pnmdepth 65535, что не очень хорошо (само по себе) для тестирования endianness, так как низкие и высокие байты могут оказаться одинаковыми (я не сделал уведомление сразу же, потому что print(array) выводит десятичное число). Я должен был также применить pnmgamma, чтобы сэкономить некоторое замешательство.

  • Поскольку Python настолько медленно, numpy пытается быть подлого умными о том, как он применяет определенные операции (см broadcasting). Первое эмпирическое правило для эффективности с numpy - . Пусть вам понадобится нулевая ручка для итерации (или по другому пути don't write your own for loops). Самое смешное в приведенном выше коде состоит в том, что он лишь частично следует этому правилу при выполнении «примерной обработки изображений», и поэтому производительность этой линии крайне зависит от параметров, которые были присвоены reshape.

  • Следующая большая numpy тайна порядок байт: Почему newbyteorder(), кажется return an array, когда это documented вернуть dtype. Это актуально, если вы хотите преобразовать в native endian с dst.pixels=dst.pixels.byteswap(True).newbyteorder().

  • Советы по переносу на Python 3: binary input with an ASCII text header, read from stdin

+0

Почему попытка написать кажущиеся тривиальными программы Python, похоже, всегда приводит к одиссею через Stack Overflow? – nobar

+0

Одна из вещей, которая сводит меня с ума от Python, - это мелкие копии, такие как 'dst = src' выше. Иногда я думаю, что Python просто слишком сложно понять программисту на C++. – nobar

+0

... Я нашел некоторые из самых низких голосовых ответов [здесь] (http://stackoverflow.com/questions/9541025/how-to-copy-a-python-class), чтобы быть наиболее полезным. В частности, похоже, что я могу решить свою проблему выше, выполнив 'dst = src()'. – nobar

4

Вам нужен режим "L;16"; однако, похоже, что PIL имеет режим "L", закодированный в файл File.c при загрузке PGM. Вам нужно будет write your own decoder, если вы хотите читать 16-битную PGM.

Однако, 16-битовая поддержка изображения по-прежнему кажется, слоеное:

>>> 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 

Я думаю, PIL способен чтение изображений с 16 битами, но на самом деле хранения и манипулирования им все еще является экспериментальной.

>>> 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)] 

Престол, он просто интерпретировать значение 0xCAFE в 0xFE, что не совсем правильно.

+0

Я счастлив просто читать их. Если мне нужно писать, я буду использовать PNG. Я тоже в порядке, манипулируя ими как данные в numpy, а не как изображение в PIL. Ваша почта была полезной, но можете ли вы рассказать о том, как я могу правильно читать данные? – mankoff

+0

Вы имеете в виду написать декодер для PIL или как интерпретировать PGM? –

+0

Ваше выделенное курсивом '' 'чтение' '' заставило меня думать, что есть какой-то трюк, чтобы заставить его работать как есть? Я пытаюсь адаптировать рабочую среду здесь (http://stackoverflow.com/questions/7247371/python-and-16-bit-tiff), но без потери бит. Если требуется специальный декодер, я напишу его на основе учебника PIL. Формат PGM кажется довольно простым, поэтому, возможно, я должен просто прочитать его непосредственно в numpy ... – mankoff

1

Вот общий PNM/PAM читатель на основе NumPy и недокументированная функция в 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) 

Конечно письма этот формат изображения, как правило, не требует помощи библиотеки ...

+0

Я заимствовал некоторые идеи из [этого связанного вопроса] (http://stackoverflow.com/questions/7368739/numpy-and-16-bit-pgm). – nobar

+0

Что касается поддержки PAM, используемая здесь функция 'read_pnm_header()' не возвращает 'TUPLTYPE', но возвращает правильное значение для' DEPTH' (которое я называл 'samples'). – nobar

+0

См. [Этот вопрос] (http://stackoverflow.com/questions/2850893/reading-binary-data-from-stdin) для важных заметок об использовании stdio вместо файла. – nobar