#!/usr/bin/env python # coding: utf-8 # In[1]: #Tous les codes sont basés sur l'environnement suivant #python 3.7 #opencv 3.1.0 #pytorch 1.4.0 import torch from torch.autograd import Variable import torch.nn as nn import torch.nn.functional as F import cv2 import matplotlib.pyplot as plt import numpy as np import random import math import pickle import random from PIL import Image import sys # In[3]: #Les fonctions dans ce bloc ne sont pas utilisées par le réseau, mais certaines fonctions d'outils def tensor_imshow(im_tensor,cannel): b,c,h,w=im_tensor.shape if c==1: plt.imshow(im_tensor.squeeze().detach().numpy()) else: plt.imshow(im_tensor.squeeze().detach().numpy()[cannel,:]) # Obtenez des données d'entraînement # frag,vt=get_training_fragment(frag_size,image) # frag est un patch carrée de taille (frag_size*frag_size) a partir du image(Son emplacement est aléatoire) # vt est la vérité terrain de la forme Dirac. def get_training_fragment(frag_size,im): h,w,c=im.shape n=random.randint(0,int(h/frag_size)-1) m=random.randint(0,int(w/frag_size)-1) shape=frag_size/4 vt_h=math.ceil((h+1)/shape) vt_w=math.ceil((w+1)/shape) vt=np.zeros([vt_h,vt_w]) vt_h_po=round((vt_h-1)*(n*frag_size/(h-1)+(n+1)*frag_size/(h-1))/2) vt_w_po=round((vt_w-1)*(m*frag_size/(w-1)+(m+1)*frag_size/(w-1))/2) vt[vt_h_po,vt_w_po]=1 vt = np.float32(vt) vt=torch.from_numpy(vt.reshape(1,1,vt_h,vt_w)) return im[n*frag_size:(n+1)*frag_size,m*frag_size:(m+1)*frag_size,:],vt # Cette fonction convertit l'image en variable de type Tensor. # Toutes les données de calcul du réseau sont de type Tensor # Img.shape=[Height,Width,Channel] # Tensor.shape=[Batch,Channel,Height,Width] def img2tensor(im): im=np.array(im,dtype="float32") tensor_cv = torch.from_numpy(np.transpose(im, (2, 0, 1))) im_tensor=tensor_cv.unsqueeze(0) return im_tensor # Trouvez les coordonnées de la valeur maximale dans une carte de corrélation # x,y=show_coordonnee(carte de corrélation) def show_coordonnee(position_pred): map_corre=position_pred.squeeze().detach().numpy() h,w=map_corre.shape max_value=map_corre.max() coordonnee=np.where(map_corre==max_value) return coordonnee[0].mean()/h,coordonnee[1].mean()/w # Filtrer les patchs en fonction du nombre de pixels noirs dans le patch # Si seuls les pixels non noirs sont plus grands qu'une certaine proportion(seuillage), revenez à True, sinon False def test_fragment32_32(frag,seuillage): a=frag[:,:,0]+frag[:,:,1]+frag[:,:,2] mask = (a == 0) arr_new = a[mask] if arr_new.size/a.size<=(1-seuillage): return True else: return False # Ces deux fonctions permettent de sauvegarder le réseau dans un fichier # ou de load le réseau stocké à partir d'un fichier def save_net(file_path,net): pkl_file = open(file_path, 'wb') pickle.dump(net,pkl_file) pkl_file.close() def load_net(file_path): pkl_file = open(file_path, 'rb') net= pickle.load(pkl_file) pkl_file.close() return net # In[4]: # Les fonctions de ce bloc sont utilisées pour construire le réseau # Créer un poids de type DeepMatch comme valeur initiale de Conv1 (non obligatoire) def ini(): kernel=torch.zeros([8,3,3,3]) array_0=np.array([[1,2,1],[0,0,0],[-1,-2,-1]],dtype='float32') array_1=np.array([[2,1,0],[1,0,-1],[0,-1,-2]],dtype='float32') array_2=np.array([[1,0,-1],[2,0,-2],[1,0,-1]],dtype='float32') array_3=np.array([[0,-1,-2],[1,0,-1],[2,1,0]],dtype='float32') array_4=np.array([[-1,-2,-1],[0,0,0],[1,2,1]],dtype='float32') array_5=np.array([[-2,-1,0],[-1,0,1],[0,1,2]],dtype='float32') array_6=np.array([[-1,0,1],[-2,0,2],[-1,0,1]],dtype='float32') array_7=np.array([[0,1,2],[-1,0,1],[-2,-1,0]],dtype='float32') for i in range(3): kernel[0,i,:]=torch.from_numpy(array_0) kernel[1,i,:]=torch.from_numpy(array_1) kernel[2,i,:]=torch.from_numpy(array_2) kernel[3,i,:]=torch.from_numpy(array_3) kernel[4,i,:]=torch.from_numpy(array_4) kernel[5,i,:]=torch.from_numpy(array_5) kernel[6,i,:]=torch.from_numpy(array_6) kernel[7,i,:]=torch.from_numpy(array_7) return torch.nn.Parameter(kernel,requires_grad=True) # Calculer le poids initial de la couche convolutive add # n, m signifie qu'il y a n * m sous-patches dans le patch d'entrée # Par exemple, le patch d'entrée est 16 * 16, pour les patchs 4 * 4 de la première couche, n = 4, m = 4 # pour les patchs 8 * 8 de la deuxième couche, n = 2, m = 2 def kernel_add_ini(n,m): input_canal=int(n*m) output_canal=int(n/2)*int(m/2) for i in range(int(n/2)): for j in range(int(m/2)): kernel_add=np.zeros([1,input_canal],dtype='float32') kernel_add[0,i*2*m+j*2]=1 kernel_add[0,i*2*m+j*2+1]=1 kernel_add[0,(i*2+1)*m+j*2]=1 kernel_add[0,(i*2+1)*m+j*2+1]=1 if i==0 and j==0: add=torch.from_numpy(kernel_add.reshape(1,input_canal,1,1)) else: add_=torch.from_numpy(kernel_add.reshape(1,input_canal,1,1)) add=torch.cat((add,add_),0) return torch.nn.Parameter(add,requires_grad=False) # Calculer le poids initial de la couche convolutive shift # shift+add Peut réaliser l'étape de l'agrégation # Voir ci-dessus pour les paramètres n et m. # Pour des étapes plus détaillées, veuillez consulter mon rapport de stage def kernel_shift_ini(n,m): input_canal=int(n*m) output_canal=int(n*m) kernel_shift=torch.zeros([output_canal,input_canal,3,3]) array_0=np.array([[1,0,0],[0,0,0],[0,0,0]],dtype='float32') array_1=np.array([[0,0,1],[0,0,0],[0,0,0]],dtype='float32') array_2=np.array([[0,0,0],[0,0,0],[1,0,0]],dtype='float32') array_3=np.array([[0,0,0],[0,0,0],[0,0,1]],dtype='float32') kernel_shift_0=torch.from_numpy(array_0) kernel_shift_1=torch.from_numpy(array_1) kernel_shift_2=torch.from_numpy(array_2) kernel_shift_3=torch.from_numpy(array_3) for i in range(n): for j in range(m): if i==0 and j==0: kernel_shift[0,0,:]=kernel_shift_0 else: if i%2==0 and j%2==0: kernel_shift[i*m+j,i*m+j,:]=kernel_shift_0 if i%2==0 and j%2==1: kernel_shift[i*m+j,i*m+j,:]=kernel_shift_1 if i%2==1 and j%2==0: kernel_shift[i*m+j,i*m+j,:]=kernel_shift_2 if i%2==1 and j%2==1: kernel_shift[i*m+j,i*m+j,:]=kernel_shift_3 return torch.nn.Parameter(kernel_shift,requires_grad=False) # Trouvez le petit patch(4 * 4) dans la n ème ligne et la m ème colonne du patch d'entrée # Ceci est utilisé pour calculer la convolution et obtenir la carte de corrélation def get_patch(fragment,psize,n,m): return fragment[:,:,n*psize:(n+1)*psize,m*psize:(m+1)*psize] ################################################################################################################### class Net(nn.Module): def __init__(self,frag_size,psize): super(Net, self).__init__() h_fr=frag_size w_fr=frag_size n=int(h_fr/psize) # n*m patches dans le patch d'entrée m=int(w_fr/psize) self.conv1 = nn.Conv2d(3,8,kernel_size=3,stride=1,padding=1) # Si vous souhaitez initialiser Conv1 avec les poids de DeepMatch, exécutez la ligne suivante # self.conv1.weight=ini() self.Relu = nn.ReLU(inplace=True) self.maxpooling=nn.MaxPool2d(3,stride=2, padding=1) self.shift1=nn.Conv2d(n*m,n*m,kernel_size=3,stride=1,padding=1) self.shift1.weight=kernel_shift_ini(n,m) self.add1 = nn.Conv2d(n*m,int(n/2)*int(m/2),kernel_size=1,stride=1,padding=0) self.add1.weight=kernel_add_ini(n,m) n=int(n/2) m=int(m/2) if n>=2 and m>=2:# Si n=m=1,Notre réseau n'a plus besoin de plus de couches pour agréger les cartes de corrélation self.shift2=nn.Conv2d(n*m,n*m,kernel_size=3,stride=1,padding=1) self.shift2.weight=kernel_shift_ini(n,m) self.add2 = nn.Conv2d(n*m,int(n/2)*int(m/2),kernel_size=1,stride=1,padding=0) self.add2.weight=kernel_add_ini(n,m) n=int(n/2) m=int(m/2) if n>=2 and m>=2: self.shift3=nn.Conv2d(n*m,n*m,kernel_size=3,stride=1,padding=1) self.shift3.weight=kernel_shift_ini(n,m) self.add3 = nn.Conv2d(n*m,int(n/2)*int(m/2),kernel_size=1,stride=1,padding=0) self.add3.weight=kernel_add_ini(n,m) def get_descripteur(self,img,using_cuda): # Utilisez Conv1 pour calculer le descripteur, descripteur_img=self.Relu(self.conv1(img)) b,c,h,w=descripteur_img.shape couche_constante=0.5*torch.ones([1,1,h,w]) if using_cuda: couche_constante=couche_constante.cuda() # Ajouter une couche constante pour éviter la division par 0 lors de la normalisation descripteur_img=torch.cat((descripteur_img,couche_constante),1) # la normalisation descripteur_img_norm=descripteur_img/torch.norm(descripteur_img,dim=1) return descripteur_img_norm def forward(self,img,frag,using_cuda): psize=4 # Utilisez Conv1 pour calculer le descripteur, descripteur_input1=self.get_descripteur(img,using_cuda) descripteur_input2=self.get_descripteur(frag,using_cuda) b,c,h,w=frag.shape n=int(h/psize) m=int(w/psize) ####################################### # Calculer la carte de corrélation par convolution pour les n*m patchs plus petit. for i in range(n): for j in range(m): if i==0 and j==0: map_corre=F.conv2d(descripteur_input1,get_patch(descripteur_input2,psize,i,j),padding=2) else: a=F.conv2d(descripteur_input1,get_patch(descripteur_input2,psize,i,j),padding=2) map_corre=torch.cat((map_corre,a),1) ######################################## # Étape de polymérisation map_corre=self.maxpooling(map_corre) map_corre=self.shift1(map_corre) map_corre=self.add1(map_corre) ######################################### # Répétez l'étape d'agrégation jusqu'à obtenir le graphique de corrélation du patch d'entrée n=int(n/2) m=int(m/2) if n>=2 and m>=2: map_corre=self.maxpooling(map_corre) map_corre=self.shift2(map_corre) map_corre=self.add2(map_corre) n=int(n/2) m=int(m/2) if n>=2 and m>=2: map_corre=self.maxpooling(map_corre) map_corre=self.shift3(map_corre) map_corre=self.add3(map_corre) b,c,h,w=map_corre.shape # Normalisation de la division par maximum map_corre=map_corre/(map_corre.max()) # Normalisation SoftMax #map_corre=(F.softmax(map_corre.reshape(1,1,h*w,1),dim=2)).reshape(b,c,h,w) return map_corre # In[5]: def run_net(net,img,frag,frag_size,using_cuda): h,w,c=frag.shape n=int(h/frag_size) m=int(w/frag_size) frag_list=[] ##################################### # Obtenez des patchs carrés des fragments et mettez-les dans la frag_list for i in range(n): for j in range(m): frag_32=frag[i*frag_size:(i+1)*frag_size,j*frag_size:(j+1)*frag_size] if test_fragment32_32(frag_32,0.6): frag_list.append(frag_32) img_tensor=img2tensor(img) ###################################### if using_cuda: img_tensor=img_tensor.cuda() coordonnee_list=[] ####################################### # Utilisez le réseau pour calculer les positions de tous les patchs dans frag_list[] # Mettez le résultat du calcul dans coordonnee_list[] for i in range(len(frag_list)): frag_tensor=img2tensor(frag_list[i]) if using_cuda: frag_tensor=frag_tensor.cuda() res=net.forward(img_tensor,frag_tensor,using_cuda) if using_cuda: res=res.cpu() po_h,po_w=show_coordonnee(res) coordonnee_list.append([po_h,po_w]) h_img,w_img,c=img.shape position=[] for i in range(len(coordonnee_list)): x=int(round(h_img*coordonnee_list[i][0])) y=int(round(w_img*coordonnee_list[i][1])) position.append([x,y]) return position # In[10]: if __name__=='__main__': # La taille du patch d'entrée est de 16*16 frag_size=16 # La taille du plus petit patch dans réseau est de 4 *4 fixée psize=4 using_cuda=True net=Net(frag_size,psize) # Pour chaque fresque, le nombre d'itérations est de 1000 itera=1000 if using_cuda: net=net.cuda() # Choisissez l'optimiseur et la fonction de coût optimizer = torch.optim.Adam(net.parameters()) loss_func = torch.nn.MSELoss() # Dans le processus d'apprentissage du réseau,le changement d'erreur est placé dans loss_value=[] # et le changement de Conv1 poids est placé dans para_value[] loss_value=[] para_value=[] ####################################################training_net #Les données d'entraînement sont 6 fresques for n in range(6): im_path="./fresque"+str(n)+".ppm" img_training=cv2.imread(im_path) h,w,c=img_training.shape # Si la peinture murale est trop grande, sous-échantillonnez-la et rétrécissez-la while h*w>(1240*900): img_training=cv2.resize(img_training,(int(h/2),int(w/2)),interpolation=cv2.INTER_CUBIC) h,w,c=img_training.shape im_tensor=img2tensor(img_training) if using_cuda: im_tensor=im_tensor.cuda() for i in range(itera): # Tous les 100 cycles, enregistrez le changement de poids if i%100==0: para=net.conv1.weight para=para.detach().cpu() para_value.append(para) frag,vt=get_training_fragment(frag_size,img_training) frag_tensor=img2tensor(frag) if using_cuda: vt=vt.cuda() frag_tensor=frag_tensor.cuda() # Utilisez des patchs et des fresques de données d'entraînement pour faire fonctionner le réseau frag_pred=net.forward(im_tensor,frag_tensor,using_cuda) b,c,h,w=vt.shape # Utilisez la fonction de coût pour calculer l'erreur err_=loss_func(vt,frag_pred) # Utilisez l'optimiseur pour ajuster le poids de Conv1 optimizer.zero_grad() err_.backward(retain_graph=True) optimizer.step() loss_value.append(err_.tolist()) del frag_tensor,frag_pred,err_,vt torch.cuda.empty_cache() # In[7]: len(loss_value) # In[11]: plt.plot(loss_value) # In[12]: file_path="./net_trainned6000" save_net(file_path,net)