Code source de mlstatpy.image.detection_segment.detection_segment

# -*- coding: utf-8 -*-
"""
Détecte les segments dans une image.


:githublink:`%|py|6`
"""
import math
import copy
import time
import numpy
from PIL import Image, ImageDraw
from .queue_binom import tabule_queue_binom
from .geometrie import Point
from .detection_segment_segangle import SegmentBord
from .detection_nfa import LigneGradient, InformationPoint


[docs]def convert_array2PIL(img, mode=None): """ Convertit une image donnée sous la forme d'un array au format :epkg:`numpy:array`. :param img: :epkg:`numpy:array` :param mode: voir `modes <https://pillow.readthedocs.io/en/3.1.x/handbook/concepts.html#modes>`_, si None, essaye de deviner. :return: *PIL* Le mode ``'binary'`` convertit une image issue de la fonction :func:`random_noise_image <mlstatpy.image.detection_segment.random_image.random_noise_image>`. :githublink:`%|py|29` """ if mode == 'binary': fimg = img.astype(numpy.float32) img255 = (- fimg + 1) * 255 img = img255.astype(numpy.uint8) mode = None return _load_image(img, 'PIL', mode=mode)
[docs]def convert_PIL2array(img): """ Convertit une image donnée sous la forme d'une image :epkg:`Pillow` au format :epkg:`numpy:array`. :param img: :epkg:`Pillow` :return: :epkg:`numpy:array` :githublink:`%|py|45` """ return _load_image(img, 'array')
[docs]def _load_image(img, format='PIL', mode=None): """ Charge une image en différents formats. :param img: image (*array*, *PIL*, filename) :param format: *array* ou *PIL* :param mode: voir `modes <https://pillow.readthedocs.io/en/3.1.x/handbook/concepts.html#modes>`_, si None, essaye de deviner. :return: *PIL* :githublink:`%|py|58` """ if isinstance(img, str): img = Image.open(img) return _load_image(img, format) if isinstance(img, Image.Image): if format == 'PIL': return img if format == 'array': d1, d0 = img.size[1], img.size[0] img = numpy.array(img.getdata(), dtype=numpy.uint8) if len(img.shape) == 1: gray = img.shape[0] - d1 * d0 elif len(img.shape) == 2: gray = img.shape[0] * img.shape[1] - d1 * d0 elif(img.shape) == 3: gray = img.shape[0] * img.shape[1] * img.shape[2] - d1 * d0 else: raise ValueError( # pragma: no cover "Unexpected shape {0}".format(img.shape)) if gray == 0: img = img.reshape((d1, d0)) else: img = img.reshape((d1, d0, 3)) return img raise ValueError( # pragma: no cover "Unexpected value for fomat: '{0}'".format(format)) if isinstance(img, numpy.ndarray): if format == 'array': return img if format == 'PIL': return Image.fromarray(img, mode=mode) raise ValueError( # pragma: no cover "Unexpected value for fomat: '{0}'".format(format)) raise TypeError( # pragma: no cover "numpy array expected not {0}".format(type(img)))
[docs]def compute_gradient(img, color=None): """ Retourne le gradient d'une image sous forme d'une matrice de Point, consideres ici comme des vecteurs. :githublink:`%|py|99` """ return _calcule_gradient(img, color=color)
[docs]def _calcule_gradient(img, color=None): """ Retourne le gradient d'une image sous forme d'une matrice de Point, consideres ici comme des vecteurs. :param img: *fichier*, *array*, *PIL* (image en niveau de gris) :param method: ``'fast'`` or not :param color: calcule le gradient pour cette couleur, None si l'image est en niveau de gris :return: array of *shape (y, x, 2)*, first dimension is *dx*, second one is *dy* :githublink:`%|py|114` """ img = _load_image(img, 'array') img = img.astype(numpy.float32) if color is not None: img = img[:, :, color] dx1 = img[:, 1:-1] - img[:, :-2] dx2 = img[:, 2:] - img[:, 1:-1] dx = (dx1 + dx2) / 2 dy1 = img[1:-1, :] - img[:-2, :] dy2 = img[2:, :] - img[1:-1, :] dy = (dy1 + dy2) / 2 res = numpy.zeros(img.shape + (2,)) res[:, 1:-1, 0] = dx res[1:-1, :, 1] = dy return res
[docs]def plot_gradient(image, gradient, more=None, direction=-1): """ Construit une image a partir de la matrice de gradient afin de pouvoir l'afficher grace au module pygame, cette fonction place directement le resultat dans image, si direction > 0, cette fonction affiche egalement le gradient sur l'image tous les 10 pixels si direction vaut 10. :githublink:`%|py|140` """ image_ = _load_image(image, 'PIL') image = ImageDraw.Draw(image_) X, Y = image_.size if direction != -1: for x in range(0, X - 1): for y in range(0, Y - 1): n = gradient[y, x] if more is None: v = int((n[0]**2 + n[1] ** 2)**0.5 + 0.5) elif more == "x": v = int(n[0] / 2 + 127 + 0.5) else: v = int(n[1] / 2 + 127 + 0.5) image.line([(x, y), (x, y)], fill=(v, v, v)) if direction in (0, -1): pass elif direction > 0: # on dessine des petits gradients dans l'image for x in range(0, X, direction): for y in range(0, Y, direction): n = gradient[y, x] t = (n[0]**2 + n[1] ** 2)**0.5 if t == 0: continue m = copy.copy(n) m /= t if t > direction: t = direction if t < 2: t = 2 m *= t image.line([(x, y), (x + int(m[0]), y + int(m[1]))], fill=(255, 255, 0)) elif direction == -2: # derniere solution, la couleur represente l'orientation # en chaque point de l'image for x in range(0, X): for y in range(0, Y): n = gradient[y, x] i = int(-n[0] * 10 + 128) j = int(n[1] * 10 + 128) i, j = min(i, 255), min(j, 255) i, j = max(i, 0), max(j, 0) image.line([(x, y), (x, y)], fill=(0, j, i)) else: raise ValueError( # pragma: no cover "Unexpected value for direction={0}".format(direction)) return image_
[docs]def plot_segments(image, segments, outfile=None, color=(255, 0, 0)): """ Dessine les segments produits par la fonction :func:`detect_segments <mlstatpy.image.detection_segment.detection_segment.detect_segments>` :param image: image (*fichier*, *array*, *PIL*) :param segments: résultats de la fonction :func:`detect_segments <mlstatpy.image.detection_segment.detection_segment.detect_segments>` :param outfile: fichier de sortie :param color: couleur :return: nom de fichier ou image :githublink:`%|py|202` """ image = _load_image(image, 'PIL') draw = ImageDraw.Draw(image) for seg in segments: draw.line([(seg.a.x, seg.a.y), (seg.b.x, seg.b.y)], fill=color) if outfile is not None: image.save(outfile) return outfile return image
[docs]def detect_segments(image, proba_bin=1.0 / 16, cos_angle=math.cos(1.0 / 16 / 2 * (math.pi * 2)), seuil_nfa=1e-5, seuil_norme=2, angle=math.pi / 24.0, stop=-1, verbose=False): """ Détecte les segments dans une image. :param image: image (*fichier*, *array*, *PIL*) :param proba_bin: est en fait un secteur angulaire (360 / 16) qui determine la proximite de deux directions :param cos_angle: est le cosinus de l'angle correspondant à ce secteur angulaire :param seuil_nfa: au delà de ce seuil, on considere qu'un segment génère trop de fausses alertes pour être sélectionné :param seuil_norme: norme en deça de laquelle un gradient est trop petit pour etre significatif (c'est du bruit) :param angle: lorsqu'on balaye l'image pour détecter les segments, on tourne en rond selon les angles 0, angle, 2*angle, 3*angle, ... :param stop: arrête après avoir collecté tant de segments :param verbose: affiche l'avancement :return: les segments :githublink:`%|py|234` """ gray_image = _load_image(image, 'PIL').convert('L') grad = _calcule_gradient(gray_image) # on calcule les tables de la binomiale pour eviter d'avoir a le fait a # chaque fois qu'on en a besoin yy, xx = grad.shape[:2] nbbin = int(math.ceil(math.sqrt(xx * xx + yy * yy))) binomiale = tabule_queue_binom(nbbin, proba_bin) # nb_seg est le nombre total de segment de l'image # il y a xx * yy pixels possibles dont (xx*yy)^2 couples de pixels (donc de segments) nb_seg = xx * xx * yy * yy # on cree une instance de la classe permettant de parcourir # tous les segments de l'image reliant deux points du contour seg = SegmentBord(Point(xx, yy)) # initialisation avant de parcourir l'image segment = [] # resultat, ensemble des segments significatifs ti = time.perf_counter() # memorise l'heure de depart # pour savoir combien de segments on a deja visite (seg) n = 0 cont = True # condition d'arret de la boucle # on cree une classe permettant de recevoir les informations relatives # a l'image et au gradient pour un segment reliant deux points # du contour de l'image points = [InformationPoint(Point(0, 0), False, 0) for i in range(0, xx + yy)] ligne = LigneGradient(points, seuil_norme=seuil_norme, seuil_nfa=seuil_nfa) # premier segment seg.premier() # autres variables a decouvrir en cours de route not_aligned = 0 # tant qu'on a pas fini while cont: # calcule les informations relative a un segment de l'image reliant deux bords # position des pixels, norme du gradient, alignement avec le segment seg.decoupe_gradient(grad, cos_angle, ligne, seuil_norme) if len(ligne) > 3 and ligne.has_aligned_point(): # si le segment contient plus de trois pixels # alors on peut se demander s'il inclut des sous-segments significatifs res = ligne.segments_significatifs(binomiale, nb_seg) # on ajoute les resultats à la liste segment.extend(res) if len(segment) >= stop > 0: break else: not_aligned += 1 # on passe au segment suivant cont = seg.next() n += 1 # pour verifier que cela avance if verbose and n % 1000 == 0: print( # pragma: no cover "n = ", n, " ... ", len(segment), " temps ", "%2.2f" % (time.perf_counter() - ti), " sec", "nalign", not_aligned) return segment