1
0

main.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  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((256, 256))
  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(x1: int, x2: int, y1: int, y2: int, 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. lst_pts = liste_points_traverses(x1, x2, y1, y2)
  130. err_act = 0
  131. nb_pts = len(lst_pts)
  132. if (x1 == 0 and x2 == 0) or (x1 == get_longueur_img(img2) and x2 == get_longueur_img(img2)) or (y1 == 0 and y2 == 0) or (y1 == get_hauteur_img(img2) and y2 == get_hauteur_img(img2)):
  133. return 999999999
  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[0], clou_dep[1], liste_c[i][0], liste_c[i][1], img1, img2))
  155. red_err = min(liste_red_err)
  156. meilleur_clou = liste_c[liste_red_err.index(red_err)]
  157. return (meilleur_clou, red_err)
  158. def retrace_image(chemin_img_source: str, nom_image_sortie: str, resize: bool = False):
  159. """Retrace l'image donnée avec des fils et sauvegarde dans le dossier resultat, au chemin donné"""
  160. image = import_image(chemin_img_source, resize)
  161. new_img = tab_image_blanche(get_longueur_img(image), get_hauteur_img(image))
  162. clous = generer_clous(new_img)
  163. clou_dep = clous[0]
  164. cpt_augmentation_err = 0
  165. cpt_droites = 0
  166. while cpt_augmentation_err < 2:
  167. meilleur_clou, reduction_err = trouve_meilleure_droite(image, new_img, clou_dep, clous)
  168. new_img = tracer_droite(clou_dep, meilleur_clou, new_img)
  169. clou_dep = meilleur_clou
  170. cpt_droites += 1
  171. if reduction_err >= 0:
  172. cpt_augmentation_err += 1
  173. print(cpt_droites)
  174. image_res = Image.fromarray(uint8(new_img))
  175. image_res.save("resultat/" + nom_image_sortie + ".png")
  176. ### Tests ###
  177. retrace_image("images/losa.jpg", "losa-egzbogez")
  178. ### TODO ###
  179. # Pas forcément mesurer l'erreur d'une bande de quelques pixels au bord si on met les clous dedans