Ceci est un article pour trouver la matrice affine dans la correspondance des points caractéristiques. Dans cet article, nous déciderons des points caractéristiques manuellement. Le but est de trouver la matrice affine A qui convertit image1 en image2 avec les points caractéristiques des deux images connues comme ceci.
Si vous pouvez trouver la matrice Affin à partir des points caractéristiques, vous pouvez superposer les images comme ceci.
Calcul matriciel pour transformer chaque coordonnée de l'image
\left(
\begin{array}{c}
x^{'}\\
y^{'}\\
1
\end{array}
\right)
=
\left(
\begin{array}{ccc}
a & b & t_{x}\\
c & d & t_{y}\\
0 & 0 & 1
\end{array}
\right)
\left(
\begin{array}{c}
x\\
y\\
1
\end{array}
\right)
de
A=
\left(
\begin{array}{ccc}
a & b & t_{x}\\
c & d & t_{y}\\
0 & 0 & 1
\end{array}
\right)
La partie de est la matrice affine. Un excellent qui peut représenter la rotation, l'agrandissement, la réduction, le mouvement, l'inversion et le cisaillement d'une image avec cette matrice seule! !!
Pour la conversion affine, je me suis référé à l'article suivant. Comprendre parfaitement la conversion Affin Conversion d'affine par matrice (agrandissement / réduction / rotation / cisaillement / mouvement) -Réinventeur du traitement d'image Python-
Lorsqu'il y a $ N (N \ geqq3) $ points d'entités dans deux images, les coordonnées des points d'entités dans l'image avant la conversion sont calculées.
\left(
\begin{array}{c}
x_{n}\\
y_{n}
\end{array}
\right)
Coordonnées après retour
\left(
\begin{array}{c}
x^{'}_{n}\\
y^{'}_{n}
\end{array}
\right)
En tant qu'expression matricielle qui effectue une transformation affine sur toutes les coordonnées $ N $
\left(
\begin{array}{c}
x^{'}_{1}&x^{'}_{2}&\cdots&x^{'}_{N}\\
y^{'}_{1}&x^{'}_{2}&\cdots&x^{'}_{N}\\
1&1&\cdots&1
\end{array}
\right)
=
\left(
\begin{array}{ccc}
a & b & t_{x}\\
c & d & t_{y}\\
0 & 0 & 1
\end{array}
\right)
\left(
\begin{array}{c}
x_{1}&x_{2}&\cdots&x_{N}\\
y_{1}&x_{2}&\cdots&x_{N}\\
1&1&\cdots&1
\end{array}
\right)
Il peut être représenté par. Le but est de trouver ceci $ a, b, c, d, t_ {x}, t_ {y} $. Voici un ensemble de coordonnées avant et après la conversion
\left(
\begin{array}{c}
x_{n}\\
y_{n}
\end{array}
\right)
,
\left(
\begin{array}{c}
x^{'}_{n}\\
y^{'}_{n}
\end{array}
\right)
Conversion d'affine en
\left(
\begin{array}{c}
x^{'}_{n}\\
y^{'}_{n}\\
1
\end{array}
\right)
=
\left(
\begin{array}{ccc}
a & b & t_{x}\\
c & d & t_{y}\\
0 & 0 & 1
\end{array}
\right)
\left(
\begin{array}{c}
x_{n}\\
y_{n}\\
1
\end{array}
\right)
Lorsque vous développez
\begin{align}
x^{'}_{n}&=ax_{n} + by_{n} + t_{x}\\
y^{'}_{n}&=cx_{n} + dy_{n} + t_{y}
\end{align}
Sera.
w_1=
\left(
\begin{array}{c}
a\\
b\\
t_{x}
\end{array}
\right)
,
w_2=
\left(
\begin{array}{c}
c\\
d\\
t_{y}
\end{array}
\right)
,
p_{n}=
\left(
\begin{array}{c}
x_{n} & y_{n} & 1
\end{array}
\right)
,
p^{'}_{n}=
\left(
\begin{array}{c}
x^{'}_{n} & y^{'}_{n} & 1
\end{array}
\right)
Si vous préparez un vecteur comme
\begin{align}
x^{'}_{n}&=p_{n}w_1\\
y^{'}_{n}&=p_{n}w_2
\end{align}
Peut être écrit. La distance entre les coordonnées de destination de la conversion et les coordonnées après retour par conversion Affin est utilisée comme fonction d'erreur pour obtenir $ w_1 $ et $ w_2 $ lorsque la fonction d'erreur est la plus petite. Fonction d'erreur $ E $
E=\sum_{n=1}^{N}
\Bigl(
(x^{'}_{n} - p_{n}w_1)^{2} + (y^{'}_{n} - p_{n}w_2)^{2}
\Bigr)
Pour définir ceci pour représenter cela au format matriciel
X^{'}=
\left(
\begin{array}{c}
x^{'}_{1}\\
\vdots\\
x^{'}_{N}
\end{array}
\right)
,
Y^{'}=
\left(
\begin{array}{c}
y^{'}_{1}\\
\vdots\\
y^{'}_{N}
\end{array}
\right)
,
P=
\left(
\begin{array}{c}
p_{1}\\
\vdots\\
p_{N}
\end{array}
\right)
=
\left(
\begin{array}{c}
x_{1} & y_{2} & 1\\
&\vdots&\\
x_{N} & y_{N} & 1
\end{array}
\right)
étant donné que
E=
(X^{'} - Pw_1)^{T}(X^{'} - Pw_1) + (Y^{'} - Pw_2)^T(Y^{'} - Pw_2)
Peut être écrit. Une fois déplié
\begin{align}
E&=({X^{'}}^{T} - (Pw_1)^{T})(X^{'} - Pw_1) + ({Y^{'}}^{T} - (Pw_2)^{T})(Y^{'}-Pw_2)\\
&={X^{'}}^{T}X^{'} - {X^{'}}^{T}Pw_1 - (Pw_1)^{T}X^{'} + (Pw_1)^{T}(Pw_1) + {Y^{'}}^{T}Y^{'} - {Y^{'}}^{T}Pw_2 - (Pw_2)^{T}Y^{'} + (Pw_2)^{T}(Pw_2)\\
&={X^{'}}^{T}X^{'} - w^{T}_{1}P^{T}{X^{'}}^{T} - w^{T}_{1}P^{T}{X^{'}}^{T} + w^{T}_{1}P^{T}Pw_{1} + {Y^{'}}^{T}Y^{'} - w^{T}_{2}P^{T}{Y^{'}}^{T} - w^{T}_{2}P^{T}{Y^{'}}^{T} + w^{T}_{2}P^{T}Pw_{2}\\
&={X^{'}}^{T}X^{'} - 2w^{T}_{1}P^{T}{X^{'}}^{T} + w^{T}_{1}P^{T}Pw_{1} + {Y^{'}}^{T}Y^{'} - 2w^{T}_{2}P^{T}{Y^{'}}^{T} + w^{T}_{2}P^{T}Pw_{2}\\
\end{align}
Ce sera. Si vous trouvez le moment où $ E $ devient plus petit par différenciation partielle avec $ w_1 $ et $ w_2 $
\begin{align}
\frac{\partial E}{\partial w_{1}}=-2P^{T}X^{'} + 2P^{T}Pw_{1}&=0\\
2P^{T}w_{1}&=2P^{T}X^{'}\\
w_{1}&=(P^{T}P)^{-1}P^{T}X^{'}\\
\frac{\partial E}{\partial w_{2}}=-2P^{T}Y^{'} + 2P^{T}Pw_{2}&=0\\
2P^{T}w_{2}&=2P^{T}Y^{'}\\
w_{2}&=(P^{T}P)^{-1}P^{T}Y^{'}
\end{align}
En conséquence, $ w_1 $ et $ w_2 $ ont été obtenus, donc la matrice affine a été obtenue.
Implémentons-le en Python en utilisant uniquement numpy.
import numpy as np
import math
from PIL import Image
from matplotlib import pyplot as plt
#Une fonction qui fait référence à la fin du tableau pour ceux qui dépassent la plage de l'image de référence
def clip_xy(ref_xy, img_shape):
#Remplacer pour la coordonnée x
ref_x = np.where((0 <= ref_xy[:, 0]) & (ref_xy[:, 0] < img_shape[1]), ref_xy[:, 0], -1)
#Remplacer à propos de la coordonnée y
ref_y = np.where((0 <= ref_xy[:, 1]) & (ref_xy[:, 1] < img_shape[0]), ref_xy[:, 1], -1)
#Combinez et retournez
return np.vstack([ref_x, ref_y]).T
#Conversion d'affine
def affine(data, affine, draw_area_size):
# data:Données d'image à convertir en affine
# affine:Matrice d'affine
#:draw_area_size:Elle peut être identique ou meilleure que la forme des données
#Matrice inverse de la matrice Affin
inv_affine = np.linalg.inv(affine)
x = np.arange(0, draw_area_size[1], 1)
y = np.arange(0, draw_area_size[0], 1)
X, Y = np.meshgrid(x, y)
XY = np.dstack([X, Y, np.ones_like(X)])
xy = XY.reshape(-1, 3).T
#Calcul des coordonnées de référence
ref_xy = inv_affine @ xy
ref_xy = ref_xy.T
#Coordonnées autour des coordonnées de référence
liner_xy = {}
liner_xy['downleft'] = ref_xy[:, :2].astype(int)
liner_xy['upleft'] = liner_xy['downleft'] + [1, 0]
liner_xy['downright'] = liner_xy['downleft'] + [0, 1]
liner_xy['upright'] = liner_xy['downleft'] + [1, 1]
#Calcul de poids avec interpolation linéaire
liner_diff = ref_xy[:, :2] - liner_xy['downleft']
liner_weight = {}
liner_weight['downleft'] = (1 - liner_diff[:, 0]) * (1 - liner_diff[:, 1])
liner_weight['upleft'] = (1 - liner_diff[:, 0]) * liner_diff[:, 1]
liner_weight['downright'] = liner_diff[:, 0] * (1 - liner_diff[:, 1])
liner_weight['upright'] = liner_diff[:, 0] * liner_diff[:, 1]
#Poids et ajouter
liner_with_weight = {}
for direction in liner_weight.keys():
l_xy = liner_xy[direction]
l_xy = clip_xy(l_xy, data.shape)
l_xy = np.dstack([l_xy[:, 0].reshape(draw_area_size), l_xy[:, 1].reshape(draw_area_size)])
l_weight = liner_weight[direction].reshape(draw_area_size)
liner_with_weight[direction] = data[l_xy[:, :, 1], l_xy[:, :, 0]] * l_weight
data_linear = sum(liner_with_weight.values())
return data_linear
#Fonction pour trouver la matrice affine à partir des points caractéristiques
def registration(P, x_dash, y_dash):
w1 = np.linalg.inv(P.T @ P) @ P.T @ x_dash
w2 = np.linalg.inv(P.T @ P) @ P.T @ y_dash
affine_matrix = np.array([[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0]])
affine_matrix[0, :] = w1
affine_matrix[1, :] = w2
print(affine_matrix)
return affine_matrix
#Point d'objet cliqué Enregistrer le tableau
future_points1 = np.array([[1, 1]])
future_points2 = np.array([[1, 1]])
count_fp1 = 0
count_fp2 = 0
#Cliquez pour déterminer les points caractéristiques
def onclick(event):
global future_points1
global future_points2
global count_fp1
global count_fp2
click_axes = event.inaxes
x = math.floor(event.xdata)
y = math.floor(event.ydata)
if click_axes == ax1:
if count_fp1 == 0:
future_points1[0, :] = (x, y)
count_fp1 = 1
else:
future_points1 = np.vstack([future_points1, np.array([x, y])])
count_fp1 += count_fp1
print(future_points1)
if click_axes == ax2:
if count_fp2 == 0:
future_points2[0, :] = (x, y)
count_fp2 = 1
else:
future_points2 = np.vstack([future_points2, np.array([x, y])])
count_fp2 += count_fp2
print(future_points2)
click_axes.scatter(x, y)
fig.canvas.draw_idle()
#Entrez la superposition masculine et d'image
def onEnter(event):
if event.key == 'enter' and future_points1.size == future_points2.size and future_points1.size >= 3:
# P:Matrice de coordonnées source de conversion([[x,y,1],[x,y,1],...]
# x_dash:Vecteur de coordonnées X de la destination de conversion
# y_dash:Destination de conversion vecteur de coordonnées y
vec_one = np.ones((future_points2.shape[0], 1))
P = np.hstack([future_points2, vec_one])
x_dash = future_points1[:, 0]
y_dash = future_points1[:, 1]
affine_matrix = registration(P, x_dash, y_dash)
#Trouver l'image après la conversion affine
affined_image = affine(image2, affine_matrix, image1.shape)
x = np.arange(0, affined_image.shape[1], 1)
y = np.arange(0, affined_image.shape[0], 1)
X_affined, Y_affined = np.meshgrid(x, y)
ax3.pcolormesh(X_affined, Y_affined, affined_image, cmap='gray', shading='auto', alpha=0.2)
fig.canvas.draw_idle()
#Chargement d'image
image1 = np.array(Image.open('./source/test1.jpg').convert('L'))
image2 = np.array(Image.open('./source/t_test1.jpg').convert('L'))
#Bg à la fin de l'image_ajout de couleur de couleur
bg_color = 256
image2 = np.hstack([image2, bg_color * np.ones((image2.shape[0], 1), int)])
image2 = np.vstack([image2, bg_color * np.ones((1, image2.shape[1]), int)])
x_image1 = np.arange(0, image1.shape[1], 1)
y_image1 = np.arange(0, image1.shape[0], 1)
X1, Y1 = np.meshgrid(x_image1, y_image1)
x_image2 = np.arange(0, image2.shape[1], 1)
y_image2 = np.arange(0, image2.shape[0], 1)
X2, Y2 = np.meshgrid(x_image2, y_image2)
fig = plt.figure(figsize=(8, 8))
ax1 = fig.add_subplot(221)
mesh1 = ax1.pcolormesh(X1, Y1, image1, shading='auto', cmap='gray')
ax1.invert_yaxis()
ax2 = fig.add_subplot(223)
mesh2 = ax2.pcolormesh(X2, Y2, image2, shading='auto', cmap='gray')
ax2.invert_yaxis()
ax3 = fig.add_subplot(222)
mesh3 = ax3.pcolormesh(X1, Y1, image1, shading='auto', cmap='gray', alpha=0.2)
ax3.invert_yaxis()
cid = fig.canvas.mpl_connect('button_press_event', onclick)
cid = fig.canvas.mpl_connect('key_press_event', onEnter)
plt.show()
En regardant la formule, elle est similaire à la formule pour trouver le coefficient de régression linéaire! !! Ou plutôt, c'est presque la même chose que faire une régression linéaire. .. .. Ce serait intéressant si nous pouvions créer des vecteurs de caractéristiques comme la régression linéaire, ou gérer la distorsion d'image en utilisant bien la méthode de processus gaussien.
Veuillez signaler toute erreur ou tout point déroutant.