Compare commits

...

29 Commits
test ... master

Author SHA1 Message Date
Ludovic CASTIGLIA 2459bc231f ajout du zip
4 months ago
Ludovic CASTIGLIA fe0678e675 modification readme
4 months ago
Ludovic CASTIGLIA b541773f9a ajout et modification de dernière minutes
4 months ago
Ludovic CASTIGLIA e6c34733ff ajout d'exemple + interface
4 months ago
ludovic.castglia 79057a7081 gitignore + interface
4 months ago
Ludovic CASTIGLIA 688e37dc6f changement readme
4 months ago
Ludovic CASTIGLIA 047f50e72d readme
4 months ago
Ludovic CASTIGLIA 2080bbc784 merge comment plus ajout tentative infructueuse
4 months ago
Ludovic CASTIGLIA bfe218a9c1 chargement asset
4 months ago
Ludovic CASTIGLIA cbc523b935 nouvelle génération de la demo2.png
4 months ago
Ludovic CASTIGLIA 63d7218f08 commentaire
4 months ago
Ludovic CASTIGLIA 66b99b2960 commentaire ajouté
4 months ago
Ludovic CASTIGLIA 1aebd69352 changement de multithread et regen du demo2
4 months ago
Ludovic CASTIGLIA 2498b92908 fin des optimization
4 months ago
Ludovic CASTIGLIA 7a5e1081bc opti et simplification
4 months ago
Ludovic CASTIGLIA e3d2a4dd7a quelques optimization
4 months ago
Ludovic CASTIGLIA 8f77457707 selection
4 months ago
Ludovic CASTIGLIA d8ea070449 erreur de logique dans nnf sur les fields
4 months ago
Ludovic CASTIGLIA be0997a6fd simplification
4 months ago
Ludovic CASTIGLIA 067d1000f6 nouvel algo multi tout (patch,size,pass)
4 months ago
Ludovic CASTIGLIA 66e654b8e5 multiscale multimask multipass
4 months ago
Ludovic CASTIGLIA 1142eb842c .gitignore
4 months ago
Ludovic CASTIGLIA c920a1e989 multiscale qui marche pas
4 months ago
Ludovic CASTIGLIA 604874c029 smothPatch
4 months ago
Ludovic CASTIGLIA 5ed4051a8e patchMatch2 au propre
4 months ago
Ludovic CASTIGLIA 943101b050 nouvelle implémentation au propre
4 months ago
ludovic.castglia c660ba2d91 quelques teset
4 months ago
ludovic.castglia 98146bcbb6 patchMatch première version
4 months ago
ludovic.castglia 02ac771caf ajout algo knn basique
4 months ago

3
.gitignore vendored

@ -6,6 +6,9 @@ __pycache__/
# C extensions
*.so
a
dog/
old/
# Distribution / packaging
.Python

