Partage
  • Partager sur Facebook
  • Partager sur Twitter

Optimiser traitement d'images

Anonyme
    4 mars 2016 à 8:10:43

    Bonjour !

    Je souhaite faire du traitement d'images simple en Python, càd pouvoir ouvrir tout type d'image, lire les pixels et en enregistrer.

    J'ai trouvé la librairie imageio et j'ai fait une dizaine de fonctions de ce genre :

        @_handleExceptions()
        def _setPixel(self, x, y, color):
            if x < 0 or x >= self.width or y < 0 or y >= self.height:
                print("ERREUR - Le pixel de coordonnées ({}, {}) n'existe pas. [instance {}]".format(x, y, self.instance))
            else:
                self.datas[x, y] = color
            
        @_handleExceptions()
        def _getPixel(self, x, y, average=False):
            if x < 0 or x >= self.width or y < 0 or y >= self.height:
                print("ERREUR - Le pixel de coordonnées ({}, {}) n'existe pas. [instance {}]".format(x, y, self.instance))
            else:
                if average:
                    px = self.datas[x, y]
                    return (int(px[0]) + int(px[1]) + int(px[2])) / 3
                else:
                    return self.datas[x, y]
    
        @_handleExceptions()
        def thresholding(self):
            for i in range(self.width):
                for j in range(self.height):
                    if self._getPixel(i, j, average=True) > 127:
                        self._setPixel(i, j, (255, 255, 255))
                    else:
                        self._setPixel(i, j, (0, 0, 0))

    Le problème est que le temps d'exécution est relativement long.

    Par exemple la fonction tresholding sur une image de 300*300 px prend plus de 5 secondes et j'ai encore bien plus à faire. Pour en avoir déjà fait en C++, ç'aurait été instantané. Je sais que ça ne peut pas être aussi rapide qu'en C++, mais n'existe-t-il pas de méthodes pour accélérer ce traitement ? D'autres bibliothèques, ou peut-être ma méthode de procéder n-est-elle pas bonne ?

    Merci !

    • Partager sur Facebook
    • Partager sur Twitter
    Anonyme
      4 mars 2016 à 19:01:41

      Je t'avoue que je n'ai jamais fait de vrai traitement d'image, mais il y a deux bibliothèques que tu peux utiliser:

      • La première est la bibliothèque de gestion d'image de Python: Pillow.
      • La seconde est la bibliothèque scientifique de Python et fait partie de la Scipy Stack: Scipy (en association avec Numpy). J'ai trouvé une page qui a l'air intéressante, je la met ici et voici la documentation de la partie de Scipy qui s'occupe du traitement d'image ;)

      Je connais très mal Pillow, mais Numpy et Scipy font tourner du code C sous le capot, et le code C a été optimisé au possible donc tu auras du mal à faire mieux sans passer par du code C pur (où tu pourras gagner au mieux le temps d'interfaçage entre Python et le code C) :)

      Pour te donner une idée du code nécessaire pour réaliser ta fonction thresholding, voici ce que j'ai fait:

      import numpy as np
      # Pour utiliser les fonction ci-dessous tu as besoin d'avoir installé Pillow en plus de Scipy et Numpy
      from scipy.misc import imsave, imread
      
      # Charge l'image en mémoire, l'image est chargée en noir
      # et blanc grâce au paramètre flatten passé à True.
      im = imread("test.jpg", flatten=True)
      
      # On change les couleurs.
      im[im>127] = 255
      im[im<=127] = 0
      
      # Sauvegarde de l'image
      imsave('test2.jpg', im)
      

      J'ai prit une image random (1024x768) sur le net, et j'ai lancé sur mon ordinateur relativement vieux (2x2.4 GHz, Xubuntu 15.10). Le résultat a été obtenu en environ 0.5s:

      J'ai relancé le programme avec une image autrement plus grande (11500x11500) et j'ai obtenu un résultat en environ 10 secondes ;)

      Au niveau de l'installation des bibliothèque il te suffit de lancer une console et de taper:

      pip install numpy
      pip install pillow
      pip install scipy

      Tiens nous au courant! :D

      • Partager sur Facebook
      • Partager sur Twitter
      Anonyme
        4 mars 2016 à 23:08:27

        Bon, j'ai regardé rapidement et je pense que je vais tester les deux pour voir ce qui tourne le mieux !

        Ceci dit, ma fonction de seuillage n'était qu'un exemple : mon problème est que j'aurais besoin de manipuler les pixels par moi-même, je ne peux pas me contenter des fonctions implémentées par défaut dans ces bibliothèques, je souhaite faire de la reconnaissance de caractères. Je dois donc parcourir les listes et traiter un à un chaque triplet, et c'est ça qui prend en général du temps, par ex avec numpy, étant donné qu'il passe par du C, il me semble qu'il enregistre les triplets dans son propre format et effectuer à chaque fois la transition prend du temps.

        Merci en tout cas pour ta réponse, je regarderai ça demain !

        • Partager sur Facebook
        • Partager sur Twitter
        Anonyme
          5 mars 2016 à 0:29:36

          Donne nous ces exemples plus complexes alors ;) Je connais plutôt pas mal Numpy et je suis assez familier avec ses méthodes d'indexing et ses fonctions, je pourrais te dire assez vite si ce que tu cherches à faire est réalisable facilement grâce à Numpy, et surtout si tu vas y gagner en temps de calcul :)

          Numpy passe effectivement par du C, mais il n'enregistre les données qu'une seule fois dans son propre format. Une fois que le tableau a été initialisé, il ne bouge plus. Et l'avantage d'utiliser Scipy est justement que Scipy travaille nativement avec les formats utilisés par Numpy, donc pas besoin de transition entre les formats, ça reste du "format Numpy" tout le long.

          Après si vraiment tu veux de la performance tu peux toujours intégrer un code C (à confirmer pour un code C++) dans du code Python, et donc coder toi-même tes fonctions en C et les intégrer à tes scripts Python pour effectuer les calculs lourds :)

          • Partager sur Facebook
          • Partager sur Twitter
          Anonyme
            5 mars 2016 à 9:44:10

            Je n'ai pas encore vraiment d'exemples. Je commencerai je pense par faire des moyennes de lignes et de colonnes, ça je suppose que Numpy peut simplement le faire. Mais pour la suite, j'aimerai plutôt créer mes propres fonctions et je verrais au fur et à mesure comment le projet évolue, sachant qu'il est censé durer plus d'un an.

            Comme je le disais, ce dont j'ai donc principalement besoin, c'est de pouvoir accéder rapidement aux pixels de l'image, comme je le fais dans le code dans mon premier post, il ne s'agira pas de faire des manipulations globales sur l'image mais plutôt d'en extraire des données, bien que ce soit encore vague.

            A vrai dire, j'hésite encore à le faire en C++ ou en Python. J'ai fait un logiciel de traitement d'images en C++ l'année passée, c'était vraiment beaucoup plus rapide mais je pense que je vais plutôt cette fois opter pour la simplicité de Python avec lequel je suis bien plus à l'aise. :D

            • Partager sur Facebook
            • Partager sur Twitter
            Anonyme
              6 mars 2016 à 15:01:58

              Hello !

              J'ai au final essayé avec Numpy/spicy, et c'est effectivement bien plus rapide, c'est quasi instantané !

              J'essaye donc de me faire à l'esprit matrices, pourrais-tu me dire ce que tu penses par exemple de cette fonction qui me permet de supprimer les marges blanches autour de l'image ?

                  def cropWhite(self):
                      """Supprime la marge blanche autour de l'image
                      """
                      xBorderInf = 0
                      xBorderSup = self.width - 1
                      yBorderInf = 0
                      yBorderSup = self.height - 1
                      
                      whiteLineValue = 255 * self.width
                      whiteColValue = 255 * self.height
                      
                      while(np.sum(self.datas, axis=1)[xBorderInf] == whiteLineValue):
                          xBorderInf += 1
                      while(np.sum(self.datas, axis=1)[xBorderSup] == whiteLineValue):
                          xBorderSup -= 1
                      
                      while(np.sum(self.datas, axis=0)[yBorderInf] == whiteColValue):
                          yBorderInf += 1        
                      while(np.sum(self.datas, axis=0)[yBorderSup] == whiteColValue):
                          yBorderSup -= 1
                          
                      self.width = xBorderSup - xBorderInf + 1
                      self.height = yBorderSup - yBorderInf + 1
                      
                      newDatas = np.zeros_like(self.datas)
                      newDatas.resize(self.width, self.height)
              
                      for i in range(self.width):
                          for j in range(self.height):
                              newDatas[i, j] = self.datas[xBorderInf + i, yBorderInf + j]
                      
                      self.datas = newDatas

              Elle fonctionne très bien mais je voudrais juste savoir comment je pourrais faire mieux.

              Et j'en profite pour te demander, comment est-il possible de faire la moyenne d'une zone ? J'entends par zone par exemple un carré 5x5 contenu dans une matrice de taille 50x50.

              Merci encore ! :D

              -
              Edité par Anonyme 6 mars 2016 à 15:05:15

              • Partager sur Facebook
              • Partager sur Twitter
              Anonyme
                6 mars 2016 à 17:01:16

                L'esprit de Numpy est de tout faire en vectorisant (c.f. Wikipédia pour de plus amples informations) les opérations. En effet les boucles de Python sont (très) lentes et assez coûteuses, la philosophie de la Scipy stack est de remplacer les boucles par des opérations vectorisées. Pour te donner une idée du gain sur des opérations basiques j'ai lancé un petit script:

                from time import time
                import numpy as np
                
                N = [100, 500, 1000, 5000]
                
                for n in N:
                    A, B = np.ones((n, n)), np.ones((n, n))
                
                    t0 = time()
                    A *= 5
                    t1 = time()
                    for i in range(n):
                        for j in range(n):
                            B[i][j] *= 5
                    t2 = time()
                
                    print("n:", n, \
                          "\n\tA==B:", "True" if np.all(A==B) else "False",\
                          "\n\tAvec vectorisation:", t1-t0,\
                          "\n\tSans vectorisation:", t2-t1,\
                          "\n\tRatio:", (t2-t1)/(t1-t0))
                

                Et voici la sortie:

                n: 100 
                	A==B: True 
                	Avec vectorisation: 2.9325485229492188e-05 
                	Sans vectorisation: 0.01582050323486328 
                	Ratio: 539.479674796748
                n: 500 
                	A==B: True 
                	Avec vectorisation: 0.0009701251983642578 
                	Sans vectorisation: 0.407351016998291 
                	Ratio: 419.8953059719833
                n: 1000 
                	A==B: True 
                	Avec vectorisation: 0.004158496856689453 
                	Sans vectorisation: 1.6480450630187988 
                	Ratio: 396.30787753697973
                n: 5000 
                	A==B: True 
                	Avec vectorisation: 0.09733009338378906 
                	Sans vectorisation: 42.14940023422241 
                	Ratio: 433.05619843618337
                

                Un facteur 400 pour une simple multiplication. Le facteur baisse un peu (entre 50 et 60) quand on utilise le cosinus au lieu de la multiplication, mais ça reste quand même une grosse différence de timing :p

                Donc la première chose qui m'a sauté aux yeux quand j'ai vu ton code c'est la double boucle for imbriquée. A mon avis tu perds énormément de temps dessus, surtout si ton image est grande. En fait tu peux aller beaucoup plus vite, et en moins de lignes de code. Tu peux remplacer ça:

                newDatas = np.zeros_like(self.datas)
                newDatas.resize(self.width, self.height)
                 
                for i in range(self.width):
                    for j in range(self.height):
                        newDatas[i, j] = self.datas[xBorderInf + i, yBorderInf + j]
                     
                self.datas = newDatas

                Par cette ligne:

                self.datas = self.data[xBorderInf:xBorderSup+1][yBorderInf:yBorderSup+1]

                Tu utilises l'indexing Python repris par Numpy, et surtout tu n'utilises pas de boucle dans ton code Python (en interne à Numpy c'est fait avec des boucles, mais c'est optimisé :p).

                Ensuite il y a éventuellement la fonction somme. Tu as juste besoin de tester si tous les éléments sont égaux à 255. Peut-être que la fonction somme est effectivement la plus rapide (parce qu'elle ne fait qu'un seul parcours), mais elle fait plus que ce que tu veux. Enfin bon ça m'étonnerai que tu gagnes énormément sur ce point.

                Enfin la moyenne d'une zone... Tu peux utiliser la fonction mean et le slicing pour choisir la zone à "moyenner":

                def meanZone5_5(Tableau, i, j):
                    """Retourne la moyenne du carré 5x5 dont l'élément le plus en haut à gauche est situé à l'indice (i,j)"""
                    return np.mean(Tableau[i:i+5][j:j+5])

                (code non testé)

                Tu peux sans problème passer deux paramètres en plus dans la fonction et créer une fonction moyenne encore plus générique qui va calculer la moyenne sur n'importe quel rectangle dans le tableau.

                -
                Edité par Anonyme 6 mars 2016 à 17:02:41

                • Partager sur Facebook
                • Partager sur Twitter
                  6 mars 2016 à 21:02:22

                  Salut,

                  votre discussion m' a intéressé.

                  J'ai voulu traiter l' image en niveaux de gris plutôt qu'en noir-blanc.

                  En lisant le code de plus prêt je vois :

                  # On change les couleurs.
                  im[im>127] = 255
                  im[im<=127] = 0

                  Je ne suis pas habitué à ce type d'écriture, comment y intercaler des conditions ?

                  Question bonus, si j'ai envie de donner un aspect retro type EGA ou VGA, comment je selectionne les canaux R,G,B ?

                  je sais deja qu'il faut mettre flatten=False.

                  Enfin, comment écrire la transparence avec scipy ?

                  ps : pour ceux qui galèrent à installer scipy sur linux avec python3 :

                  sudo apt-get install libopenblas-base libatlas3-base
                  sudo apt-get install gcc gfortran python-dev libblas-dev liblapack-dev cython
                  sudo pip3 install scipy

                  (sinon passer par Anaconda https://www.continuum.io/downloads )

                  • Partager sur Facebook
                  • Partager sur Twitter
                    6 mars 2016 à 21:47:22

                    .
                    • Partager sur Facebook
                    • Partager sur Twitter
                    http://sinclair.recreatedzxspectrum.com/index.php
                    Anonyme
                      7 mars 2016 à 16:43:52

                      Coucou! :) Je n'ai pas très bien compris ta question, en fait tu te demandes comment marche la ligne

                      im[im>127] = 255

                      c'est bien ça?

                      Pour bien comprendre il faut que tu ailles jeter un œil sur les méthodes d'indexing de Numpy et plus particulièrement sur les tableaux de booléens. En fait la ligne de code est un peu condensée et tu peux voir le comportement de Numpy assez bien dans cet exemple:

                      In [1]: import numpy as np
                      
                      In [2]: A = np.arange(12).reshape((3,4))
                      
                      In [3]: A
                      Out[3]: 
                      array([[ 0,  1,  2,  3],
                             [ 4,  5,  6,  7],
                             [ 8,  9, 10, 11]])
                      
                      In [4]: A > 5
                      Out[4]: 
                      array([[False, False, False, False],
                             [False, False,  True,  True],
                             [ True,  True,  True,  True]], dtype=bool)
                      
                      In [5]: A[ A > 5 ]
                      Out[5]: array([ 6,  7,  8,  9, 10, 11])

                      Ligne par ligne:

                      • 1. Classique, j'importe Numpy
                      • 2. Je crée un tableau 2D avec des chiffres différents afin de bien voir comment ça marche.
                      • 3. J'affiche ce tableau pour être certain de bien le visualiser.
                      • 4. J'affiche le tableau de booléen qui va servir à l'indexing. Ici Numpy a appliqué la condition x>5 à tous les éléments du tableau A (voir vectorisation si tu ne comprends pas pourquoi) et à ressorti un tableau qui contient les résultats de ces opérations (donc ici des booléens).
                      • 5. J'utilise ce tableau de booléen pour afficher seulement certaines valeurs de A. Ici Numpy ne va garder que les valeurs de A "qui se trouvent sur la même case qu'un True". Tu peux remplacer cette ligne par le code (plus lent mais plus compréhensible car en pur Python)
                        np.array([A[i][j] for i in range(3) for j in range(4) if (A>5)[i][j]==True])

                      Tu as donc la réponse à ta question "comment enchaîner les conditions": il faut que tu comprennes bien les tableaux de booléens de Numpy, et que tu utilises les fonctions données par Numpy.

                      Voici un exemple qui va mettre ton image en 10 niveaux de gris:

                      import numpy as np
                      from scipy.misc import imsave, imread
                       
                      def niveauGris10(image):
                          im = imread(image, flatten=True)
                          seuils = [int(255*i/10) for i in range(1, 10)]
                          for i in range(len(seuils)-1):
                              im[ np.bitwise_and(im>seuils[i], im<seuils[i+1]) ] = (seuils[i]+seuils[i+1])//2
                          return im

                      Je n'ai pas testé la fonction, et les valeurs de niveau de gris sont un peu bizarres (pas de blanc pur, ni de noir pur, mais ça c'est à toi de fixer comment sont tes niveaux de gris).

                      La chose importante à voir ici est que tu n'utilise pas and ou &! En effet and ou & attendent des arguments qui peuvent directement être évalués à True ou à False et ne fonctionnent pas élément-par-élément avec Numpy. Voici le comportement de and, & et bitwise_and sur des tableaux de booléens:

                      In [10]: A
                      Out[10]: 
                      array([[ 0,  1,  2,  3],
                             [ 4,  5,  6,  7],
                             [ 8,  9, 10, 11]])
                      
                      In [11]: A > 3 and A < 7
                      # Coupé un peu le message d'erreur
                      ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
                      
                      In [12]: A > 3 & A < 7
                      # Coupé un peu le message d'erreur
                      ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()
                      
                      In [13]: np.bitwise_and(A > 3, A < 7)
                      Out[13]: 
                      array([[False, False, False, False],
                             [ True,  True,  True, False],
                             [False, False, False, False]], dtype=bool)
                      

                      Tu peux faire pareil avec or, et même avec xor! Voir la documentation de Numpy pour avoir toutes les informations.

                      Ensuite passons à la question bonus... J'ai ouvert une image random sur mon ordinateur et j'ai juste affiché le tableau que me renvoie imread:

                      In [14]: from scipy.misc import imread
                      
                      In [15]: im = imread("***") #flatten à False par défaut.
                      
                      In [16]: im
                      Out[16]: 
                      array([[[109,  71,  50],
                              [113,  75,  52],
                              [112,  74,  51],
                              ..., 
                              [205, 180, 149],
                              [207, 182, 152],
                              [206, 180, 153]],
                      
                             [[112,  74,  53],
                              [115,  77,  56],
                              [112,  74,  51],
                              ..., 
                              [203, 179, 145],
                              [205, 180, 150],
                              [204, 179, 149]],
                      
                             [[122,  81,  61],
                              [122,  81,  61],
                              [115,  77,  54],
                              ..., 
                              [204, 180, 146],
                              [205, 181, 147],
                              [206, 181, 150]],
                      
                             ..., 
                             [[136, 141, 147],
                              [137, 142, 146],
                              [133, 137, 140],
                              ..., 
                              [153, 156, 165],
                              [152, 154, 166],
                              [149, 151, 164]],
                      
                             [[138, 143, 149],
                              [139, 144, 148],
                              [134, 138, 141],
                              ..., 
                              [150, 152, 164],
                              [150, 152, 165],
                              [151, 153, 168]],
                      
                             [[133, 138, 144],
                              [138, 143, 147],
                              [137, 141, 144],
                              ..., 
                              [143, 145, 157],
                              [142, 144, 159],
                              [145, 146, 164]]], dtype=uint8)
                      

                      Ca te montre très bien la structure du tableau qui représente l'image:

                      • C'est un tableau à 3 dimensions.
                      • La troisième dimension stocke des nombres.
                      • La seconde dimension stocke des tableaux 1D de 3 nombres (ça ressemble à du RGB tient! :p). Elle fait office de "x" (ou de "y")
                      • La première dimension fait office de "y" (ou de "x").

                      Du coup tu peux facilement récupérer le canal rouge:

                      In [17]: R = im[:,:,0]
                      
                      In [18]: R
                      Out[18]: 
                      array([[109, 113, 112, ..., 205, 207, 206],
                             [112, 115, 112, ..., 203, 205, 204],
                             [122, 122, 115, ..., 204, 205, 206],
                             ..., 
                             [136, 137, 133, ..., 153, 152, 149],
                             [138, 139, 134, ..., 150, 150, 151],
                             [133, 138, 137, ..., 143, 142, 145]], dtype=uint8)
                      

                      Ici tu as juste une utilisation de slice et une syntaxe propre à l'indexing Numpy avec des virgules. Si les virgules te gêne(nt?) dis toi que

                      R = im[:][:][0]

                      fait la même chose, mais c'est plus long à écrire ;)

                      Enfin pour la transparence il te faut utiliser la fonction imread de scipy.ndimage avec le paramètre "mode" à "RGBA". Tu verras, ton tableau ne sera plus MxNx3 mais MxNx4 puisque la quatrième composante représentera le canal alpha (transparence) :)


                      • Partager sur Facebook
                      • Partager sur Twitter
                        7 mars 2016 à 17:14:10

                        merci pour la qualité de ta réponse Nelimee .

                        Quelqu'un a déjà essayé ImageMagick (+- avec python) ?

                        gmic à l'air costaud pour les "effets spéciaux".

                        -
                        Edité par buffalo974 7 mars 2016 à 17:42:51

                        • Partager sur Facebook
                        • Partager sur Twitter
                          7 mars 2016 à 17:54:48

                          @Nelimee: J'avais justement eu l'erreur avec l'enchaînement des conditions. Merci pour l'explication :)
                          • Partager sur Facebook
                          • Partager sur Twitter
                          Précepte: Le mieux est l'ennemi du bien
                            7 mars 2016 à 19:59:19



                            from scipy.misc import imread, imsave
                            from random import randint
                            
                            im = imread("paysage.jpg") #flatten à False par défaut.
                            
                            R = im[:,:,0]
                            G = im[:,:,1]
                            B = im[:,:,2]
                            
                            R[R>127] += randint(0,45)
                            G[G>127] += randint(0,45)
                            B[B>127] += randint(0,45)
                            
                            R[R<127] -= randint(0,45)
                            G[G<127] -= randint(0,45)
                            B[B<127] -= randint(0,45)
                            
                            imsave('resultat.jpg', im)


                            • Partager sur Facebook
                            • Partager sur Twitter

                            Optimiser traitement d'images

                            × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                            × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                            • Editeur
                            • Markdown