Compare commits
8 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
e29bf957cf | 4 months ago |
![]() |
dfeb21d5a4 | 4 months ago |
![]() |
47e1fe1f1e | 4 months ago |
![]() |
3b26c00564 | 4 months ago |
![]() |
b644c96c1a | 5 months ago |
![]() |
2ebd56523f | 5 months ago |
![]() |
d83e36d227 | 5 months ago |
![]() |
abdfaf7304 | 5 months ago |
@ -1,62 +1,6 @@
|
||||
# Patch Match by Ludovic CASTIGLIA
|
||||
# patchMatch
|
||||
|
||||
## Ce qui est fourni dans le projet:
|
||||
Cette branche a pour seul but de versioner et sauvgarder des experimentations.
|
||||
Elle ne rejoindra jamais la branche ```master```.
|
||||
|
||||
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
|
||||
C'est pour ça que contrairement aux autres branche, elle n'est pas supprimé.
|
Before Width: | Height: | Size: 90 KiB |
Before Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 267 KiB |
Before Width: | Height: | Size: 467 KiB |
@ -0,0 +1,210 @@
|
||||
from matplotlib.widgets import RectangleSelector
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
|
||||
|
||||
def doPatchMatch(img,x1,y1,x2,y2,patchSize=17,nbRadomPatch=10):
|
||||
|
||||
def getPatchFromCoord(x,y):
|
||||
patch = np.array([[i, j] for i in range(patchSize) for j in range(patchSize)])
|
||||
patch[:,0] = patch[:,0] + x
|
||||
patch[:,1] = patch[:,1] + y
|
||||
return patch
|
||||
|
||||
def distance(patchValue1,patchValue2):
|
||||
mask = np.all(patchValue1 == [-1, -1, -1, -1], axis=-1)
|
||||
return np.sum((patchValue1[~mask] - patchValue2[~mask]) ** 2)
|
||||
|
||||
def getBestNeigbourPatch(xy,ogValue,ogDist,step):
|
||||
x, y = xy
|
||||
|
||||
dist = -1
|
||||
|
||||
xt, yt = x+step, y
|
||||
if (0 <= xt <= width - patchSize and 0 <= yt <= height - patchSize):
|
||||
patch = getPatchFromCoord(xt,yt)
|
||||
patchValue = patchToValue(patch)
|
||||
dist = distance(ogValue,patchValue)
|
||||
|
||||
xt, yt = x-step, y
|
||||
if (0 <= xt <= width - patchSize and 0 <= yt <= height - patchSize):
|
||||
tpatch = getPatchFromCoord(xt,yt)
|
||||
tpatchValue = patchToValue(tpatch)
|
||||
tdist = distance(ogValue,tpatchValue)
|
||||
if tdist < dist or dist == -1:
|
||||
dist = tdist
|
||||
patch = tpatch
|
||||
patchValue = tpatchValue
|
||||
|
||||
xt, yt = x, y+step
|
||||
if (0 <= xt <= width - patchSize and 0 <= yt <= height - patchSize):
|
||||
tpatch = getPatchFromCoord(xt,yt)
|
||||
tpatchValue = patchToValue(tpatch)
|
||||
tdist = distance(ogValue,tpatchValue)
|
||||
if tdist < dist or dist == -1:
|
||||
dist = tdist
|
||||
patch = tpatch
|
||||
patchValue = tpatchValue
|
||||
|
||||
xt, yt = x, y-step
|
||||
if (0 <= xt <= width - patchSize and 0 <= yt <= height - patchSize):
|
||||
tpatch = getPatchFromCoord(xt,yt)
|
||||
tpatchValue = patchToValue(tpatch)
|
||||
tdist = distance(ogValue,tpatchValue)
|
||||
if tdist < dist or dist == -1:
|
||||
dist = tdist
|
||||
patch = tpatch
|
||||
patchValue = tpatchValue
|
||||
if dist == -1:
|
||||
return False, None, None, None
|
||||
return dist < ogDist, patch, patchValue, dist
|
||||
|
||||
def getTheBestPatch(addr,ogValue):
|
||||
patchs = []
|
||||
patchsValue = []
|
||||
dists = []
|
||||
for i in range(nbRadomPatch):
|
||||
x,y = getRandomPatch()
|
||||
patch = getPatchFromCoord(x,y)
|
||||
patchValue = patchToValue(patch)
|
||||
dist = distance(ogValue,patchValue)
|
||||
patchs.append(patch)
|
||||
patchsValue.append(patchValue)
|
||||
dists.append(dist)
|
||||
|
||||
minIdx = np.argmin(np.array(dist))
|
||||
patch = patchs[minIdx]
|
||||
patchValue = patchsValue[minIdx]
|
||||
|
||||
ogDist = dists[minIdx]
|
||||
foundNew = True
|
||||
step = 5
|
||||
addr = addr[0]
|
||||
while foundNew:
|
||||
foundNew, tpatch, tpatchValue, tdist = getBestNeigbourPatch(addr,ogValue,ogDist,step)
|
||||
if (foundNew):
|
||||
addr = tpatch[patchSize//2]
|
||||
patch = tpatch
|
||||
patchValue = tpatchValue
|
||||
ogDist = tdist
|
||||
step = 5
|
||||
else:
|
||||
step = step*1.25
|
||||
foundNew = step < min(width, height)/2
|
||||
return patch, patchValue
|
||||
|
||||
|
||||
def patchToValue(patch):
|
||||
return img[patch[0][1]:patch[len(patch)-1][1], patch[0][0]:patch[len(patch)-1][0]]
|
||||
|
||||
def getRandomPatch():
|
||||
rx = np.random.randint(0, width - patchSize)
|
||||
ry = np.random.randint(0, height - patchSize)
|
||||
return rx, ry
|
||||
|
||||
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] = -1
|
||||
return np.array(perimeter)
|
||||
|
||||
def removeAndAddFromPerimiter(perimiter, addr):
|
||||
p= []
|
||||
npAddr = np.array(addr)
|
||||
for coord in perimiter:
|
||||
if not np.any(np.all(npAddr == np.array(coord), axis=1)):
|
||||
p.append(coord)
|
||||
|
||||
perimiter = p
|
||||
p1 = patchSize+2
|
||||
for dx in range(-1, p1):
|
||||
for dy in range(-1, p1):
|
||||
if (dx!=-1 and dx!=p1-1 and dy != -1 and dy != p1-1):
|
||||
continue
|
||||
nx, ny = addr[0,0] + dx, addr[0,1] + dy
|
||||
if 0 <= nx < width and 0 <= ny < height and img[ny, nx][0] == -1:
|
||||
if len(perimiter) == 0:
|
||||
perimiter.append([nx, ny])
|
||||
continue
|
||||
if not np.any(np.all(perimiter == np.array([int(nx), int(ny)]), axis=1)):
|
||||
perimiter.append([nx, ny])
|
||||
return perimiter
|
||||
|
||||
def applyPatch(patch,addr):
|
||||
for i in range(len(addr)):
|
||||
if img[addr[i, 1], addr[i, 0]][0] == -1:
|
||||
img[addr[i, 1], addr[i, 0]] = img[patch[i, 1],patch[i, 0]]
|
||||
|
||||
def getRandomFromPerimiter(perimiter):
|
||||
return perimiter[np.random.randint(len(perimiter))]
|
||||
|
||||
def loop(perimiter):
|
||||
x,y = getRandomFromPerimiter(perimiter)
|
||||
addr = getPatchFromCoord(x,y)
|
||||
ogValue = patchToValue(addr)
|
||||
patch,patchValue = getTheBestPatch(addr,ogValue)
|
||||
applyPatch(patch,addr)
|
||||
perimiter = removeAndAddFromPerimiter(perimiter,addr)
|
||||
return perimiter
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
semiPatch = int(patchSize/2)
|
||||
height, width, _ = img.shape
|
||||
|
||||
|
||||
|
||||
perimiter = initializePermimiter()
|
||||
it = 0
|
||||
|
||||
# perimiter = loop(perimiter)
|
||||
# perimiter = loop(perimiter)
|
||||
# perimiter = loop(perimiter)
|
||||
# for coord in perimiter:
|
||||
# img[coord[1], coord[0]] = [1,1,1,1]
|
||||
# img[img == -1] = 0
|
||||
|
||||
while len(perimiter)> 0:
|
||||
it += 1
|
||||
perimiter = loop(perimiter)
|
||||
if (it == 1000):
|
||||
it = 0
|
||||
print(len(perimiter))
|
||||
|
||||
return img
|
||||
|
||||
|
||||
|
||||
|
||||
img = plt.imread('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
|
||||
|
||||
print("drawing")
|
||||
img_copy = np.copy(img)
|
||||
res = doPatchMatch(img_copy,int(x1),int(y1),int(x2),int(y2),33)
|
||||
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()
|
Before Width: | Height: | Size: 533 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 532 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 2.0 MiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 2.0 MiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 352 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.1 MiB |
Before Width: | Height: | Size: 5.3 KiB |
@ -0,0 +1,133 @@
|
||||
# j'ai trouvé ce code sur la page github:
|
||||
# https://github.com/MingtaoGuo/PatchMatch/blob/master/PatchMatch.py
|
||||
# pour experimenter et comprendre comment l'algoritme marche
|
||||
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
import time
|
||||
|
||||
def cal_distance(a, b, A_padding, B, p_size):
|
||||
p = p_size // 2
|
||||
patch_a = A_padding[a[0]:a[0]+p_size, a[1]:a[1]+p_size, :]
|
||||
patch_b = B[b[0]-p:b[0]+p+1, b[1]-p:b[1]+p+1, :]
|
||||
temp = patch_b - patch_a
|
||||
num = np.sum(1 - np.int32(np.isnan(temp)))
|
||||
dist = np.sum(np.square(np.nan_to_num(temp))) / num
|
||||
return dist
|
||||
|
||||
def reconstruction(f, A, B):
|
||||
A_h = np.size(A, 0)
|
||||
A_w = np.size(A, 1)
|
||||
temp = np.zeros_like(A)
|
||||
for i in range(A_h):
|
||||
for j in range(A_w):
|
||||
temp[i, j, :] = B[f[i, j][0], f[i, j][1], :]
|
||||
Image.fromarray(temp).show()
|
||||
|
||||
|
||||
def initialization(A, B, p_size):
|
||||
A_h = np.size(A, 0)
|
||||
A_w = np.size(A, 1)
|
||||
B_h = np.size(B, 0)
|
||||
B_w = np.size(B, 1)
|
||||
p = p_size // 2
|
||||
random_B_r = np.random.randint(p, B_h-p, [A_h, A_w])
|
||||
random_B_c = np.random.randint(p, B_w-p, [A_h, A_w])
|
||||
A_padding = np.ones([A_h+p*2, A_w+p*2, 3]) * np.nan
|
||||
A_padding[p:A_h+p, p:A_w+p, :] = A
|
||||
f = np.zeros([A_h, A_w], dtype=object)
|
||||
dist = np.zeros([A_h, A_w])
|
||||
for i in range(A_h):
|
||||
for j in range(A_w):
|
||||
a = np.array([i, j])
|
||||
b = np.array([random_B_r[i, j], random_B_c[i, j]], dtype=np.int32)
|
||||
f[i, j] = b
|
||||
dist[i, j] = cal_distance(a, b, A_padding, B, p_size)
|
||||
return f, dist, A_padding
|
||||
|
||||
def propagation(f, a, dist, A_padding, B, p_size, is_odd):
|
||||
A_h = np.size(A_padding, 0) - p_size + 1
|
||||
A_w = np.size(A_padding, 1) - p_size + 1
|
||||
x = a[0]
|
||||
y = a[1]
|
||||
if is_odd:
|
||||
d_left = dist[max(x-1, 0), y]
|
||||
d_up = dist[x, max(y-1, 0)]
|
||||
d_current = dist[x, y]
|
||||
idx = np.argmin(np.array([d_current, d_left, d_up]))
|
||||
if idx == 1:
|
||||
f[x, y] = f[max(x - 1, 0), y]
|
||||
dist[x, y] = cal_distance(a, f[x, y], A_padding, B, p_size)
|
||||
if idx == 2:
|
||||
f[x, y] = f[x, max(y - 1, 0)]
|
||||
dist[x, y] = cal_distance(a, f[x, y], A_padding, B, p_size)
|
||||
else:
|
||||
d_right = dist[min(x + 1, A_h-1), y]
|
||||
d_down = dist[x, min(y + 1, A_w-1)]
|
||||
d_current = dist[x, y]
|
||||
idx = np.argmin(np.array([d_current, d_right, d_down]))
|
||||
if idx == 1:
|
||||
f[x, y] = f[min(x + 1, A_h-1), y]
|
||||
dist[x, y] = cal_distance(a, f[x, y], A_padding, B, p_size)
|
||||
if idx == 2:
|
||||
f[x, y] = f[x, min(y + 1, A_w-1)]
|
||||
dist[x, y] = cal_distance(a, f[x, y], A_padding, B, p_size)
|
||||
|
||||
def random_search(f, a, dist, A_padding, B, p_size, alpha=0.5):
|
||||
x = a[0]
|
||||
y = a[1]
|
||||
B_h = np.size(B, 0)
|
||||
B_w = np.size(B, 1)
|
||||
p = p_size // 2
|
||||
i = 4
|
||||
search_h = B_h * alpha ** i
|
||||
search_w = B_w * alpha ** i
|
||||
b_x = f[x, y][0]
|
||||
b_y = f[x, y][1]
|
||||
while search_h > 1 and search_w > 1:
|
||||
search_min_r = max(b_x - search_h, p)
|
||||
search_max_r = min(b_x + search_h, B_h-p)
|
||||
random_b_x = np.random.randint(search_min_r, search_max_r)
|
||||
search_min_c = max(b_y - search_w, p)
|
||||
search_max_c = min(b_y + search_w, B_w - p)
|
||||
random_b_y = np.random.randint(search_min_c, search_max_c)
|
||||
search_h = B_h * alpha ** i
|
||||
search_w = B_w * alpha ** i
|
||||
b = np.array([random_b_x, random_b_y])
|
||||
d = cal_distance(a, b, A_padding, B, p_size)
|
||||
if d < dist[x, y]:
|
||||
dist[x, y] = d
|
||||
f[x, y] = b
|
||||
i += 1
|
||||
|
||||
def NNS(img, ref, p_size, itr):
|
||||
A_h = np.size(img, 0)
|
||||
A_w = np.size(img, 1)
|
||||
f, dist, img_padding = initialization(img, ref, p_size)
|
||||
for itr in range(1, itr+1):
|
||||
if itr % 2 == 0:
|
||||
for i in range(A_h - 1, -1, -1):
|
||||
for j in range(A_w - 1, -1, -1):
|
||||
a = np.array([i, j])
|
||||
propagation(f, a, dist, img_padding, ref, p_size, False)
|
||||
random_search(f, a, dist, img_padding, ref, p_size)
|
||||
else:
|
||||
for i in range(A_h):
|
||||
for j in range(A_w):
|
||||
a = np.array([i, j])
|
||||
propagation(f, a, dist, img_padding, ref, p_size, True)
|
||||
random_search(f, a, dist, img_padding, ref, p_size)
|
||||
print("iteration: %d"%(itr))
|
||||
return f
|
||||
|
||||
if __name__ == "__main__":
|
||||
img = np.array(Image.open("./cup_a.jpg"))
|
||||
ref = np.array(Image.open("./cup_b.jpg"))
|
||||
p_size = 3
|
||||
itr = 5
|
||||
start = time.time()
|
||||
f = NNS(img, ref, p_size, itr)
|
||||
end = time.time()
|
||||
print(end - start)
|
||||
reconstruction(f, img, ref)
|
@ -1,184 +0,0 @@
|
||||
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
|
@ -1,114 +0,0 @@
|
||||
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)
|
@ -1,85 +0,0 @@
|
||||
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")
|
@ -1,80 +0,0 @@
|
||||
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,78 @@
|
||||
from matplotlib.widgets import RectangleSelector
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
|
||||
|
||||
|
||||
def patch_match(img, patch_size=3, iterations=1):
|
||||
height, width, _ = img.shape
|
||||
offsets = np.zeros((height, width, 2), dtype=np.int32)
|
||||
|
||||
def random_offsets():
|
||||
return np.random.randint(-patch_size, patch_size + 1, size=(height, width, 2))
|
||||
|
||||
def distance(patch1, patch2):
|
||||
return np.sum((patch1 - patch2) ** 2)
|
||||
|
||||
def get_patch(x, y):
|
||||
return img[max(0, y):min(height, max(0, y) + patch_size), max(0, x):min(width, max(0, x) + patch_size)]
|
||||
|
||||
offsets = random_offsets()
|
||||
|
||||
for _ in range(iterations):
|
||||
for y in range(patch_size,height-patch_size*2):
|
||||
for x in range(patch_size,width-patch_size*2):
|
||||
best_offset = offsets[y, x]
|
||||
best_distance = distance(get_patch(x, y), get_patch(x + best_offset[0], y + best_offset[1]))
|
||||
for dy in range(-1, 2):
|
||||
for dx in range(-1, 2):
|
||||
if dx == 0 and dy == 0:
|
||||
continue
|
||||
new_offset = best_offset + [dx, dy]
|
||||
if (new_offset[0]+x>width-patch_size or new_offset[1]+y>height-patch_size):
|
||||
continue
|
||||
new_distance = distance(get_patch(x, y), get_patch(x + new_offset[0], y + new_offset[1]))
|
||||
if new_distance < best_distance:
|
||||
best_distance = new_distance
|
||||
best_offset = new_offset
|
||||
offsets[y, x] = best_offset
|
||||
|
||||
result = np.zeros_like(img)
|
||||
for y in range(height):
|
||||
for x in range(width):
|
||||
offset = offsets[y, x]
|
||||
result[y, x] = img[(y + offset[1]) % height, (x + offset[0]) % width]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Load the image using matplotlib
|
||||
img = plt.imread('boat.png')
|
||||
|
||||
|
||||
def onselect(eclick, erelease):
|
||||
x1, y1 = eclick.xdata, eclick.ydata
|
||||
x2 = x1 + 150
|
||||
x2, y2 = erelease.xdata, erelease.ydata
|
||||
|
||||
avg_color = np.mean(img, axis=(0, 1))
|
||||
img_copy = np.copy(img)
|
||||
img_copy[int(y1):int(y2), int(x1):int(x2)] = avg_color
|
||||
res = patch_match(img_copy)
|
||||
ax.imshow(res)
|
||||
plt.draw()
|
||||
print("drawed")
|
||||
#ax.imshow(img_copy)
|
||||
#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()
|
@ -0,0 +1,101 @@
|
||||
from matplotlib.widgets import RectangleSelector
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
|
||||
def patchMatch(img,x1,y1,x2,y2,patchSize=20, iterations=1000):
|
||||
height, width, _ = img.shape
|
||||
xx1 = max(0, x1-patchSize)
|
||||
yy1 = max(0, y1-patchSize)
|
||||
xx2 = min(width, x2+patchSize)
|
||||
yy2 = min(height, y2+patchSize)
|
||||
|
||||
mask = np.ones((height, width), dtype=bool)
|
||||
mask[yy1:yy2, xx1:xx2] = False
|
||||
mask[y1:y2, x1:x2] = True
|
||||
img[int(y1):int(y2), int(x1):int(x2)] = np.mean(img[~mask], axis=(0, 1))
|
||||
img_copy = np.copy(img)
|
||||
|
||||
div = 10
|
||||
|
||||
if (xx2-xx1 < patchSize or yy2-yy1 < patchSize):
|
||||
return img
|
||||
|
||||
def getRandomPatch():
|
||||
rx = np.random.randint(0, width - patchSize)
|
||||
ry = np.random.randint(0, height - patchSize)
|
||||
return rx, ry
|
||||
|
||||
def fusion(patch1, patch2):
|
||||
return (patch1*2+patch2) / 3
|
||||
|
||||
def distance(patch1, patch2):
|
||||
return np.sum((patch1 - patch2) ** 2)
|
||||
|
||||
def gradientDescent(x, y, bestPatch, bestDistance):
|
||||
neighbors = [(-div,0), (div,0), (0,-div), (0,div), (-div,-div), (-div,div), (div,-div), (div,div)]
|
||||
patch = img[y:y + patchSize, x:x + patchSize]
|
||||
hasChanged = True
|
||||
while hasChanged:
|
||||
hasChanged = False
|
||||
for nx, ny in neighbors:
|
||||
cx = bestPatch[0] + nx
|
||||
cy = bestPatch[1] + ny
|
||||
if cx < 0 or cy < 0 or cx >= width - patchSize or cy >= height - patchSize:
|
||||
continue
|
||||
|
||||
neighborPatch = img[cy:cy + patchSize, cx:cx + patchSize]
|
||||
neighborDist = distance(patch, neighborPatch)
|
||||
if neighborDist < bestDistance:
|
||||
hasChanged = True
|
||||
bestPatch = [cx, cy]
|
||||
bestDistance = neighborDist
|
||||
return bestPatch, bestDistance
|
||||
|
||||
|
||||
for x in range(xx1,xx2-patchSize,int(patchSize/div)):
|
||||
for y in range(yy1,yy2-patchSize,int(patchSize/div)):
|
||||
px, py = getRandomPatch()
|
||||
bestPatch = [px, py]
|
||||
bestDistance = distance(img[y:y+patchSize,x:x+patchSize], img[py:py+patchSize,px:px+patchSize])
|
||||
firstDistance = np.copy(bestDistance)
|
||||
for _ in range(iterations):
|
||||
px, py = getRandomPatch()
|
||||
currentDistance = distance(img[y:y+patchSize,x:x+patchSize], img[py:py+patchSize,px:px+patchSize])
|
||||
if currentDistance > firstDistance:
|
||||
continue
|
||||
currentFirstDistance = np.copy(currentDistance)
|
||||
currentPatch, bestDistance = gradientDescent(x, y, bestPatch, bestDistance)
|
||||
if currentDistance < bestDistance:
|
||||
firstDistance = currentFirstDistance
|
||||
bestPatch = currentPatch
|
||||
bestDistance = currentDistance
|
||||
img_copy[y:y+patchSize, x:x+patchSize] = fusion(img_copy[y:y+patchSize, x:x+patchSize],img[bestPatch[1]:bestPatch[1]+patchSize, bestPatch[0]:bestPatch[0]+patchSize])
|
||||
img[y1:y2,x1:x2] = img_copy[y1:y2,x1:x2]
|
||||
return img
|
||||
|
||||
|
||||
# Load the image using matplotlib
|
||||
img = plt.imread('simson.png')
|
||||
|
||||
if len(img.shape) == 2:
|
||||
img = np.stack((img,)*3, axis=-1)
|
||||
|
||||
def onselect(eclick, erelease):
|
||||
x1, y1 = eclick.xdata, eclick.ydata
|
||||
x2 = x1 + 150
|
||||
x2, y2 = erelease.xdata, erelease.ydata
|
||||
|
||||
img_copy = np.copy(img)
|
||||
res = patchMatch(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()
|
@ -1,74 +0,0 @@
|
||||
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)
|
After Width: | Height: | Size: 33 KiB |
@ -1,164 +0,0 @@
|
||||
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
|
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 1.3 MiB |
@ -1,156 +0,0 @@
|
||||
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()
|
Before Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 70 KiB |
Before Width: | Height: | Size: 1.4 MiB |
@ -1,166 +0,0 @@
|
||||
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()
|
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 1.3 MiB |
@ -1,196 +0,0 @@
|
||||
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()
|
Before Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 1.4 MiB |