main.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. ### Modules ###
  2. from PIL import Image
  3. from numpy import asarray, ndarray, array, uint8, delete
  4. from random import choices
  5. from copy import deepcopy
  6. ### Fonctions ###
  7. def import_image(chemin: str, resize: bool = False) -> ndarray :
  8. """Charge l'image donnée, la passe en nuances de gris, et la convertit en tableau (de numpy)"""
  9. ## Chargement de l'image ##
  10. # On essaie d'ouvrir l'image avec la librairie PIL
  11. try:
  12. image = Image.open(chemin)
  13. # Gestion d'erreur si le fichier n'est pas accessible ou n'existe pas
  14. except FileNotFoundError:
  15. print("Le chemin d'accès est invalide.")
  16. # TODO Set image sur une image blanche en cas d'erreur
  17. # Gestion d'erreur dans les autres cas
  18. except:
  19. print("Une erreur s'est produite")
  20. # TODO Pareil qu'au dessus
  21. ## Conversion de l'image en nuances de gris ##
  22. # On utilise la méthode .convert() de PIL
  23. image = image.convert("L")
  24. if resize:
  25. image = image.resize((400, 400))
  26. ## Conversion en tableau ##
  27. # On utilise la fonction asarrray() de numpy
  28. image_array = asarray(image)
  29. return image_array
  30. def tab_image_blanche(longueur: int, hauteur: int) -> ndarray:
  31. """Génère un tableau contenant des pixels blancs avec les dimensions données"""
  32. # On crée autant de lignes que de pixels de hauteur, et autant de pixels que de hauteur
  33. tab = [ [255] * longueur] * hauteur
  34. return array(tab)
  35. def tab_image_noir(longueur: int, hauteur: int) -> ndarray:
  36. """Génère un tableau contenant des pixels noirs avec les dimensions données"""
  37. # On crée autant de lignes que de pixels de hauteur, et autant de pixels que de hauteur
  38. tab = [ [0] * longueur] * hauteur
  39. return array(tab)
  40. def get_longueur_img(tab_img: ndarray) -> int:
  41. """Renvoie la longueur du tableau à deux dimensions (représentant une image) donné"""
  42. return len(tab_img[0])
  43. def get_hauteur_img(tab_img: ndarray) -> int:
  44. """Renvoie la hauteur du tableau à deux dimensions (représentant une image) donné"""
  45. return len(tab_img)
  46. def assombrir_pixel(tab_img: ndarray, ligne: int, colonne: int) -> ndarray:
  47. """Réduit de 51 (parce que c'est 255/5) les valeurs des couleurs du pixel à la position donnée."""
  48. if tab_img[ligne][colonne] >= 51:
  49. tab_img[ligne][colonne] = tab_img[ligne][colonne] - 51
  50. return tab_img
  51. def generer_clous(tab_img: ndarray) -> list:
  52. """Génère un tableau qui contient des tuples qui représentent des clous : deux coordonnées et un numéro de côté -> 0 pour le haut, 1 pour la droite, 2 pour le bas, 3 pour la gauche"""
  53. liste_res = []
  54. h = get_hauteur_img(tab_img)
  55. l = get_longueur_img(tab_img)
  56. for i in range(0, l, 10):
  57. liste_res = liste_res + [(i, 0, 0)]
  58. for i in range(10, h, 10):
  59. liste_res = liste_res + [(l - 1, i, 1)]
  60. for i in range(l - 11, -1, - 10):
  61. liste_res = liste_res + [(i, h - 1, 2)]
  62. for i in range(h - 11, 0, -10):
  63. liste_res = liste_res + [(0, i, 3)]
  64. return liste_res
  65. def visible(clou1: tuple, clou2: tuple) -> bool:
  66. """Renvoie True si les clous se voient, False sinon"""
  67. return clou1[2] != clou2[2]
  68. def tracer_droite(clou1: tuple, clou2: tuple, image: ndarray) -> ndarray:
  69. """Trace une droite d'un clou à un autre."""
  70. if visible(clou1, clou2):
  71. liste_points = liste_points_traverses(clou1[0], clou1[1], clou2[0], clou2[1])
  72. for i in range(0, len(liste_points)):
  73. ligne = liste_points[i][1]
  74. col = liste_points[i][0]
  75. image = assombrir_pixel(image, ligne, col)
  76. return image
  77. def liste_points_traverses(x1: int, y1: int, x2: int, y2: int) -> list:
  78. """Renvoie les points que le segment [(x1, y1), (x2, y2)] traverse"""
  79. liste_res = []
  80. a, b = calcul_droite(x1, y1, x2, y2)
  81. # Si l'angle entre l'axe des abscisses et la droite est compris entre -45° et 45°
  82. if -1 <= a <= 1:
  83. if x1 > x2:
  84. # On cherche y pour chaque x, donc on commence du point dont le x est le plus proche de 0
  85. x1, y1, x2, y2 = x2, y2, x1, y1
  86. x = x1 + 1
  87. y = y1
  88. liste_res.append((x1, y))
  89. while x <= x2:
  90. y = round(a * x + b)
  91. liste_res.append((x, y))
  92. x += 1
  93. else:
  94. if y1 > y2:
  95. # On cherche x pour chaque y, donc on commence du point dont le y est le plus proche de 0
  96. x1, y1, x2, y2 = x2, y2, x1, y1
  97. if a != 999999999 and b != 999999999:
  98. x = x1
  99. y = y1 + 1
  100. liste_res.append((x, y1))
  101. while y <= y2:
  102. x = round((y - b) / a)
  103. liste_res.append((x, y))
  104. y += 1
  105. # Gestion des droites verticales
  106. else:
  107. x = x1
  108. y = y1 + 1
  109. liste_res.append((x, y1))
  110. for i in range(y, y2 + 1):
  111. liste_res.append((x, i))
  112. return liste_res
  113. def calcul_droite(x1: int, y1: int, x2: int, y2: int) -> tuple:
  114. """Renvoie le coefficient directeur et l'ordonnée à l'origine de la droite passant par (x1, y1) et (x2, y2)"""
  115. if x1 != x2:
  116. a = (y2 - y1) / (x2 - x1)
  117. b = y1 - x1 * a
  118. # Gestion des droites verticales
  119. else:
  120. a = 999999999
  121. b = 999999999
  122. return (a, b)
  123. def liste_clous_al(liste_clous : list, taille_l : int) -> list:
  124. """On créer une liste de clou choisis aléatoirement"""
  125. lst_res = choices(liste_clous, k=taille_l)
  126. return lst_res
  127. def reduction_erreur_droite(clou_dep: tuple, clou_arr: tuple, img1: ndarray, img2: ndarray) -> float:
  128. """Renvoie la différence entre l'erreur de l'image cible et de l'image actuelle, et l'erreur de l'image cible et de l'image actuelel si on traçait la droite donnée"""
  129. if not visible(clou_dep, clou_arr):
  130. return 999999999
  131. lst_pts = liste_points_traverses(clou_dep[0], clou_dep[1], clou_arr[0], clou_arr[1])
  132. err_act = 0
  133. nb_pts = len(lst_pts)
  134. for i in range(0, nb_pts):
  135. err_act += erreur_point(lst_pts[i], img1, img2)
  136. new_err = 0
  137. for i in range(0, nb_pts):
  138. new_err += erreur_point_trace(lst_pts[i], img1, img2)
  139. return (new_err - err_act) / nb_pts
  140. def erreur_point(p: tuple, img1: ndarray, img2: ndarray):
  141. """Renvoie la différence entre la valeur du pixel sur l'image 1 et la valeur du pixel sur l'image 2"""
  142. return abs(img1[p[1]][p[0]] - img2[p[1]][p[0]])
  143. def erreur_point_trace(p: tuple, img1: ndarray, img2: ndarray):
  144. """Renvoie la différence entre la valeur du pixel sur l'image 1 et la valeur du pixel sur l'image 2 lorsqu'on assombrit ce pixel"""
  145. if img2[p[1]][p[0]] >= 51:
  146. return abs(img1[p[1]][p[0]] - img2[p[1]][p[0]] + 51)
  147. else:
  148. return erreur_point(p, img1, img2)
  149. def trouve_meilleure_droite(img1: ndarray, img2: ndarray, clou_dep: tuple, lst_clous: list) -> tuple:
  150. """Renvoie le clou dont la droite le traversant lui et le clou de départ réduit le plus l'erreur entre les deux images"""
  151. liste_c = liste_clous_al(lst_clous, len(lst_clous) // 2)
  152. liste_red_err = []
  153. for i in range(0, len(liste_c)):
  154. liste_red_err.append(reduction_erreur_droite(clou_dep, liste_c[i], img1, img2))
  155. red_err = min(liste_red_err)
  156. meilleur_clou = liste_c[liste_red_err.index(red_err)]
  157. print(red_err, clou_dep, meilleur_clou)
  158. return (meilleur_clou, red_err)
  159. def retrace_image(chemin_img_source: str, nom_image_sortie: str, resize: bool = False):
  160. """Retrace l'image donnée avec des fils et sauvegarde dans le dossier resultat, au chemin donné"""
  161. image = import_image(chemin_img_source, resize)
  162. new_img = tab_image_blanche(get_longueur_img(image), get_hauteur_img(image))
  163. clous = generer_clous(new_img)
  164. clou_dep = clous[0]
  165. cpt_augmentation_err = 0
  166. cpt_droites = 0
  167. while cpt_augmentation_err < 3:
  168. meilleur_clou, reduction_err = trouve_meilleure_droite(image, new_img, clou_dep, clous)
  169. if reduction_err >= 0:
  170. cpt_augmentation_err += 1
  171. # On ne trace la droite que si elle réduit l'erreur
  172. else:
  173. new_img = tracer_droite(clou_dep, meilleur_clou, new_img)
  174. clou_dep = meilleur_clou
  175. cpt_droites += 1
  176. if cpt_droites % 5000 == 0:
  177. img_prov = Image.fromarray(uint8(new_img))
  178. img_prov.show()
  179. print(cpt_droites)
  180. image_res = Image.fromarray(uint8(new_img))
  181. image_res.save("resultat/" + nom_image_sortie + ".png")
  182. ### Tests ###
  183. retrace_image("images/oiseau.jpg", "oiseau-400px", True)
  184. ### TODO ###
  185. # Pas forcément mesurer l'erreur d'une bande de quelques pixels au bord si on met les clous dedans