2010-11-24 35 views
3

Comment puis-je écrire un filtre pour la bibliothèque d'imagerie python pour le format pgm plain ascii (P2). Le problème ici est que le filtre PIL de base suppose un nombre constant d'octets par pixel.Comment écrire un filtre d'image PIL pour le format pgm ordinaire?

Mon but est d'ouvrir feep.pgm avec Image.open(). Voir http://netpbm.sourceforge.net/doc/pgm.html ou ci-dessous.

Une autre solution est que je trouve un autre format de gris en ASCII bien documenté qui est supporté par PIL et tous les principaux programmes graphiques. Aucune suggestion?

feep.pgm:

P2 
# feep.pgm 
24 7 
15 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 
0 3 3 3 3 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 15 15 15 0 
0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 15 0 
0 3 3 3 0 0 0 7 7 7 0 0 0 11 11 11 0 0 0 15 15 15 15 0 
0 3 0 0 0 0 0 7 0 0 0 0 0 11 0 0 0 0 0 15 0 0 0 0 
0 3 0 0 0 0 0 7 7 7 7 0 0 11 11 11 11 0 0 15 0 0 0 0 
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 

modifier: Merci pour la réponse, cela fonctionne ... mais je besoin d'une solution qui utilise Image.open(). La plupart des programmes python utilisent PIL pour manipuler les graphiques (google: python image open). Ainsi, je dois pouvoir enregistrer un filtre à PIL. Ensuite, je peux utiliser n'importe quel logiciel qui utilise PIL. Je pense maintenant principalement scipy, pylab, etc. programmes dépendants.

éditer Ok, je pense que je l'ai maintenant. Ci-dessous l'emballage pgm2pil.py:

import Image 
import numpy 

def pgm2pil(filename): 

    try: 
     inFile = open(filename) 

     header = None 
     size = None 
     maxGray = None 
     data = [] 

     for line in inFile: 
      stripped = line.strip() 

      if stripped[0] == '#': 
       continue 
      elif header == None: 
       if stripped != 'P2': return None 
       header = stripped 
      elif size == None: 
       size = map(int, stripped.split()) 
      elif maxGray == None: 
       maxGray = int(stripped) 
      else: 
       for item in stripped.split(): 
        data.append(int(item.strip())) 

     data = numpy.reshape(data, (size[1],size[0]))/float(maxGray)*255 
     return numpy.flipud(data) 

    except: 
     pass 

    return None 

def imageOpenWrapper(fname): 
    pgm = pgm2pil(fname) 
    if pgm is not None: 
     return Image.fromarray(pgm) 
    return origImageOpen(fname) 

origImageOpen = Image.open 
Image.open = imageOpenWrapper 

Il y a une légère amélioration à la réponse de Misha. Image.open doit être sauvegardé afin d'éviter les boucles sans fin. Si retourne pgm2pil appels wrapper Aucun pgm2pil qui retourne Aucun qui appelle pgm2pil ...

est inférieure à la fonction de test (feep_false.pgm est un pgm malformé, par exemple « P2 » -> « toto » et lena.pgm est juste la fichier image):

import pgm2pil 
import pylab 

try: 
    pylab.imread('feep_false.pgm') 
except IOError: 
    pass 
else: 
    raise ValueError("feep_false should fail") 

pylab.subplot(2,1,1) 
a = pylab.imread('feep.pgm') 
pylab.imshow(a) 

pylab.subplot(2,1,2) 
b = pylab.imread('lena.png') 
pylab.imshow(b) 

pylab.show() 
+0

Merci pour votre aide. Mais, pourquoi renvoyez-vous numpy.flipud (data)? Je dois retourner juste des données. – user1245262

+0

C'était il y a longtemps quand je l'ai fait ... La raison en est que les images ne sont pas dans le système de coordonnées cartésiennes (axe Y inversé). IMHO images devraient être dans le système cartésien sans le flip (ou avec flip, je ne me souviens pas de quel côté). À la fin, ce qui précède montre correctement l'image lors de l'utilisation d'imread et d'imshow. – Juha

+0

Ah ... c'est logique. J'aurais dû y penser. Merci encore. – user1245262

Répondre

5

