425 lines
15 KiB
Python
425 lines
15 KiB
Python
|
#!/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)
|
|||
|
|