@ -1,2 +1,62 @@
# patchMatch
# Patch Match by Ludovic CASTIGLIA
## Ce qui est fourni dans le projet:
Est fournit dans ce projet le code d'une partie de mes anciennes tentatives infructueuses dans le dossier ```./tentatives infrutueuses```.
Vous pouvez également retrouver dans les dossiers portant les mêmes noms que les anciennes tentatives des photos illustrant le résultat avant/après pour que vous puissiez vous rendre compte de la progression sans avoir à les exécuter.
Vous avez également à la racine du projet, la version final de mon algorithme ainsi qu'un dossier ```./asset``` et ```./demo``` contenant des images et des démonstrations de l'algorithme.
## Utilisation du projet:
Pour lancer la version final de l'algorithme en mode console, mettez-vous à la racine du projet. Puis lancez cette commande ```./python selection.py```. Ensuite, suivez les instructions écrites dans le terminal.
Pour lancer la version final de l'algorithme en mode gui, mettez-vous à la racine du projet. Puis lancez cette commande ```./python interface.py```. Puis suivez les instructions affiché via les box de dialogue.
En image, vous pouvez rentrer ```./asset/vache.png``` et pour le masque, vous pouvez dessiner le vôtre ou utiliser ```./asset/demo1_mask.png``` ou tout autre combo image et mask présent dans asset et demo ou dans votre disque dur (le programme accèpte les images .png .jpg .bmp ...).
## Principe de l'algorithme:
Le principe de cet algorithme est assez simple à comprendre, mais son implémentation comporte quelques subtilités.</br>
On commence cet algorithme avec une image et un mask (image booléene de même dimension que l'image). Dans un premier temp, on génère en boucle une version à chaque fois deux fois plus petite de l'image jusqu'a ce que la largeur ou la hauteur de l'image face 2 pixel. Ensuite, il suffirat de remplir le trou pour la première image (la plus réduite) puis de compléter les images suivantes à partir des précédentes jusqu'a obtenir l'image en résolution complête.</br>
Mais la question est comment remplir ce trou et compléter l'image à partir des autres? Pour cela on vas venir trouver dans l'image actuel via une recherche par proximité géographique puis de manière random (plusieurs fois pour réduire les erreurs) un patch (un groupe de pixel) qui a une faible distance avec l'emplacement que l'on veut remplacer (distance dans le sens différence entre les valeurs des pixels). Une fois que l'on a trouver plusieurs patchs pour une zone, chaque patch vas voté pour la valeur des pixels en fonction d'un poid inversement proportionel à la distance (plus le patch est différent de la zone moins son vote a de l'importance).</br>
On procède ainsi pour toutes les zones dans le troue jusqu'a obtenir l'image. Ensuite on recommence ce procèsse plusieurs fois pour chaque résolution de l'image (en prenant en modèle la version précédente de l'image pas la résolution précédente) afin de réduire la différence entre les patchs et ainsi augmenter la cohérence globale de l'image.<br>
Le dernier point à développer est le calcule de la distance entre deux patchs. Pour cela, je viens calculer la différence élevé au carré des valeurs des pixels. Et lorsque le patch survol le mask ou est en dehors de l'image alors sa distance est la distance maximum (255*255*3) pour que le patch ne soit pas selectioné.
## Exemple avant | après de l'algorithme:
<image src="./asset/vache.png" width="400px"/>
<image src="./demo/demo1.png" width="400px"/>
<image src="./demo/demo2.png" width="400px"/></br>
<image src="./asset/trou.jpg" width="400px"/>
<image src="./demo/demo3.png" width="400px"/>
<image src="./demo/demo4.png" width="400px"/></br>
<image src="./asset/hib.jpg" width="400px"/>
<image src="./demo/demo5.png" width="400px"/></br>
<image src="./asset/btl.jpg" width="400px"/>
<image src="./demo/demo6.png" width="400px"/>
## Tentatives infructueuses:
### knn:
Cet algorithme était plutôt simple, lorsque l'on a généré un trou, on vient le remplir en faisant un knn sur tous les pixels en périphérie de ce trou jusqu'à avoir recouvert entièrement le trou. Cet algorithme n'est adapté qu'au patch de forme rectangulaire et il ne marche bien que si le fond que l'on veut régénérer est plutôt lisse et sans texture.
### PatchMatch2:
Dans cette tentative, j'ai implémenté très naïvement ce que je comprenais de l'algorithme patch match pour l'inpainting. Cependant, même si cet algorithme marche un peu mieux avec des fonds texturé, il crée tout de même une démarcation claire entre l'image de base et la masque.
### smothPatch:
Pour palier au problème de démarcation claire entre patch, j'ai fusionné les deux algorithmes précédents. Je commence par recouvrir le masque de patch de l'extérieur vers l'intérieur jusqu'à ce que le masque soit entièrement recouvert. Puis entre toutes les bordures de patch, je viens appliquer l'algorithme knn sur la bordure pour faire une transition plus douce entre les patchs. À ce moment, je commençais à trouver ce résultat satisfaisant. Cependant, on voyait encore les démarcations entre le masque et le reste de l'image.
### multiScale:
Pour cette implémentation, je suis repartie de PatchMatch2 que j'ai appliqué sur différentes tailles de résolution. Le résultat produit n'était pas très bon, car ma recherche de patch était défaillante. Cependant, malgré des résultats mitigés, on pouvait entrevoir du potentiel. J'ai donc décidé de partir de cette base pour réaliser la version final de mon algorithme qui en plus de la taille multiple, fait du multi masque (applique différents masques au même endroit pour faire réduire la variation).
## Inspirations:
- https://gfx.cs.princeton.edu/pubs/Barnes_2009_PAR/patchmatch.pdf
- https://www.youtube.com/watch?v=XTRO6yQOvJc
- https://helios2.mi.parisdescartes.fr/~jdelon/enseignement/tp_image/org/TP_inpainting.html
- https://wenfulee.github.io/CS-766-Computer-Vision/
## Lien du dépot public code first:
https://codefirst.iut.uca.fr/git/ludovic.castiglia/patchMatch

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 267 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

@ -0,0 +1,184 @@
from imageRelation import ImageRelation
from maskedImage import MaskedImage
import matplotlib.pyplot as plt
from tkinter import filedialog
import concurrent.futures
import tkinter as tk
import numpy as np
import pyautogui
import cv2
def read(file):
# fonction qui renvoit une image à partir de son chemin
img = plt.imread(file)
if img.dtype == np.float32: # si on est en valeur entre 0 et 1 au lieux de entre 0 et 255
img = (img * 255).astype(np.uint8)
img = img[:,:,0:3] # si on est en rgba
return img
def doTheInpainting(img,mask,radius):
# fonction qui effectue l'impainting
def maximizeForTheScale(scale):
# fonction interne qui effectue l'impainting pour une version réduite de l'image
iterEM = 1+2*scale
npPass = min(7,1+scale)
source = sourceToTarget.input
target = targetToSource.output
newTarget = None
for emloop in range(1,iterEM+1): # on fait les différentes pass pour augmenter petit à petit la qualité de l'image
# initialisation
if (newTarget != None):
targetToSource.input = newTarget
target = newTarget
newTarget = None
for y in range(source.height):
for x in range(source.width):
if not source.containsMask(x,y,radius):
targetToSource.field[y,x] = (x,y,0)
# on cherche les patchs
targetToSource.findBestPatch(npPass)
# on crée la source et la target
upscaled = False
if scale>=1 and emloop == iterEM:
newSource = images[scale-1]
newTarget = target.upScale(newSource.height,newSource.width)
upscaled = True
else:
newSource = images[scale]
newTarget = target.copy()
upscaled = False
# on vote, on applique les votes puis on affiche les changements
vote = np.zeros((newTarget.width, newTarget.height, 4))
voteForPixels(targetToSource,vote,newSource,upscaled)
applyVote(newTarget, vote)
result = cv2.resize(newTarget.image, (initial.width, initial.height), interpolation=cv2.INTER_AREA)
plt.imshow(result)
plt.pause(0.01)
return newTarget, sourceToTarget, targetToSource
initial = MaskedImage(img,mask)
images = [initial]
source = initial
while source.width>radius and source.height>radius: # crée les versions réduite en qualité de l'image
source = source.downScale()
images.append(source)
maxLevel = len(images)
for level in range(maxLevel-1,0,-1): # pour toutes les tailes de l'image, progressivement completer le trou à partir des images de qualité infèrieur déjà calculé
source = images[level]
if (level == maxLevel-1):
target = source.copy()
target.mask[0:target.height,0:target.width] = False
sourceToTarget = ImageRelation(source,target,radius)
sourceToTarget.randomize()
targetToSource = ImageRelation(target,source,radius)
targetToSource.randomize()
else:
newImRel = ImageRelation(source,target,radius)
newImRel.initializeFromImageRelation(sourceToTarget)
sourceToTarget = newImRel
newImRelRev = ImageRelation(target,source,radius)
newImRelRev.initializeFromImageRelation(targetToSource)
targetToSource = newImRelRev
target, sourceToTarget, targetToSource = maximizeForTheScale(level)
plt.imshow(target.image)
plt.pause(0.01)
return target.image
def voteForPixels(imRel, vote, source, upscale):
def voteForNb(nb):
# ajoute au vote pour toutes les pixels les valeurs déterminé lors de la recherche des patchs
wid = imRel.input.width//7
for y in range(imRel.input.height):
for x in range(nb*wid,(nb+1)*wid if nb != 7 else imRel.input.width): # divise par 8 l'image dans la largeur pour calculer ses 8 parties en même temps grâce aux threads
xp, yp, dp = imRel.field[y,x]
w = MaskedImage.similarity[dp]
for dy in range(-imRel.patchSize,imRel.patchSize): # pour toutes les pixels du patch qui ne sorte pas de l'image
ys = yp+dy
if not 0<=ys<imRel.input.height:
continue
yt = y+dy
if not 0<=yt<imRel.input.height:
continue
for dx in range(-imRel.patchSize,imRel.patchSize):
xs = xp+dx
if not 0<=xs<imRel.input.width:
continue
xt = x+dx
if not 0<=xt<imRel.input.width:
continue
if upscale: # si on change d'échelle prendre les 4 pixels autour pour faire comme un knn
addToVote(source,2*xs,2*ys,vote,2*xt,2*yt,w)
addToVote(source,2*xs+1,2*ys,vote,2*xt+1,2*yt,w)
addToVote(source,2*xs,2*ys+1,vote,2*xt,2*yt+1,w)
addToVote(source,2*xs+1,2*ys+1,vote,2*xt+1,2*yt+1,w)
else:
addToVote(source,xs,ys,vote,xt,yt,w)
# utilise une pool pour profiter des 8 threads de ma machine pour réduire le temp d'execution
pool = concurrent.futures.ThreadPoolExecutor(max_workers=8)
for i in range(8):
pool.submit(voteForNb,i)
pool.shutdown(wait=True)
def addToVote(src,xs,ys,vote,xd,yd,w):
# on ajoute au vote d'une pixel la couleur de la pixel asigné multiplié par un poid qui change en fonction de la similarité entre les deux pixels
if src.mask[ys,xs]:
return
vote[xd,yd,0] += w*src.image[ys,xs,0]
vote[xd,yd,1] += w*src.image[ys,xs,1]
vote[xd,yd,2] += w*src.image[ys,xs,2]
vote[xd,yd,3] += w
def applyVote(target,vote):
# appliquer les changements de pixels déterminé par les votes
for y in range(target.height):
for x in range(target.width):
if vote[x,y,3]>0:
r = int(vote[x,y,0]/vote[x,y,3])
g = int(vote[x,y,1]/vote[x,y,3])
b = int(vote[x,y,2]/vote[x,y,3])
target.image[y,x] = (r,g,b)
target.mask[y,x] = False
def binDialog(titre,text):
# box de dialogue à choix bianire
pyautogui.FAILSAFE = True
return pyautogui.confirm(
text=text,
title=titre,
buttons=['Oui', 'Non']
)
def dialog(titre,text):
# box de dialogue à choix bianire
pyautogui.FAILSAFE = True
pyautogui.confirm(
text=text,
title=titre,
buttons=['Ok']
)
def stringDialog(titre, text):
# box de dialogue avec un input
pyautogui.FAILSAFE = True
return pyautogui.prompt(
text=text,
title=titre,
default=''
)
def selectImage():
# fonction de selection d'une image
root = tk.Tk()
root.withdraw() # Hide the main window
file_path = filedialog.askopenfilename(
title="Select an image",
filetypes=[
("Image files", "*.png *.jpg *.jpeg *.bmp")
]
)
return file_path

@ -0,0 +1,114 @@
from maskedImage import MaskedImage
import numpy as np
import random
class ImageRelation:
def __init__(self,input,output,patchSize):
self.input = input
self.output = output
self.patchSize = patchSize
def randomize(self):
# on crée un assignation zone patch de manière random
self.field = np.zeros((self.input.height, self.input.width, 3), dtype=int)
self.field[:,:,2] = MaskedImage.DSCALE # tant que la réel distance est pas calculé, elle est concidéré comme maximal
self.field[:,:,0] = np.random.randint(0,self.output.width,(self.input.height,self.input.width))
self.field[:,:,1] = np.random.randint(0,self.output.height,(self.input.height,self.input.width))
self.initialize() # on calcule la vrai distance et on change les patch si ils ne conviennent pas
def initializeFromImageRelation(self,imRel):
# on crée l'assignation zone patch à partir des assignations précédentes
self.field = np.zeros((self.input.height, self.input.width, 3), dtype=int)
fx = int(self.input.width/imRel.input.width)
fy = int(self.input.height/imRel.input.height)
for y in range(self.input.height):
for x in range(self.input.width):
xl = min(int(x/fx),imRel.input.width-1)
yl = min(int(y/fy),imRel.input.height-1)
self.field[y,x] = (imRel.field[yl,xl,0]*fx, imRel.field[yl,xl,1]*fy, MaskedImage.DSCALE)
self.initialize() # on calcule la vrai distance et on change si ils ne conviennent pas
def initialize(self):
for y in range(self.input.height):
for x in range(self.input.width):
self.field[y,x,2] = self.distance(x,y,self.field[y,x,0],self.field[y,x,1]) # calcule la vrai distance des patchs
iter= 0
maxIter = 10 # au cas ou pour ne pas rester bloqué par manque de chance
while (self.field[y,x,2] == MaskedImage.DSCALE and iter<maxIter): # tant qu'on a pas trouvé un patch qui n'est pas dans le trou
self.field[y,x] = (random.randint(0,self.output.width),random.randint(0,self.output.height),self.distance(x,y,self.field[y,x,0],self.field[y,x,1]))
iter += 1
def findBestPatch(self,nbPass):
# recherche les meilleurs patch pour toutes les zones
for i in range(nbPass):
for y in range(self.input.height-1): # on cherche le meilleur patch à droit et en bas
for x in range(self.input.width-1):
if (self.field[y,x,2]>0):
self.findBestPatchFroOne(x,y,i)
for y in range(self.input.height-1,0,-1): # on cherche le meilleur patche en haut et à gauche
for x in range(self.input.width-1,0,-1):
if (self.field[y,x,2]>0):
self.findBestPatchFroOne(x,y,-i)
def findBestPatchFroOne(self,x,y,direction):
# recherche le meilleur patch pour une zone en particulier dans un sens (en haut et à gauche | à droite et en bas) + cherche random
# horizontale
if (0<x-direction<self.input.width):
xp = self.field[y,x-direction,0] +direction
yp = self.field[y,x-direction,1]
dp = self.distance(x,y,xp,yp)
if (dp<self.field[y,x,2]):
self.field[y,x] = (xp,yp,dp)
# verticale
if (0<y-direction<self.input.height):
xp = self.field[y-direction,x,0]
yp = self.field[y-direction,x,1] + direction
dp = self.distance(x,y,xp,yp)
if (dp<self.field[y,x,2]):
self.field[y,x] = (xp,yp,dp)
# recherche random
# (on fait la cherche random ici et pas dans la méthode parente pour pouvoir faire deux recherche random au lieux de 1
# ce qui ne côute pas grand chose et augmente la qualité de l'inpainting)
zoneRecherche = max(self.output.height,self.output.width)
while zoneRecherche>0:
xp = self.field[y,x,0] + random.randint(0,2*zoneRecherche)-zoneRecherche
yp = self.field[y,x,1] + random.randint(0,2*zoneRecherche)-zoneRecherche
xp = max(0,min(self.output.width-1,xp))
yp = max(0,min(self.output.height-1,yp))
dp = self.distance(x,y,xp,yp)
if (dp<self.field[y,x,2]):
self.field[y,x] = (xp,yp,dp)
zoneRecherche = int(zoneRecherche/2)
def distance(self,x,y,xp,yp):
# simple interface avec la function distance
return distance(self.input,x,y,self.output,xp,yp,self.patchSize)
def distance(source, xs, ys, target, xt, yt, patchSize): # cette function est ici et pas dans function pour éviter les import circulaire et car ce fichier est le seul qui l'utilise
# calcule la distance entre deux patch (pas la distance physique mais la distance des valeurs de leurs pixels)
# en utilisant la somme des erreurs au carré
# la valeurs maximal de cette fonction est MaskedImage.DSCALE et n'est retourné en pratique presque que quand
# le patch à un problème (qu'il est en partie dans un trou ou qu'il sort du cadre de l'image)
dMax = 255*255*3
distance, wsum = 0, dMax * (patchSize*2)**2
for dy in range(-patchSize, patchSize):
yks, ykt = ys + dy, yt + dy
if not (1 <= yks < source.height - 1 and 1 <= ykt < target.height - 1):
distance += dMax * patchSize * 2
continue
for dx in range(-patchSize, patchSize):
xks, xkt = xs + dx, xt + dx
if not (1 <= xks < source.width - 1 and 1 <= xkt < target.width - 1):
distance += dMax
continue
if source.containsMask(xks, yks,patchSize):
distance += dMax
continue
if target.containsMask(xkt, ykt,patchSize):
distance += dMax
continue
d = np.sum((source.image[yks, xks] - target.image[ykt, xkt]) ** 2)
distance += d
return int(MaskedImage.DSCALE * distance / wsum)

@ -0,0 +1,85 @@
from matplotlib.widgets import RectangleSelector
import matplotlib.pyplot as plt
from function import *
import numpy as np
# verrifier que l'image est de même taille que le mask
def onSelect(eclick, erelease):
# remplace les pixels sélectioné en rouge et enregistre ses pixels comme étant à modifé
x1, y1 = int(eclick.xdata), int(eclick.ydata)
x2, y2 = int(erelease.xdata), int(erelease.ydata)
img[y1:y2+1,x1:x2+1] = (255,0,0)
mask[y1:y2+1,x1:x2+1] = True
maskImg[y1:y2+1,x1:x2+1] = 255
ax.imshow(img)
plt.draw()
dialog("consigne","Dans la prochaine boite de dialogue, selectionez l'image que vous voulez traiter")
imgOr = read(selectImage())
img = imgOr.copy()
rep = binDialog("image mask", "avez vous déjà le mask de l'image") == "Oui"
height, width = img.shape[:2]
if rep:
# récupère le mask enregistré
maskImg = read(selectImage())
if (len(maskImg.shape) == 3): # converti l'image si elle est trop
maskImg = maskImg[:,:,0]
mask = maskImg == 255
if (img.shape[:2] != mask.shape[:2]):
dialog("erreur","La taille de l'image et la taille du mask ne correspondent pas. Veuillez relancer le programme")
exit()
img[mask] = (255,0,0)
else:
# lance la fenêtre de selection
fig, ax = plt.subplots()
plt.axis("off")
ax.imshow(img)
mask = np.zeros((height, width), dtype=bool)
maskImg = np.zeros((height, width), dtype=np.uint8)
# permet la selection en rectangle sur l'image et appelle la fonction onSelect
toggle_selector = RectangleSelector(ax, onSelect, useblit=True,
button=[1], minspanx=5, minspany=5, spancoords='pixels',
interactive=True)
plt.imshow(img)
plt.show()
if (img.shape[:2] != mask.shape[:2]):
dialog("erreur","la taille de l'image et la taille du mask ne corresponde pas")
exit()
# lance une nouvelle fenêtre que l'on vas pouvoir annimer en fonction de l'avancement de l'inpainting
plt.ion()
plt.axis("off")
plt.imshow(mask)
plt.show()
plt.title("mask de l'image")
plt.pause(0.01)
plt.title("génération de l'image")
# lance l'impainting sur img avec le mask mask avec une taile de patch de 2
res = doTheInpainting(img,mask,2)
plt.ioff()
plt.close()
dialog("terminé","la génération de l'image est terminé")
if binDialog("avant/après","Voulez vous voir la comparaison avant après ?") == "Oui":
fig, (ax1, ax2) = plt.subplots(1, 2)
ax1.imshow(imgOr)
ax1.set_title('Original')
ax1.axis("off")
ax2.imshow(res)
ax2.set_title('Result')
ax2.axis("off")
plt.show()
if binDialog("mask de l'image","Voulez vous enregistrer le mask de l'image ?") == "Oui":
save_path = stringDialog("mask de l'image","Entrez le chemin d'enregistrement du mask: ")
plt.imsave(save_path, maskImg, cmap='gray')
if binDialog("image","Voulez vous enregister l'image généré ?") == "Oui":
save_path = stringDialog("image","Entrez le chemin d'enregistrement de l'image: ")
plt.imsave(save_path, res)
dialog("fin","Merci d'avoir utitilé ce programe")

Binary file not shown.

@ -0,0 +1,80 @@
import numpy as np
class MaskedImage:
DSCALE = 10_000 # valeur arbitraire qui est le nombre max de la function de distance
base = [1.0, 0.99, 0.96, 0.83, 0.38, 0.11, 0.02, 0.005, 0.0006, 0.0001, 0]
similarity = np.interp(np.linspace(0, 1, DSCALE), np.linspace(0, 1, len(base)), base)
# base et similarity permettent de calculer le poid à appliquer en fonction de la distance (plus la distance est grande plus le poid vas être élevé pour obtenir la prochaine fois une distance faible)
def __init__(self,image=None,mask=None,width=None,height=None):
# deux méthodes de création d'objet:
# - à partir des dimentions
# - à partir d'une image et d'un mask déjà existant
if image is not None:
self.image = image
self.height, self.width = image.shape[:2]
self.mask = mask
return
self.width = width
self.height = height
self.image = np.zeros((self.height, self.width, 3), dtype=np.uint8)
self.mask = np.zeros((self.height, self.width), dtype=bool)
def containsMask(self,x,y,patchSize):
# renvoit vrai si le patch centrer en x,y est en partie dans le trou
for dy in range(-patchSize,patchSize):
if (y+dy < 0 or self.height <= y+dy):
continue
for dx in range(-patchSize,patchSize):
if (x+dx < 0 or self.width <= x+dx):
continue
if self.mask[y+dy,x+dx]:
return True
return False
def copy(self):
return MaskedImage(image=self.image.copy(), mask=self.mask.copy())
def downScale(self):
# réduit la qualité d'une image par deux en faisant la moyene des valeurs des pixels qui disparaisse
newW, newH = self.width // 2, self.height // 2
poids = np.array([1, 5, 10, 10, 5, 1]) # poid du vote en fonction de la distance avec le centre
newimage = np.zeros((newH, newW, 3), dtype=np.uint8)
newmask = np.zeros((newH, newW), dtype=bool)
for y in range(0, self.height - 1, 2):
for x in range(0, self.width - 1, 2):
r, g, b, ttP, m = 0, 0, 0, 0, 0
for dy in range(-2, 4): # faire un vote de la couleur avec toutes les pixels à deux de distance (x et/ou y) du centre
yk = y + dy
if (yk<0 or yk >= self.height):
continue
ky = poids[2 + dy]
for dx in range(-2, 4):
xk = x + dx
if (xk<0 or xk >= self.width):
continue
if self.mask[yk, xk]:
continue
k = poids[2 + dx] * ky
r += k * self.image[yk, xk, 0] # ajout du vote pour les valeurs r g et b
g += k * self.image[yk, xk, 1]
b += k * self.image[yk, xk, 2]
ttP += k
m += 1
if ttP > 0:
newimage[y // 2, x // 2] = (r // ttP, g // ttP, b // ttP) # assignation aux pixels de la moyenne pondéré des voisines
newmask[y // 2, x // 2] = False
else:
newmask[y // 2, x // 2] = True
return MaskedImage(image=newimage, mask=newmask)
def upScale(self, newH, newW):
# renvoit une nouvelle image aux bonnes dimentions
y_indices = np.floor(np.linspace(0, self.height-1, newH)).astype(int)
x_indices = np.floor(np.linspace(0, self.width-1, newW)).astype(int)
y_coords, x_coords = np.meshgrid(y_indices, x_indices, indexing='ij')
newImage = MaskedImage(width=newW, height=newH)
newImage.image = self.image[y_coords, x_coords]
newImage.mask = self.mask[y_coords, x_coords]
return newImage

@ -0,0 +1,74 @@
from matplotlib.widgets import RectangleSelector
import matplotlib.pyplot as plt
from function import *
import numpy as np
# verrifier que l'image est de même taille que le mask
def onSelect(eclick, erelease):
# remplace les pixels sélectioné en rouge et enregistre ses pixels comme étant à modifé
x1, y1 = int(eclick.xdata), int(eclick.ydata)
x2, y2 = int(erelease.xdata), int(erelease.ydata)
img[y1:y2+1,x1:x2+1] = (255,0,0)
mask[y1:y2+1,x1:x2+1] = True
maskImg[y1:y2+1,x1:x2+1] = 255
ax.imshow(img)
plt.draw()
imgOr = read(input("lien relatif de l'image: "))
img = imgOr.copy()
rep = input("avez vous le mask de l'image (y:n): ")
yes = ["y","Y","yes","YES","Yes","o","O","oui","OUI"]
height, width = img.shape[:2]
if (rep in yes):
# récupère le mask enregistré
maskImg = read(input("lien relatife de l'image: "))
if (len(maskImg.shape) == 3):
maskImg = maskImg[:,:,0]
mask = maskImg == 255
img[mask] = (255,0,0)
else:
# lance la fenêtre de selection
fig, ax = plt.subplots()
plt.axis("off")
ax.imshow(img)
mask = np.zeros((height, width), dtype=bool)
maskImg = np.zeros((height, width), dtype=np.uint8)
# permet la selection en rectangle sur l'image et appelle la fonction onSelect
toggle_selector = RectangleSelector(ax, onSelect, useblit=True,
button=[1], minspanx=5, minspany=5, spancoords='pixels',
interactive=True)
plt.imshow(img)
plt.show()
# lance une nouvelle fenêtre que l'on vas pouvoir annimer en fonction de l'avancement de l'inpainting
plt.ion()
plt.axis("off")
plt.imshow(mask)
plt.show()
plt.title("mask de l'image")
plt.pause(1)
plt.title("génération de l'image")
# lance l'impainting sur img avec le mask mask avec une taile de patch de 2
res = doTheInpainting(img,mask,2)
plt.ioff()
if input("afficher le résultat avant après (y/n): ") in yes:
fig, (ax1, ax2) = plt.subplots(1, 2)
ax1.imshow(imgOr)
ax1.set_title('Original')
ax1.axis("off")
ax2.imshow(res)
ax2.set_title('Result')
ax2.axis("off")
plt.show()
if input("enregister le mask (y/n):") in yes:
save_path = input("Entrez le chemin d'enregistrement du mask: ")
plt.imsave(save_path, maskImg, cmap='gray')
if input("enregister l'image produite (y/n):") in yes:
save_path = input("Entrez le chemin d'enregistrement de l'image: ")
plt.imsave(save_path, res)

@ -0,0 +1,164 @@
from matplotlib.widgets import RectangleSelector
import matplotlib.pyplot as plt
from random import randint
import numpy as np
def initialPatchMatch(img,x1,y1,x2,y2,patchSize=129):
def getDist(pValue1, pValue2):
return np.sum((pValue1 - pValue2) ** 2)
def initializePermimiter(finish=False):
perimeter = []
for x in range(x1, x2 + 1):
perimeter.append((x, y1))
perimeter.append((x, y2))
if finish:
perimeter.append((x,y1-1))
perimeter.append((x,y2+1))
for y in range(y1 + 1, y2):
perimeter.append((x1, y))
perimeter.append((x2, y))
if finish:
perimeter.append((x1-1,y))
perimeter.append((x2+1,y))
return np.array(perimeter)
def getRandomPatchFromPerimiter(perimiter):
x,y = perimiter[np.random.randint(len(perimiter))]
patch = np.array([[i, j] for i in range(x - semiPatch, x + semiPatch + 1)
for j in range(y - semiPatch, y + semiPatch + 1)])
return patch
def getZoneMask(zoneValue,outside):
mask = []
for value in zoneValue:
mask.append((value.sum() == 0) ^outside)
return np.array(mask)
def applyMask(patch,mask,oposed=False):
return patch[mask^oposed]
def getValueFromPatch(patch):
ret = img[patch[0][1]:patch[0][1]+patchSize,patch[0][0]:patch[0][0]+patchSize]
ret = ret.transpose(1, 0, 2)
return ret.reshape(-1, 3)
def getRandomPatch(patchCoordFound):
if (len(patchCoordFound) == 0):
#TODO peut être trouver un patch autour du trou et verrifier que pas dans le trou
x = randint(semiPatch,width-semiPatch-1)
y = randint(semiPatch,height-semiPatch-1)
patch = np.array([[i, j] for i in range(x - semiPatch, x + semiPatch + 1)
for j in range(y - semiPatch, y + semiPatch + 1)])
else:
patch = patchCoordFound[randint(0,len(patchCoordFound)-1)]
return patch
def getBestNeigbourPatch(zoneMask,filteredZoneValue,dist,patch,offset):
voisin = [[-1,-1],[-1,0],[0,-1],[0,0],[1,-1],[-1,1],[0,1],[1,0],[1,1]]
found = False
bPatch = []
for x,y in voisin:
nPatch = patch.copy()
nPatch[:,0] += x*offset
nPatch[:,1] += y*offset
if np.any(nPatch < 0) or np.any(nPatch[:,0] >= width) or np.any(nPatch[:,1] >= height):
#TODO verrifier que le patch est pas dans le troue si non ff
continue
nPatchValue = getValueFromPatch(nPatch)
filteredPatchValue = applyMask(nPatchValue,zoneMask)
nDist = getDist(filteredZoneValue,filteredPatchValue)
if (nDist < dist):
dist = nDist
bPatch = nPatch
found = True
return found,bPatch,dist
def getBestPatchForZone(zoneValue,zoneMask,patchCoordFound):
filteredZoneValue = applyMask(zoneValue,zoneMask)
patch = getRandomPatch(patchCoordFound)
patchValue = getValueFromPatch(patch)
filteredPatchValue = applyMask(patchValue,zoneMask)
dist = getDist(filteredZoneValue,filteredPatchValue)
offset = 1
while offset < min(width,height):
found, nPatch,nDist = getBestNeigbourPatch(zoneMask,filteredZoneValue,dist,patch,offset)
if (found):
patch = nPatch
dist = nDist
offset = 1
else:
offset*=2
patchCoordFound.append(patch)
return patchValue
def applyPatch(filteredZone,zoneMask, patchValue):
filteredPatchValue = applyMask(patchValue,zoneMask,True)
for i in range(len(filteredZone)) :
img[filteredZone[i][1],filteredZone[i][0]] = filteredPatchValue[i]
def updatePerimiter(filteredZone,perimiter):
for x,y in filteredZone:
if ((x,y) in filteredZone):
perimiter = np.delete(perimiter, np.where((perimiter == [x, y]).all(axis=1))[0], axis=0)
voisin = [[-1,-1],[-1,0],[0,-1],[0,0],[1,-1],[-1,1],[0,1],[1,0],[1,1]]
for x,y in filteredZone:
for offsetx,offsety in voisin:
if img[y+offsety,x+offsetx].sum() == 0:
perimiter = np.vstack((perimiter, [x+offsetx, y+offsety]))
return perimiter
def addEdge(edges,zone):
# pas des deux coté car zone pas filteredZone pour endroit biscornue
x,y = zone[0]
for xx in range(x,x+patchSize):
if x1<=xx<=x2:
if y1<=y<=y2:
edges.append([xx,y])
if y1<=y+patchSize<=y2:
edges.append([xx,y+patchSize])
for yy in range(y,y+patchSize):
if y1<=yy<=y2:
if x1<=x<=x2:
edges.append([x,yy])
if x1<=x+patchSize<=x2:
edges.append([x+patchSize,yy])
return edges
def smoothEdges(edges):
perimiter = initializePermimiter(True)
edges.extend(perimiter.tolist())
edges = np.array(edges)
offsets = np.array([[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]])
for edge in edges:
neighbors = edge + offsets[:,None]
neighbors = neighbors.reshape(-1,2)
valid_neighbors = neighbors[
(neighbors[:,0] >= 0) & (neighbors[:,0] < width) &
(neighbors[:,1] >= 0) & (neighbors[:,1] < height)
]
if len(valid_neighbors) > 0:
neighbor_values = img[valid_neighbors[:,1], valid_neighbors[:,0]]
avg_value = np.mean(neighbor_values, axis=0)
img[edge[1], edge[0]] = avg_value
semiPatch = int(patchSize/2)
height, width, _ = img.shape
patchCoordFound = []
edges = []
perimiter = initializePermimiter()
while len(perimiter)> 0:
zone = getRandomPatchFromPerimiter(perimiter)
edges = addEdge(edges,zone)
zoneValue = getValueFromPatch(zone)
zoneMask = getZoneMask(zoneValue,True)
filteredZoneInside = applyMask(zone,zoneMask,True)
patchValue = getBestPatchForZone(zoneValue,zoneMask,patchCoordFound)
applyPatch(filteredZoneInside,zoneMask,patchValue)
perimiter = updatePerimiter(filteredZoneInside,perimiter)
smoothEdges(edges)
return img

@ -0,0 +1,83 @@
from matplotlib.widgets import RectangleSelector
import matplotlib.pyplot as plt
import numpy as np
def doKnn(img,x1,y1,x2,y2):
def getNotInBoundNeighbour(neighbour, x1,y1,x2,y2):
mask = np.logical_or(
np.logical_or(neighbour[:, 0] < y1, neighbour[:, 0] > y2),
np.logical_or(neighbour[:, 1] < x1, neighbour[:, 1] > x2)
)
return neighbour[mask]
def neighbourReelPixel(x,y):
tNeighbour = np.copy(neighbour)
tNeighbour = tNeighbour + np.array([y,x])
return tNeighbour
def getAvgPixelFromNeighbour(neighbour):
return np.mean(img[neighbour[:,0],neighbour[:,1]], axis=0)
neighbour = np.array([[-1,-1],[-1,0],[0,-1],[-1,1],[1,-1],[0,1],[1,0],[1,1]])
x1c = x1
y1c = y1
x2c = x2
y2c = y2
# tant que les pixels en périphérie du trou ne se rejoignent pas alors le trou n'est pas comblé
while x1 != x2 and y1 != y2:
# on comble les pixels à gauche et à droite
for x in range(x1,x2):
currentNeighbour1 = neighbourReelPixel(x,y1)
currentNeighbour2 = neighbourReelPixel(x,y2)
currentNeighbour1 = getNotInBoundNeighbour(currentNeighbour1,x1,y1,x2,y2)
currentNeighbour2 = getNotInBoundNeighbour(currentNeighbour2,x1,y1,x2,y2)
currentColor1 = getAvgPixelFromNeighbour(currentNeighbour1)
currentColor2 = getAvgPixelFromNeighbour(currentNeighbour2)
img[y1,x] = currentColor1
img[y2,x] = currentColor2
# puis en haut et en bas
for y in range(y1,y2):
currentNeighbour1 = neighbourReelPixel(x1,y)
currentNeighbour2 = neighbourReelPixel(x2,y)
currentNeighbour1 = getNotInBoundNeighbour(currentNeighbour1,x1,y1,x2,y2)
currentNeighbour2 = getNotInBoundNeighbour(currentNeighbour2,x1,y1,x2,y2)
currentColor1 = getAvgPixelFromNeighbour(currentNeighbour1)
currentColor2 = getAvgPixelFromNeighbour(currentNeighbour2)
img[y,x1] = currentColor1
img[y,x2] = currentColor2
x1 += 1
x2 -= 1
y1 += 1
y2 -= 1
for x in range(x1c, x2c):
for y in range(y1c, y2c):
currentNeighbour = neighbourReelPixel(x, y)
currentNeighbour = getNotInBoundNeighbour(currentNeighbour,0,0,0,0)
currentColor = getAvgPixelFromNeighbour(currentNeighbour)
img[y, x] = currentColor
img[y1:y2,x1:x2]
return img
img = plt.imread('asset/boat.png')
if len(img.shape) == 2:
img = np.stack((img,)*3, axis=-1)
def onselect(eclick, erelease):
x1, y1 = eclick.xdata, eclick.ydata
x2, y2 = erelease.xdata, erelease.ydata
img_copy = np.copy(img)
res = doKnn(img_copy,int(x1),int(y1),int(x2),int(y2))
ax.imshow(res)
plt.draw()
fig, ax = plt.subplots()
ax.imshow(img)
toggle_selector = RectangleSelector(ax, onselect, useblit=True,
button=[1], minspanx=5, minspany=5, spancoords='pixels',
interactive=True)
plt.axis('off')
plt.show()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

@ -0,0 +1,156 @@
from matplotlib.widgets import RectangleSelector
import matplotlib.pyplot as plt
from random import randint
import numpy as np
import cv2
import time
from function import *
def reScale(img,scale):
height, width = img.shape[:2]
new_height = int(height / scale)
new_width = int(width / scale)
scaled_img = cv2.resize(img, (new_width, new_height), interpolation=cv2.INTER_AREA)
return scaled_img, new_height,new_width
def reScaleCoord(oWidth,oHeight,nWidth,nHeight,x1,y1,x2,y2):
x1, x2 = int(x1*nWidth/oWidth),int(x2*nWidth/oWidth)
y1, y2 = int(y1*nHeight/oHeight),int(y2*nHeight/oHeight)
return x1,y1,x2,y2
def getDist(pValue1, pValue2):
return np.sum((pValue1 - pValue2) ** 2)
def getRandomPatch(img2,pSize,x1,y1,x2,y2):
height, width = img2.shape[:2]
x = [randint(0,x1),randint(x2,width-pSize)][randint(0,1)]
y = [randint(0,y1),randint(y2,height-pSize)][randint(0,1)]
patch = getZoneFromCoord(x,y,pSize)
return patch
def getValueFromPatch(img,patch,pSize):
ret = img[patch[0][1]:patch[0][1]+pSize,patch[0][0]:patch[0][0]+pSize]
ret = ret.transpose(1, 0, 2)
return ret.reshape(-1, 3)
def applyPatch(img,zone,patchValue):
for i in range(len(zone)) :
img[zone[i][1],zone[i][0]] = patchValue[i]
return img
def findBestPatchFromNeigbour(zoneValue,oDist,patch,offset,height,width,img,pSize):
neigbour = [[-1,-1],[-1,0],[0,-1],[-1,1],[1,-1],[0,1],[1,0],[1,1]]
trouve = False
rP = patch
for x,y in neigbour:
p = patch.copy()
p[:,0] += x*offset
p[:,1] += y*offset
if np.any(p < 0) or np.any(p[:,0] >= width) or np.any(p[:,1] >= height):
continue
value = getValueFromPatch(img,p,pSize)
dist = getDist(zoneValue,value)
if (dist < oDist):
trouve = True
oDist = dist
rP = p
return trouve, rP, oDist
def findBestPatch(img2,zone,zoneValue,pSize,pixSize,height,width,x1,y1,x2,y2):
if not (x1<=zone[0][0]<=x2 and y1<=zone[0][1]):
patch = zone.copy()
return patch
patch = getRandomPatch(img2,int(pSize/pixSize)*2,x1,y1,x2,y2)
pValue = getValueFromPatch(img2,patch,pSize)
pdist = getDist(zoneValue,pValue)
for i in range(500):
tpatch = getRandomPatch(img2,int(pSize/pixSize)*2,x1,y1,x2,y2)
tpValue = getValueFromPatch(img2,tpatch,pSize)
tpdist = getDist(zoneValue,tpValue)
if tpdist<pdist:
pdist = tpdist
patch = tpatch
offset = 1
while offset < min(height,width)/3:
found, nPatch,nDist = findBestPatchFromNeigbour(zoneValue,pdist,patch,int(offset),height,width,img2,pSize)
if found:
patch = nPatch
pdist = nDist
offset = 1
else:
offset*=2
return patch
def getZoneFromCoord(x,y,patchSize):
zone = np.array([[i, j] for i in range(x, x + patchSize)
for j in range(y, y + patchSize)])
return zone
def rebuildImg(img1,img2,pixSize,x1,y1,x2,y2):
height,width = img1.shape[:2]
pSize = pixSize * 2
for x in range(int(width/pSize)):
for y in range(int(height/pSize)):
zone = getZoneFromCoord(x*pSize,y*pSize,pSize)
if not (x1<=x*pSize<=x2 and y1<=y*pSize<=y2):
zoneValue = getValueFromPatch(img2,zone,pSize)
applyPatch(img1,zone,zoneValue)
continue
zoneValue = getValueFromPatch(img1,zone,pSize)
patch = findBestPatch(img2,zone,zoneValue,pSize,pixSize,height,width,x1,y1,x2,y2)
patchValue = getValueFromPatch(img2,patch,pSize)
img1 = applyPatch(img1,zone,patchValue)
return img1
def doPatchMatch(image,x1,y1,x2,y2,scaleFactor=20,patchSize=129):
oHeight, oWidth = image.shape[:2]
rImage, nHeight, nWidth = reScale(image,scaleFactor)
nx1, ny1, nx2, ny2 = reScaleCoord(oWidth,oHeight,nWidth,nHeight,x1,y1,x2,y2)
rImage[ny1:ny2+1, nx1:nx2+1] = 0
rImage = initialPatchMatch(rImage,nx1,ny1,nx2,ny2,5)
while scaleFactor != 2:
scaleFactor -= 1
rImage, nHeight, nWidth = reScale(rImage,scaleFactor/(scaleFactor+1))
timg, h,w = reScale(image,scaleFactor)
nx1, ny1, nx2, ny2 = reScaleCoord(oWidth,oHeight,w,h,x1,y1,x2,y2)
rImage = rebuildImg(rImage,timg,int(h/nHeight),nx1,ny1,nx2,ny2)
tempRes, _, _= reScale(rImage,1/scaleFactor)
ax.imshow(tempRes)
plt.draw()
plt.pause(0.1)
nHeight = h
print(scaleFactor)
return tempRes
img = plt.imread('asset/vache.png')
if img.dtype == np.float32:
img = (img * 255).astype(np.uint8)
img = img[:,:,0:3]
def onselect(eclick, erelease):
x1, y1 = eclick.xdata, eclick.ydata
x2, y2 = erelease.xdata, erelease.ydata
print("drawing")
img_copy = np.copy(img)
res = doPatchMatch(img_copy,int(x1),int(y1),int(x2),int(y2))
ax.imshow(res)
plt.draw()
print("drawed")
fig, ax = plt.subplots()
ax.imshow(img)
toggle_selector = RectangleSelector(ax, onselect, useblit=True,
button=[1], minspanx=5, minspany=5, spancoords='pixels',
interactive=True)
plt.axis('off')
plt.show()

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

@ -0,0 +1,166 @@
from matplotlib.widgets import RectangleSelector
import matplotlib.pyplot as plt
from random import randint
import numpy as np
def doPatchMatch(img,x1,y1,x2,y2,patchSize=65):
def getDist(pValue1, pValue2):
return np.sum((pValue1 - pValue2) ** 2)
def initializePermimiter():
perimeter = []
for x in range(x1, x2 + 1):
perimeter.append((x, y1))
perimeter.append((x, y2))
for y in range(y1 + 1, y2):
perimeter.append((x1, y))
perimeter.append((x2, y))
img[y1:y2+1, x1:x2+1] = 0
return np.array(perimeter)
def getRandomPatchFromPerimiter(perimiter):
x,y = perimiter[np.random.randint(len(perimiter))]
patch = np.array([[i, j] for i in range(x - semiPatch, x + semiPatch + 1)
for j in range(y - semiPatch, y + semiPatch + 1)])
return patch
def getZoneMask(zoneValue,outside):
mask = []
for value in zoneValue:
mask.append((value.sum() == 0) ^outside)
return np.array(mask)
def applyMask(patch,mask,oposed=False):
patchf = []
for i in range(len(mask)):
if(mask[i]^oposed):
patchf.append(patch[i])
return np.array(patchf)
def getValueFromPatch(patch):
value = []
for x,y in patch:
value.append(img[y,x])
return np.array(value)
def getRandomPatch(patchCoordFound):
if (len(patchCoordFound) ==0):
#TODO peut être trouver un patch autour du trou et verrifier que pas dans le trou
x = randint(semiPatch,width-semiPatch-1)
y = randint(semiPatch,height-semiPatch-1)
patch = np.array([[i, j] for i in range(x - semiPatch, x + semiPatch + 1)
for j in range(y - semiPatch, y + semiPatch + 1)])
else:
patch = patchCoordFound[randint(0,len(patchCoordFound)-1)]
return patch
def getBestNeigbourPatch(zoneMask,filteredZoneValue,dist,patch,offset):
voisin = [[-1,-1],[-1,0],[0,-1],[0,0],[1,-1],[-1,1],[0,1],[1,0],[1,1]]
found = False
bPatch = []
for x,y in voisin:
nPatch = patch.copy()
nPatch[:,0] += x*offset
nPatch[:,1] += y*offset
if np.any(nPatch < 0) or np.any(nPatch[:,0] >= width) or np.any(nPatch[:,1] >= height):
#TODO verrifier que le patch est pas dans le troue si non ff
continue
nPatchValue = getValueFromPatch(nPatch)
filteredPatchValue = applyMask(nPatchValue,zoneMask)
nDist = getDist(filteredZoneValue,filteredPatchValue)
if (nDist < dist):
dist = nDist
bPatch = nPatch
found = True
return found,bPatch,dist
def getBestPatchForZone(zoneValue,zoneMask,patchCoordFound):
filteredZoneValue = applyMask(zoneValue,zoneMask)
patch = getRandomPatch(patchCoordFound)
patchValue = getValueFromPatch(patch)
filteredPatchValue = applyMask(patchValue,zoneMask)
dist = getDist(filteredZoneValue,filteredPatchValue)
offset = 1
while offset < min(width,height)/3:
found, nPatch,nDist = getBestNeigbourPatch(zoneMask,filteredZoneValue,dist,patch,offset)
if (found):
patch = nPatch
dist = nDist
offset = 1
else:
offset*=2
patchCoordFound.append(patch)
return patchValue
def applyPatch(zoneCoord,zoneMask, patchValue):
filteredPatchValue = applyMask(patchValue,zoneMask,True)
filteredZone = applyMask(zoneCoord,zoneMask,True)
for i in range(len(filteredZone)) :
img[filteredZone[i][1],filteredZone[i][0]] = filteredPatchValue[i]
def updatePerimiter(zone,zoneMask,perimiter):
filteredZone = applyMask(zone,zoneMask,True)
for x,y in filteredZone:
if ((x,y) in filteredZone):
perimiter = np.delete(perimiter, np.where((perimiter == [x, y]).all(axis=1))[0], axis=0)
voisin = [[-1,-1],[-1,0],[0,-1],[0,0],[1,-1],[-1,1],[0,1],[1,0],[1,1]]
for x,y in filteredZone:
for offsetx,offsety in voisin:
if img[y+offsety,x+offsetx].sum() == 0:
perimiter = np.vstack((perimiter, [x+offsetx, y+offsety]))
return perimiter
semiPatch = int(patchSize/2)
height, width, _ = img.shape
patchCoordFound = []
eadges = []
perimiter = initializePermimiter()
it = 0
while len(perimiter)> 0:
zone = getRandomPatchFromPerimiter(perimiter)
zoneValue = getValueFromPatch(zone)
zoneMask = getZoneMask(zoneValue,True)
patchValue = getBestPatchForZone(zoneValue,zoneMask,patchCoordFound)
applyPatch(zone,zoneMask,patchValue)
perimiter = updatePerimiter(zone,zoneMask,perimiter)
it +=1
print(it)
return img
# for x, y in zone:
# if 0 <= x < width and 0 <= y < height:
# img[y, x] = [255, 255, 255]
# return img
img = plt.imread('asset/mur.jpg')
if img.dtype == np.float32:
img = (img * 255).astype(np.uint8)
img = img[:,:,0:3]
def onselect(eclick, erelease):
x1, y1 = eclick.xdata, eclick.ydata
x2, y2 = erelease.xdata, erelease.ydata
print("drawing")
img_copy = np.copy(img)
res = doPatchMatch(img_copy,int(x1),int(y1),int(x2),int(y2))
ax.imshow(res)
plt.draw()
print("drawed")
fig, ax = plt.subplots()
ax.imshow(img)
toggle_selector = RectangleSelector(ax, onselect, useblit=True,
button=[1], minspanx=5, minspany=5, spancoords='pixels',
interactive=True)
plt.axis('off')
plt.show()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

@ -0,0 +1,196 @@
from matplotlib.widgets import RectangleSelector
import matplotlib.pyplot as plt
from random import randint
import numpy as np
def doPatchMatch(img,x1,y1,x2,y2,patchSize=129):
def getDist(pValue1, pValue2):
return np.sum((pValue1 - pValue2) ** 2)
def initializePermimiter(finish=False):
perimeter = []
for x in range(x1, x2 + 1):
perimeter.append((x, y1))
perimeter.append((x, y2))
if finish:
perimeter.append((x,y1-1))
perimeter.append((x,y2+1))
for y in range(y1 + 1, y2):
perimeter.append((x1, y))
perimeter.append((x2, y))
if finish:
perimeter.append((x1-1,y))
perimeter.append((x2+1,y))
return np.array(perimeter)
def getRandomPatchFromPerimiter(perimiter):
x,y = perimiter[np.random.randint(len(perimiter))]
patch = np.array([[i, j] for i in range(x - semiPatch, x + semiPatch + 1)
for j in range(y - semiPatch, y + semiPatch + 1)])
return patch
def getZoneMask(zoneValue,outside):
mask = []
for value in zoneValue:
mask.append((value.sum() == 0) ^outside)
return np.array(mask)
def applyMask(patch,mask,oposed=False):
return patch[mask^oposed]
def getValueFromPatch(patch):
ret = img[patch[0][1]:patch[0][1]+patchSize,patch[0][0]:patch[0][0]+patchSize]
ret = ret.transpose(1, 0, 2)
return ret.reshape(-1, 3)
def getRandomPatch(patchCoordFound):
if (len(patchCoordFound) == 0):
#TODO peut être trouver un patch autour du trou et verrifier que pas dans le trou
x = randint(semiPatch,width-semiPatch-1)
y = randint(semiPatch,height-semiPatch-1)
patch = np.array([[i, j] for i in range(x - semiPatch, x + semiPatch + 1)
for j in range(y - semiPatch, y + semiPatch + 1)])
else:
patch = patchCoordFound[randint(0,len(patchCoordFound)-1)]
return patch
def getBestNeigbourPatch(zoneMask,filteredZoneValue,dist,patch,offset):
voisin = [[-1,-1],[-1,0],[0,-1],[0,0],[1,-1],[-1,1],[0,1],[1,0],[1,1]]
found = False
bPatch = []
for x,y in voisin:
nPatch = patch.copy()
nPatch[:,0] += x*offset
nPatch[:,1] += y*offset
if np.any(nPatch < 0) or np.any(nPatch[:,0] >= width) or np.any(nPatch[:,1] >= height):
#TODO verrifier que le patch est pas dans le troue si non ff
continue
nPatchValue = getValueFromPatch(nPatch)
filteredPatchValue = applyMask(nPatchValue,zoneMask)
nDist = getDist(filteredZoneValue,filteredPatchValue)
if (nDist < dist):
dist = nDist
bPatch = nPatch
found = True
return found,bPatch,dist
def getBestPatchForZone(zoneValue,zoneMask,patchCoordFound):
filteredZoneValue = applyMask(zoneValue,zoneMask)
patch = getRandomPatch(patchCoordFound)
patchValue = getValueFromPatch(patch)
filteredPatchValue = applyMask(patchValue,zoneMask)
dist = getDist(filteredZoneValue,filteredPatchValue)
offset = 1
while offset < min(width,height)/2:
found, nPatch,nDist = getBestNeigbourPatch(zoneMask,filteredZoneValue,dist,patch,offset)
if (found):
patch = nPatch
dist = nDist
offset = 1
else:
offset*=2
patchCoordFound.append(patch)
return patchValue
def applyPatch(filteredZone,zoneMask, patchValue):
filteredPatchValue = applyMask(patchValue,zoneMask,True)
for i in range(len(filteredZone)) :
img[filteredZone[i][1],filteredZone[i][0]] = filteredPatchValue[i]
def updatePerimiter(filteredZone,perimiter):
for x,y in filteredZone:
if ((x,y) in filteredZone):
perimiter = np.delete(perimiter, np.where((perimiter == [x, y]).all(axis=1))[0], axis=0)
voisin = [[-1,-1],[-1,0],[0,-1],[0,0],[1,-1],[-1,1],[0,1],[1,0],[1,1]]
for x,y in filteredZone:
for offsetx,offsety in voisin:
if img[y+offsety,x+offsetx].sum() == 0:
perimiter = np.vstack((perimiter, [x+offsetx, y+offsety]))
return perimiter
def addEdge(edges,zone):
# pas des deux coté car zone pas filteredZone pour endroit biscornue
x,y = zone[0]
for xx in range(x,x+patchSize):
if x1<=xx<=x2:
if y1<=y<=y2:
edges.append([xx,y])
if y1<=y+patchSize<=y2:
edges.append([xx,y+patchSize])
for yy in range(y,y+patchSize):
if y1<=yy<=y2:
if x1<=x<=x2:
edges.append([x,yy])
if x1<=x+patchSize<=x2:
edges.append([x+patchSize,yy])
return edges
def smoothEdges(edges):
perimiter = initializePermimiter(True)
edges.extend(perimiter.tolist())
edges = np.array(edges)
offsets = np.array([[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]])
for edge in edges:
neighbors = edge + offsets[:,None]
neighbors = neighbors.reshape(-1,2)
valid_neighbors = neighbors[
(neighbors[:,0] >= 0) & (neighbors[:,0] < width) &
(neighbors[:,1] >= 0) & (neighbors[:,1] < height)
]
if len(valid_neighbors) > 0:
neighbor_values = img[valid_neighbors[:,1], valid_neighbors[:,0]]
avg_value = np.mean(neighbor_values, axis=0)
img[edge[1], edge[0]] = avg_value
# for x,y in edges:
# img[y,x] = [255,0,0]
semiPatch = int(patchSize/2)
height, width, _ = img.shape
patchCoordFound = []
edges = []
perimiter = initializePermimiter()
img[y1:y2+1, x1:x2+1] = 0
it = 0
while len(perimiter)> 0:
zone = getRandomPatchFromPerimiter(perimiter)
edges = addEdge(edges,zone)
zoneValue = getValueFromPatch(zone)
zoneMask = getZoneMask(zoneValue,True)
filteredZoneInside = applyMask(zone,zoneMask,True)
patchValue = getBestPatchForZone(zoneValue,zoneMask,patchCoordFound)
applyPatch(filteredZoneInside,zoneMask,patchValue)
perimiter = updatePerimiter(filteredZoneInside,perimiter)
it +=1
print(it)
print("smoothing edges")
smoothEdges(edges)
return img
img = plt.imread('asset/vache.png')
if img.dtype == np.float32:
img = (img * 255).astype(np.uint8)
img = img[:,:,0:3]
def onselect(eclick, erelease):
x1, y1 = eclick.xdata, eclick.ydata
x2, y2 = erelease.xdata, erelease.ydata
print("drawing")
img_copy = np.copy(img)
res = doPatchMatch(img_copy,int(x1),int(y1),int(x2),int(y2))
ax.imshow(res)
plt.draw()
print("drawed")
fig, ax = plt.subplots()
ax.imshow(img)
toggle_selector = RectangleSelector(ax, onselect, useblit=True,
button=[1], minspanx=5, minspany=5, spancoords='pixels',
interactive=True)
plt.axis('off')
plt.show()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

Loading…
Cancel
Save