diff --git a/code/main.py b/code/main.py index eeb1f94..9f6223d 100644 --- a/code/main.py +++ b/code/main.py @@ -51,6 +51,7 @@ print "| %s |" % exactLength("", 25, 0) print "| 30) %s |" % exactLength("Reveler une teinte", 21, -1) print "| 31) %s |" % exactLength("Colorer une forme", 21, -1) print "| 32) %s |" % exactLength("Colorer les formes", 21, -1) +print "| 33) %s |" % exactLength("Relever les contours", 21, -1) print "+---------------------------+" print "| %s |" % exactLength("TESTS DE FILTRES", 25, 0) print "| %s |" % exactLength("", 25, 0) @@ -158,6 +159,9 @@ elif action == 31: elif action == 32: print startStr colorAllShapes() # colorie la forme contenant le pixel de coordonnées donné +elif action == 33: + print startStr + testStroke() # trace les contours uniquement à partir de formes pleines # filtres diff --git a/code/shapes/3.bmp b/code/shapes/3.bmp new file mode 100644 index 0000000..c785836 Binary files /dev/null and b/code/shapes/3.bmp differ diff --git a/code/shapes/4.bmp b/code/shapes/4.bmp new file mode 100644 index 0000000..5d9a25f Binary files /dev/null and b/code/shapes/4.bmp differ diff --git a/code/tests.py b/code/tests.py index fc0b672..41da0b3 100644 --- a/code/tests.py +++ b/code/tests.py @@ -745,7 +745,7 @@ def colorShape(x=0, y=0): # récupère la forme print "| Getting shape |",; t.reset(); - shape = FX.Shape.get(img.content.map[y][x], img.content.map) + shape = FX.Shape.getShape(img.content.map[y][x], img.content.map) # on colorie la forme en rouge for pixel in shape: @@ -806,7 +806,7 @@ def colorAllShapes(): for pixel in line: # condition (si ce n'est pas le fond ~= noir) if pixel.r + pixel.g + pixel.b > 3*100 and pixel not in already: # si loin du noir - shape = FX.Shape.get(pixel, img.content.map) + shape = FX.Shape.getShape(pixel, img.content.map) print "shape detected" R, G, B = random.randint(0,255), random.randint(0,255), random.randint(0,255) @@ -838,6 +838,60 @@ def colorAllShapes(): +# Récupère et colore les contours à partir de formes pleines # +############################################################## +# @sysarg 1 Image à traiter +# @stsarg 2 Image de sortie +# +# @history +# Parse le fichier d'entrée +# récupère les contours +# trace les contours +# Unparse le tout et l'enregistre dans le fichier de sortie +def testStroke(): + t = Timer(); + img = BMPFile() + + # lecture du fichier + print "| Reading file |",; t.reset(); + with open( sys.argv[1] ) as f: + binFile = f.read(); + print "%s |" % (t.get()) + + # parsage + print "| Parsing image |",; t.reset(); + img.parse( binFile ); + print "%s |" % (t.get()) + + + strokes = [] + # récupère la forme + print "| Getting Strokes |",; t.reset(); + strokes = FX.Shape.getStrokes(img.content.map) + + # met tout les pixels hors des contours en noir et les autres en blanc + for line in img.content.map: + for pixel in line: + if pixel in strokes: + pixel.setRGB(255,255,255) + else: + pixel.setRGB(0,0,0) + print "%s |" % (t.get()) + + + + print "| Unparsing |",; t.reset(); + img.unparse(newBpp=24); + print "%s |" % (t.get()) + + print "| Writing File |",; t.reset(); + with open( sys.argv[2], "w") as f: + f.write( img.binData ); + print "%s |" % (t.get()) + + + + diff --git a/code/utility/Shape.py b/code/utility/Shape.py index 8d1c5d5..75a4272 100644 --- a/code/utility/Shape.py +++ b/code/utility/Shape.py @@ -13,7 +13,7 @@ class Shape: # # @return retourne la liste des pixels composant la forme (références) # - def get(self, originalPixel, pixelMap, seuil=10): + def getShape(self, originalPixel, pixelMap, seuil=10): width = len( pixelMap[0] ) height = len( pixelMap ) @@ -66,4 +66,128 @@ class Shape: except: pass - return shape; \ No newline at end of file + return shape; + + # récupère le contour dans lequel est le pixel donné # + ###################################################### + # @param originalPixel pixel original + # @param pixelMap matrice de pixels + # @param seuil Ecart entre le pixel et ses alentours à partir duquel on considère un contour ou une continuité de la forme + # + # Blanc = fond + # Noir = forme + # + # @return retourne la liste des pixels composant le contour + # + def getEdges(self, originalPixel, pixelMap, seuil=10): + width = len( pixelMap[0] ) + height = len( pixelMap ) + + # retourne un kernel 2x2 (sur lequel on fera les traitements) + def getKernel(pixel, pos): + x, y = pixel.x, pixel.y + if pos == 0: + return [ pixelMap[y][x], pixelMap[y][x+1], pixelMap[y+1][x], pixelMap[y+1][x+1] ]; # [pixel, autre], [autre, autre] + elif pos == 1: + return [ pixelMap[y][x-1], pixelMap[y][x], pixelMap[y+1][x-1], pixelMap[y+1][x] ]; # [autre, pixel], [autre, autre] + elif pos == 2: + return [ pixelMap[y-1][x+1], pixelMap[y-1][x], pixelMap[y][x], pixelMap[y][x+1] ]; # [autre, autre], [pixel, autre] + elif pos == 3: + return [ pixelMap[y-1][x-1], pixelMap[y-1][x], pixelMap[y][x-1], pixelMap[y][x] ]; # [autre, autre], [autre, pixel] + + # retourne un kernel 2x2 en fonction de la direction actuelle et de la position (changement de position) + def flipKernel(kernel, pos, vect): + pixel = kernel[pos]; + if pos == 0: # si kernel de type [pixel, autre], [autre, autre] + if vect == 0: # si direction == 0 (vers la droite) + return getKernel(pixel, pos+2) + if vect == 1: # si direction == 1 (vers le bas) + return getKernel(pixel, pos+1) + + if pos == 1: # si kernel de type [autre, pixel], [autre, autre] + if vect == 1: # si direction == 1 (vers le bas) + return getKernel(pixel, pos-1) + if vect == 2: # si direction == 2 (vers la gauche) + return getKernel(pixel, pos+2) + + if pos == 2: # si kernel de type [autre, autre], [pixel, autre] + if vect == 3: # si direction == 3 (vers le haut) + return getKernel(pixel, pos+1) + if vect == 0: # si direction == 0 (vers la droite) + return getKernel(pixel, pos-2) + + if pos == 3: # si kernel de type [autre, autre], [autre, pixel] + if vect == 2: # si direction == 2 (vers la gauche) + return getKernel(pixel, pos-2) + if vect == 3: # si direction == 3 (vers le haut) + return getKernel(pixel, pos-1) + + # dans un cas non pris en charge on renvoie une liste de "0" + return [0,0,0,0] + + + stroke = [] + + # le pixel de départ + master = originalPixel + slaves = []; + nextP = None + + position = 0; # cf. getKernel + direction = 0; # 0 = droite, 1 = bas, 2 = gauche, 4 = haut + + + # récupère les pixels + while nextP != master: + # on définit le kernel + k = getKernel(master, position) + + # on vide la liste des esclaves + slaves = [] + + # on parcourt les pixels du kernel + for pix in k: + # si pixel sur même ligne ou colonne (contre le pixel maître) + if pix.x == pixel.x or pix.y == pixel.y: + slaves.append( pix ); + + maybeNext = [None, 0]; + + # on parcourt les esclaves + for pix in slaves: + # si l'esclave est quasiment de la même couleur (différence r+g+b < seuil) + if abs(pixel.r-pix.r) <= seuil and abs(pixel.g-pix.g) <= seuil and abs(pixel.b-pix.b) <= seuil: + if pixel.x != pix.x: # si le contour suit les x + maybeNext = [pix, pix.x-pixel.x] + else: # si le contour suit les y + maybeNext = [pix, ] + + + + return stroke + + + # récupère les contours de chaque forme à partir de formes pleines # + #################################################################### + # @param pixelMap matrice de pixels + # @param seuil Ecart entre le pixel et ses alentours à partir duquel on considère un contour ou une continuité de la forme + # + # Blanc = fond + # Noir = forme + # + # @return retourne la liste des contours contenant chacun les pixels la composant + # + def getStrokes(self, pixelMap, seuil=10): + strokeArray = [] # contiendra la liste des contours + already = [] # contient la liste des contours, séparés par un élément valant "0" + stroke = [] # contiendra les pixels du contour en cours + + # on parcourt tout les pixels + for line in pixelMap: + for pixel in line: + if (pixel.r+pixel.g+pixel.b) >= 3*255/2 and pixel not in already: # si pixel ~blanc~ et pas déjà dans un contour + # traitement de forme + stroke = self.getEdges(pixel, pixelMap, seuil=seuil); + already += [0] + stroke + + return strokeArray; \ No newline at end of file diff --git a/docs/draws.svg b/docs/draws.svg new file mode 100644 index 0000000..4ea6b4e --- /dev/null +++ b/docs/draws.svg @@ -0,0 +1,346 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dossier b/dossier new file mode 100644 index 0000000..df33f90 --- /dev/null +++ b/dossier @@ -0,0 +1,25 @@ +\documentclass{article} +\usepackage[utf8]{inputenc} +\usepackage[usenames,dvipsnames,svgnames,table]{xcolor} + +\title{TRAITEMENT D'IMAGE\\Débruitage} +\author{Adrien MARQUÈS - Alexis HELSON } +\date{Septembre 2015} + +\begin{document} + +\maketitle + + + + + + +% Introduction +\section{Introduction} +Cette étude vise à améliorer les méthodes de débruitage d'images, et donc de développer un programme universel de débruitage, c'est-à-dire qu'il réduirait convenablement tout type de bruit. Le programma est développé en Python2 pour la simplicité et efficacité du langage. Le code sur Github à l'adresse suivante:\\ +\begin{center} \textcolor{blue}{https://github.com/xdrm-brackets/bmp\_python} \end{center} + + + +\end{document} diff --git a/dossier.tex b/dossier.tex new file mode 100644 index 0000000..e7418b3 --- /dev/null +++ b/dossier.tex @@ -0,0 +1,109 @@ +\documentclass{article} +\usepackage[utf8]{inputenc} +\usepackage[usenames,dvipsnames,svgnames,table]{xcolor} +\usepackage{amsmath,amsfonts} +\usepackage{lmodern} + + +% Définition des symboles +\newcommand{\N}{\mathbb{N}} % \N pour les entiers naturels +\newcommand{\R}{\mathbb{R}} % \R pour les réels + +\title{TRAITEMENT D'IMAGE\\Débruitage} +\author{Adrien MARQUÈS - Alexis HELSON } +\date{Septembre 2015} + +\begin{document} + + \maketitle + + % Introduction + \section{Introduction} + Cette étude vise à améliorer les méthodes de \emph{nettoyage d'image}, et donc de développer un programme universel de débruitage, c'est-à-dire qu'il réduirait convenablement tout type de bruit, dépendant toutefois de paramètres adaptés à chacun. + \begin{center} \emph{Avant d'essayer d'améliorer quelque chose, il faut le comprendre.} \end{center} + + Pour cela, nous avont donc étudié: + \begin{description} + \item Le débruitage standard + \item Le filtrage par convolution + \item La détection de formes + \item La détection de contours + \end{description} + Le programme a été développé en Python2 pour la simplicité et efficacité du langage. Le code est sur Github à l'adresse suivante: + \begin{center} \textcolor{blue}{https://github.com/xdrm-brackets/bmp\_python} \end{center} + + + + + % Structure du programme + \section{Architecture et structure du programme} + + \begin{description} + \item \textbf{racine} + \begin{description} + \item \textbf{main.py} \hfill \\ + Programme principal, il affiche une interface (en console) permettant d'éxécuter une des opérations disponibles. + \item \textbf{tests.py} \hfill \\ + Contient les corps de toutes le opérations pouvant être choisies dans le programme principal. + \item \textbf{BMPFile.py} \hfill \\ + Contient les structures \emph{BMPFile}, \emph{BMPContent}, \emph{BMPHeader}, permettant d'encoder et de décoder un fichier BMP. Ainsi que la strucure \emph{RGBPixel} permettant de coder un pixel. + \item \textbf{Noise.py} \hfill \\ + Contient tout les traitements tels que les bruitages et débruitages, les filtres, et les détections de formes et contours. (il importe tout le dossier \emph{utility}) + + \begin{description} + \item \textbf{SaltAndPepper\_Noise.py} \hfill \\ + Contient les méthodes de bruitage et débruitage du bruit de type \emph{Poivre \& Sel}. + \item \textbf{Additive\_Noise.py} \hfill \\ + Contient les méthodes de bruitage et débruitage du bruit de type \emph{additif}. + \item \textbf{Gaussian\_Noise.py} \hfill \\ + Contient les méthodes de bruitage et débruitage du bruit de type \emph{gaussien}. + \item \textbf{Multiplicative\_Noise.py} \hfill \\ + Contient les méthodes de bruitage et débruitage du bruit de type \emph{multiplicatif}. + \item \textbf{Color\_Noise.py} \hfill \\ + Contient les méthodes de bruitage et débruitage du bruit de type \emph{multicolore}. + \item \textbf{Filter.py} \hfill \\ + Contient les méthodes de filtrage (standard ou par convolution). + \item \textbf{Shape.py} \hfill \\ + Contient les méthodes de détection de formes et de contours + + \end{description} + \end{description} + \end{description} + + Le fichier \textbf{tests.py} utilise la structure \emph{BMPFile} afin de lire/écrire les fichiers ainsi que la structure \emph{Noise} qui contient toutes les méthodes de bruitage et de débruitage, les filtres et les détections de formes et de contours. Le fichier \emph{\textbf{Noise.py}} (qui contient la structure de même nom) appelle les différents fichiers contenus dans le dossier \textbf{utility}. Les fichiers contenus dans \textbf{utility} peuvent être:\\ + - Un fichier qui contient toutes les méthodes pour un type de bruit (bruitage et débruitage)\\ + - Le fichier \emph{Filters.py} qui contient toutes les méthodes de filtrage\\ + - Le fichier \emph{Shapes.py} qui contient toutes les méthodes de détection de formes et contours + + + + % Fichiers + \section{Fichiers} + La lecture et l'écriture des fichiers se fait octet par octet, la structure \emph{BMPFile} permet de transformer les octets d'un fichier BMP en matrice de pixels, et inversement, de transformer une matrice de pixels en fichier BMP. Les types d'encodage des couleurs pris en charge sont 8 (nuance de gris) bits par pixel, et 24 (rouge, vert, bleu) bits par pixels. Les pixels sont du type \emph{RGBPixel} qui contient les coordonnées du pixel (x, y), ainsi que la couleur du pixel au format RGB (rouge, vert, bleu) chaque nuance prend sa valeur entre 0 et 255. + \\\par Le seul format pris en charge est le format BMP, pour accepter d'autres formats (jpeg, png, gif, etc), il suffirait de récupérer une matrice de pixels (\emph{RGBPixel}), car toutes les méthodes agissent uniquement sur la matrice. Il serait ainsi possible de convertir une image vers un autre format. + + + + + % Débruitage standard + \section{Le bruit Poivre \& Sel} + \textbf{Définition graphique}\par + L'image est parsemée de pixels ayant une teinte extrême (blancs ou noirs) sans rapport avec leur contexte (voisinage). + \- + \\* + \textbf{Bruitage}\par + Soit \emph{width} et \emph{height}, respectivement la largeur et la hauteur de l'image en nombre de pixels. + L'image est bruitée par rapport à \emph{seuil} le seuil de bruitage. + \begin{align*} + &\text{Soit:}\\ + &\text{ - }width\in\N\text{, la largeur de l'image}\\ + &\text{ - }height\in\N\text{, la hauteur de l'image}\\ + &\text{ - }seuil\in\R\text{, le seuil de bruitage, tel que }seuil\in[0;1]\\ + &\text{ - }n = seuil \times width \times height\text{, le nombre de pixels à traiter}\\ + &\text{ - }(x, y) \text{, tel que} (x, y)\in\N^2\text{, et }x\in[0,width[, y\in[0,height[ + \end{align*} + \emph{n} couples \emph{(x, y)} + + + +\end{document} \ No newline at end of file diff --git a/dossier/Salt&Pepper.svg b/dossier/Salt&Pepper.svg deleted file mode 100644 index 6a26ae6..0000000 --- a/dossier/Salt&Pepper.svg +++ /dev/null @@ -1,583 +0,0 @@ - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - 1,1 - 1,2 - 1,3 - 2,3 - 3,3 - 3,2 - 3,1 - 2,1 - 2,2 - - - - - - - - - - - - 1,1 - 1,2 - 1,3 - 2,3 - 3,3 - 3,2 - 3,1 - 2,1 - 2,2 - - - - - - - - - - - - 1,1 - 1,2 - 1,3 - 2,3 - 3,3 - 3,2 - 3,1 - 2,1 - 2,2 - - -