1
0

main.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. ### Modules ###
  2. from PIL import Image
  3. from numpy import asarray, ndarray, array, uint8, delete
  4. from random import choices, randint
  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((300, 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_noire(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 tab_image_grise(longueur: int, hauteur: int) -> ndarray:
  41. """Génère un tableau contenant des pixels gris avec les dimensions données"""
  42. # On crée autant de lignes que de pixels de hauteur, et autant de pixels que de hauteur
  43. tab = [ [127] * longueur] * hauteur
  44. return array(tab)
  45. def get_longueur_img(tab_img: ndarray) -> int:
  46. """Renvoie la longueur du tableau à deux dimensions (représentant une image) donné"""
  47. return len(tab_img[0])
  48. def get_hauteur_img(tab_img: ndarray) -> int:
  49. """Renvoie la hauteur du tableau à deux dimensions (représentant une image) donné"""
  50. return len(tab_img)
  51. def assombrir_pixel(tab_img: ndarray, ligne: int, colonne: int) -> ndarray:
  52. """Réduit de 51 (parce que c'est 255/5) les valeurs des couleurs du pixel à la position donnée."""
  53. if tab_img[ligne][colonne] >= 51:
  54. tab_img[ligne][colonne] = tab_img[ligne][colonne] - 51
  55. return tab_img
  56. def eclaircir_pixel(tab_img: ndarray, ligne: int, colonne: int) -> ndarray:
  57. """Augmente de 51 (parce que c'est 255/5) les valeurs des couleurs du pixel à la position donnée."""
  58. if tab_img[ligne][colonne] <= 214:
  59. tab_img[ligne][colonne] += 51
  60. return tab_img
  61. def generer_clous(tab_img: ndarray) -> list:
  62. """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"""
  63. liste_res = []
  64. h = get_hauteur_img(tab_img)
  65. l = get_longueur_img(tab_img)
  66. for i in range(0, l, 10):
  67. liste_res = liste_res + [(i, 0, 0)]
  68. for i in range(10, h, 10):
  69. liste_res = liste_res + [(l - 1, i, 1)]
  70. for i in range(l - 11, -1, - 10):
  71. liste_res = liste_res + [(i, h - 1, 2)]
  72. for i in range(h - 11, 0, -10):
  73. liste_res = liste_res + [(0, i, 3)]
  74. return liste_res
  75. def generer_clous_plus_clous_aleatoires(tab_img):
  76. """Génère un tableau de clous classiques (cf generer_clous) + des clous aleatoires au milieu de l'image"""
  77. h = get_hauteur_img(tab_img)
  78. l = get_longueur_img(tab_img)
  79. liste_res = generer_clous(tab_img)
  80. for i in range(0, 200):
  81. liste_res.append((randint(20, l - 20), randint(20, h - 20), i + 4))
  82. return liste_res
  83. def visible(clou1: tuple, clou2: tuple) -> bool:
  84. """Renvoie True si les clous se voient, False sinon"""
  85. return clou1[2] != clou2[2]
  86. def tracer_droite(clou1: tuple, clou2: tuple, image: ndarray) -> ndarray:
  87. """Trace une droite noire d'un clou à un autre."""
  88. if visible(clou1, clou2):
  89. liste_points = liste_points_traverses(clou1[0], clou1[1], clou2[0], clou2[1])
  90. for i in range(0, len(liste_points)):
  91. ligne = liste_points[i][1]
  92. col = liste_points[i][0]
  93. image = assombrir_pixel(image, ligne, col)
  94. return image
  95. def tracer_droite_blanche(clou1: tuple, clou2: tuple, image: ndarray) -> ndarray:
  96. """Trace une droite blanche d'un clou à un autre."""
  97. if visible(clou1, clou2):
  98. liste_points = liste_points_traverses(clou1[0], clou1[1], clou2[0], clou2[1])
  99. for i in range(0, len(liste_points)):
  100. ligne = liste_points[i][1]
  101. col = liste_points[i][0]
  102. image = eclaircir_pixel(image, ligne, col)
  103. return image
  104. def liste_points_traverses(x1: int, y1: int, x2: int, y2: int) -> list:
  105. """Renvoie les points que le segment [(x1, y1), (x2, y2)] traverse"""
  106. liste_res = []
  107. a, b = calcul_droite(x1, y1, x2, y2)
  108. # Si l'angle entre l'axe des abscisses et la droite est compris entre -45° et 45°
  109. if -1 <= a <= 1:
  110. if x1 > x2:
  111. # On cherche y pour chaque x, donc on commence du point dont le x est le plus proche de 0
  112. x1, y1, x2, y2 = x2, y2, x1, y1
  113. x = x1 + 1
  114. y = y1
  115. liste_res.append((x1, y))
  116. while x <= x2:
  117. y = round(a * x + b)
  118. liste_res.append((x, y))
  119. x += 1
  120. else:
  121. if y1 > y2:
  122. # On cherche x pour chaque y, donc on commence du point dont le y est le plus proche de 0
  123. x1, y1, x2, y2 = x2, y2, x1, y1
  124. if a != 999999999 and b != 999999999:
  125. x = x1
  126. y = y1 + 1
  127. liste_res.append((x, y1))
  128. while y <= y2:
  129. x = round((y - b) / a)
  130. liste_res.append((x, y))
  131. y += 1
  132. # Gestion des droites verticales
  133. else:
  134. x = x1
  135. y = y1 + 1
  136. liste_res.append((x, y1))
  137. for i in range(y, y2 + 1):
  138. liste_res.append((x, i))
  139. return liste_res
  140. def calcul_droite(x1: int, y1: int, x2: int, y2: int) -> tuple:
  141. """Renvoie le coefficient directeur et l'ordonnée à l'origine de la droite passant par (x1, y1) et (x2, y2)"""
  142. if x1 != x2:
  143. a = (y2 - y1) / (x2 - x1)
  144. b = y1 - x1 * a
  145. # Gestion des droites verticales
  146. else:
  147. a = 999999999
  148. b = 999999999
  149. return (a, b)
  150. def liste_clous_al(liste_clous : list, taille_l : int) -> list:
  151. """On créer une liste de clou choisis aléatoirement"""
  152. lst_res = choices(liste_clous, k=taille_l)
  153. return lst_res
  154. def reduction_erreur_droite(clou_dep: tuple, clou_arr: tuple, img1: ndarray, img2: ndarray) -> float:
  155. """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 noire donnée"""
  156. if not visible(clou_dep, clou_arr):
  157. return 999999999
  158. lst_pts = liste_points_traverses(clou_dep[0], clou_dep[1], clou_arr[0], clou_arr[1])
  159. err_act = 0
  160. nb_pts = len(lst_pts)
  161. for i in range(0, nb_pts):
  162. err_act += erreur_point(lst_pts[i], img1, img2)
  163. new_err = 0
  164. for i in range(0, nb_pts):
  165. new_err += erreur_point_assombri(lst_pts[i], img1, img2)
  166. return (new_err - err_act) / nb_pts
  167. def reduction_erreur_droite_blanche(clou_dep: tuple, clou_arr: tuple, img1: ndarray, img2: ndarray) -> float:
  168. """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 blanche donnée"""
  169. if not visible(clou_dep, clou_arr):
  170. return 999999999
  171. lst_pts = liste_points_traverses(clou_dep[0], clou_dep[1], clou_arr[0], clou_arr[1])
  172. err_act = 0
  173. nb_pts = len(lst_pts)
  174. for i in range(0, nb_pts):
  175. err_act += erreur_point(lst_pts[i], img1, img2)
  176. new_err = 0
  177. for i in range(0, nb_pts):
  178. new_err += erreur_point_eclairci(lst_pts[i], img1, img2)
  179. return (new_err - err_act) / nb_pts
  180. def erreur_point(p: tuple, img1: ndarray, img2: ndarray):
  181. """Renvoie la différence entre la valeur du pixel sur l'image 1 et la valeur du pixel sur l'image 2"""
  182. return abs(img1[p[1]][p[0]] - img2[p[1]][p[0]])
  183. def erreur_point_assombri(p: tuple, img1: ndarray, img2: ndarray):
  184. """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"""
  185. if img2[p[1]][p[0]] >= 51:
  186. return abs(img1[p[1]][p[0]] - img2[p[1]][p[0]] + 51)
  187. else:
  188. return erreur_point(p, img1, img2)
  189. def erreur_point_eclairci(p: tuple, img1: ndarray, img2: ndarray):
  190. """Renvoie la différence entre la valeur du pixel sur l'image 1 et la valeur du pixel sur l'image 2 lorsqu'on éclaircit ce pixel"""
  191. if img2[p[1]][p[0]] <= 51:
  192. return abs(img1[p[1]][p[0]] - img2[p[1]][p[0]] - 51)
  193. else:
  194. return erreur_point(p, img1, img2)
  195. def trouve_meilleure_droite(img1: ndarray, img2: ndarray, clou_dep: tuple, lst_clous: list) -> tuple:
  196. """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"""
  197. liste_c = liste_clous_al(lst_clous, len(lst_clous) // 2)
  198. liste_red_err = []
  199. for i in range(0, len(liste_c)):
  200. liste_red_err.append(reduction_erreur_droite(clou_dep, liste_c[i], img1, img2))
  201. red_err = min(liste_red_err)
  202. meilleur_clou = liste_c[liste_red_err.index(red_err)]
  203. return (meilleur_clou, red_err)
  204. def trouve_meilleure_droite_blanche(img1: ndarray, img2: ndarray, clou_dep: tuple, lst_clous: list) -> tuple:
  205. """Renvoie le clou dont la droite blanche le traversant lui et le clou de départ réduit le plus l'erreur entre les deux images"""
  206. liste_c = liste_clous_al(lst_clous, len(lst_clous) // 2)
  207. liste_red_err = []
  208. for i in range(0, len(liste_c)):
  209. liste_red_err.append(reduction_erreur_droite_blanche(clou_dep, liste_c[i], img1, img2))
  210. red_err = min(liste_red_err)
  211. meilleur_clou = liste_c[liste_red_err.index(red_err)]
  212. return (meilleur_clou, red_err)
  213. def retrace_image(chemin_img_source: str, nom_image_sortie: str, resize: bool = False):
  214. """Retrace l'image donnée avec des fils et sauvegarde dans le dossier resultat, au chemin donné"""
  215. image = import_image(chemin_img_source, resize)
  216. new_img = tab_image_blanche(get_longueur_img(image), get_hauteur_img(image))
  217. clous = generer_clous(new_img)
  218. clou_dep = clous[0]
  219. cpt_augmentation_err = 0
  220. cpt_droites = 0
  221. while cpt_augmentation_err < 3:
  222. meilleur_clou, reduction_err = trouve_meilleure_droite(image, new_img, clou_dep, clous)
  223. print(reduction_err, clou_dep, meilleur_clou)
  224. if reduction_err >= 0:
  225. cpt_augmentation_err += 1
  226. # On ne trace la droite que si elle réduit l'erreur
  227. else:
  228. cpt_augmentation_err = 0
  229. new_img = tracer_droite(clou_dep, meilleur_clou, new_img)
  230. clou_dep = meilleur_clou
  231. cpt_droites += 1
  232. print(cpt_droites)
  233. image_res = Image.fromarray(uint8(new_img))
  234. image_res.save("resultat/" + nom_image_sortie + ".png")
  235. def retrace_image_deux_fils(chemin_img_source: str, nom_image_sortie: str, resize: bool = False):
  236. """Retrace l'image donnée avec un fil noir et un fil blanc sur un fond gris et sauvegarde dans le dossier resultatnb, au chemin donné"""
  237. image = import_image(chemin_img_source, resize)
  238. new_img = tab_image_grise(get_longueur_img(image), get_hauteur_img(image))
  239. clous = generer_clous(new_img)
  240. clou_dep_noir = clous[0]
  241. clou_dep_blanc = clous[0]
  242. cpt_augmentation_err = 0
  243. cpt_droites = 0
  244. while cpt_augmentation_err < 5:
  245. meilleur_clou_noir, reduction_err_noire = trouve_meilleure_droite(image, new_img, clou_dep_noir, clous)
  246. meilleur_clou_blanc, reduction_err_blanche = trouve_meilleure_droite_blanche(image, new_img, clou_dep_blanc, clous)
  247. if reduction_err_noire <= reduction_err_blanche:
  248. print(reduction_err_noire, clou_dep_noir, meilleur_clou_noir)
  249. if reduction_err_noire >= 0:
  250. cpt_augmentation_err += 1
  251. # On ne trace la droite que si elle réduit l'erreur
  252. else:
  253. cpt_augmentation_err = 0
  254. new_img = tracer_droite(clou_dep_noir, meilleur_clou_noir, new_img)
  255. clou_dep_noir = meilleur_clou_noir
  256. cpt_droites += 1
  257. else:
  258. print(reduction_err_blanche, clou_dep_blanc, meilleur_clou_blanc)
  259. if reduction_err_blanche >= 0:
  260. cpt_augmentation_err += 1
  261. # On ne trace la droite que si elle réduit l'erreur
  262. else:
  263. cpt_augmentation_err = 0
  264. new_img = tracer_droite_blanche(clou_dep_blanc, meilleur_clou_blanc, new_img)
  265. clou_dep_blanc = meilleur_clou_blanc
  266. cpt_droites += 1
  267. print(cpt_droites)
  268. image_res = Image.fromarray(uint8(new_img))
  269. image_res.save("resultatnb/" + nom_image_sortie + ".png")
  270. def retrace_image_clous_aleatoires(chemin_img_source: str, nom_image_sortie: str, resize: bool = False):
  271. """Retrace l'image donnée avec des fils et sauvegarde dans le dossier resultat, au chemin donné"""
  272. image = import_image(chemin_img_source, resize)
  273. new_img = tab_image_blanche(get_longueur_img(image), get_hauteur_img(image))
  274. clous = generer_clous_plus_clous_aleatoires(new_img)
  275. clou_dep = clous[0]
  276. cpt_augmentation_err = 0
  277. cpt_droites = 0
  278. while cpt_augmentation_err < 3:
  279. meilleur_clou, reduction_err = trouve_meilleure_droite(image, new_img, clou_dep, clous)
  280. print(reduction_err, clou_dep, meilleur_clou)
  281. if reduction_err >= 0:
  282. cpt_augmentation_err += 1
  283. # On ne trace la droite que si elle réduit l'erreur
  284. else:
  285. cpt_augmentation_err = 0
  286. new_img = tracer_droite(clou_dep, meilleur_clou, new_img)
  287. clou_dep = meilleur_clou
  288. cpt_droites += 1
  289. print(cpt_droites)
  290. image_res = Image.fromarray(uint8(new_img))
  291. image_res.save("resultat-clous-al/" + nom_image_sortie + ".png")
  292. ### Tests ###
  293. retrace_image_clous_aleatoires("images/chateau.jpg", "chateau-400px", True)
  294. ### TODO ###
  295. # Pas forcément mesurer l'erreur d'une bande de quelques pixels au bord si on met les clous dedans