La façon dont je traite actuellement avec c'est par numpy:

  1. Lire l'image dans un tableau 2D numpy. Vous n'avez pas besoin d'utiliser numpy, mais je l'ai trouvé plus facile à utiliser que les réseaux réguliers Python 2D
  2. Convertir le tableau de 2D en PIL.Image objet à l'aide PIL.Image.fromarray

Si vous insistez sur l'utilisation PIL.Image.open , vous pouvez écrire un wrapper qui tente de charger un fichier PGM en premier (en regardant l'en-tête). S'il s'agit d'une sortie PGM, chargez l'image en suivant les étapes ci-dessus, sinon, passez la responsabilité à PIL.Image.open.

Voici un code que j'utilise pour obtenir une image PBM dans un tableau numpy.

import re 
import numpy 

def pbm2numpy(filename): 
    """ 
    Read a PBM into a numpy array. Only supports ASCII PBM for now. 
    """ 
    fin = None 
    debug = True 

    try: 
     fin = open(filename, 'r') 

     while True: 
      header = fin.readline().strip() 
      if header.startswith('#'): 
       continue 
      elif header == 'P1': 
       break 
      elif header == 'P4': 
       assert False, 'Raw PBM reading not implemented yet' 
      else: 
       # 
       # Unexpected header. 
       # 
       if debug: 
        print 'Bad mode:', header 
       return None 

     rows, cols = 0, 0 
     while True: 
      header = fin.readline().strip() 
      if header.startswith('#'): 
       continue 

      match = re.match('^(\d+) (\d+)$', header) 
      if match == None: 
       if debug: 
        print 'Bad size:', repr(header) 
       return None 

      cols, rows = match.groups() 
      break 

     rows = int(rows) 
     cols = int(cols) 

     assert (rows, cols) != (0, 0) 

     if debug: 
      print 'Rows: %d, cols: %d' % (rows, cols) 

     # 
     # Initialise a 2D numpy array 
     # 
     result = numpy.zeros((rows, cols), numpy.int8) 

     pxs = [] 

     # 
     # Read to EOF. 
     # 
     while True: 
      line = fin.readline().strip() 
      if line == '': 
       break 

      for c in line: 
       if c == ' ': 
        continue 

       pxs.append(int(c)) 

     if len(pxs) != rows*cols: 
      if debug: 
       print 'Insufficient image data:', len(pxs) 
      return None 

     for r in range(rows): 
      for c in range(cols): 
       # 
       # Index into the numpy array and set the pixel value. 
       # 
       result[r, c] = pxs[r*cols + c] 

     return result 

    finally: 
     if fin != None: 
      fin.close() 
     fin = None 

    return None 

Vous devrez le modifier légèrement pour répondre à vos besoins, à savoir:

  • Traiter P2 (ASCII, échelle de gris) au lieu de P1 (ASCII, deux niveaux).
  • Utilisez un conteneur différent si vous n'utilisez pas numpy. Les tableaux 2D Python normaux fonctionneront très bien.

EDIT

Voici comment je gérer une enveloppe:

def pgm2pil(fname): 
    # 
    # This method returns a PIL.Image. Use pbm2numpy function above as a 
    # guide. If it can't load the image, it returns None. 
    # 
    pass 

def wrapper(fname): 
    pgm = pgm2pil(fname) 

    if pgm is not None: 
     return pgm 
    return PIL.Image.open(fname) 

# 
# This is the line that "adds" the wrapper 
# 
PIL.Image.open = wrapper 

Je n'ai pas écrit pgm2pil parce que ça va être très semblable à pgm2numpy. La seule différence sera qu'il stocke le résultat dans un PIL.Image par opposition à un tableau numpy. Je n'ai pas non plus testé le code de l'emballage (désolé, un peu à l'heure pour le moment) mais c'est une approche assez courante, donc je m'attends à ce que ça marche.

Maintenant, il semble que vous voulez autres applications qui utilisent PIL pour le chargement d'images pour être en mesure de gérer les sorties PGM. Il est possible d'utiliser l'approche ci-dessus, mais vous devez vous assurer que le code wrapper ci-dessus est ajouté avant le premier appel à PIL.Image.open. Vous pouvez vous assurer que cela se produit en ajoutant le code source de l'encapsuleur au code source PIL (si vous avez accès).

+0

La question est alors: comment écrire un wrapper? Et comment puis-je dire à Python que j'ai un wrapper pour PIL.image.open? – Juha

+0

@Juha voir ma réponse mise à jour. – misha