[PYTHON] Application de la conversion affine par tenseur - de la détection de base à la détection d'objet -

Il s'agit d'un article qui tente de simplifier le prétraitement en utilisant la conversion affine par tenseur. Les transformations Affin peuvent être appliquées aux images, mais les mêmes transformations peuvent être appliquées aux annotations telles que Bounding Box. Vous pouvez également l'étendre à un tenseur pour appliquer différentes transformations à un objet en même temps.

Principes de base de la conversion Affin

La transformation Affin exprime le mouvement des points avec la formule suivante.

\begin{bmatrix}x' \\\ y' \\\ 1 \end{bmatrix} = \begin{bmatrix}a & b & c \\\ d & e & f \\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}x \\\ y \\\ 1 \end{bmatrix} \tag{1}

Si vous voulez voir une compréhension vague de la conversion affine, veuillez également lire cet article.

Il prend le produit d'une matrice 3x3 et d'une matrice 3x1, où la matrice 3x3 définit la transformation et la matrice 3x1 est le point avant le déplacement.

Exemple spécifique 1: mouvement parallèle

Lorsque le point (10, 20) est déplacé de 100 dans la direction $ x $ et de 50 dans la direction $ y $, les coordonnées après le mouvement sont (110, 70), qui sont exprimées comme suit.

\begin{bmatrix}110 \\\ 70 \\\ 1 \end{bmatrix} = \begin{bmatrix}1 & 0 & 100 \\\ 0 & 1 & 50 \\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}10 \\\ 20 \\\ 1 \end{bmatrix} \tag{2}

Vous pouvez penser qu'il n'est pas nécessaire d'utiliser une telle matrice, mais le fait est qu'elle peut être représentée par une matrice. Si vous l'écrivez dans le code, c'est comme suit.

affine = np.array([[1, 0, 100], [0, 1, 50], [0, 0, 1]])
source = np.array([10, 20, 1])[:, None]
dest = np.dot(affine, source)
print(dest)
#[[110]
# [ 70]
# [  1]]

En supposant que le mouvement dans la direction $ x $ est $ t_x $ et que le mouvement dans la direction $ y $ est $ t_y $, la matrice de conversion pour un mouvement parallèle est la suivante.

\begin{bmatrix}1 & 0 & t_x \\\ 0 & 1 & t_y \\\ 0 & 0 & 1 \end{bmatrix} \tag{3}

La troisième ligne de chaque matrice est un nombre sans signification. 1 est inclus pour faciliter le calcul du produit matriciel.

Exemple spécifique 2: agrandissement / réduction

Lorsque le point (50, 100) est doublé dans la direction $ x $ et 0,8 fois dans la direction $ y $, les coordonnées après le mouvement sont (100, 80), qui sont exprimées comme suit.

\begin{bmatrix}100 \\\ 80 \\\ 1 \end{bmatrix} = \begin{bmatrix}2 & 0 & 0 \\\ 0 & 0.8 & 0 \\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}50 \\\ 100 \\\ 1 \end{bmatrix} \tag{4}
affine = np.array([[2, 0, 0], [0, 0.8, 0], [0, 0, 1]])
source = np.array([50, 100, 1])[:, None]
dest = np.dot(affine, source)
print(dest)
# [[100.]
#  [ 80.]
#  [  1.]]

En supposant que l'expansion dans la direction $ x $ est $ s_x $ et que l'expansion dans la direction $ y $ est $ s_y $, la matrice de conversion de mise à l'échelle est la suivante.

\begin{bmatrix}s_x & 0 & 0 \\\ 0 & s_y & 0 \\\ 0 & 0 & 1 \end{bmatrix} \tag{5}

Conversion d'affin pour plusieurs points

Puisque la transformation affine est un calcul matriciel, le nombre de points peut être élargi arbitrairement. Lors de la recherche de la transformation affine pour $ N $ points, prenez le produit de la matrice 3x3 et de la matrice 3xN. Le résultat est une matrice 3xN.

\begin{bmatrix} x_1' & \cdots & x_N' \\\ y_1' & \cdots & y_N' \\\ 1 & \cdots & 1 \end{bmatrix} = \begin{bmatrix} a & b & c \\\ d & e & f \\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x_1 & \cdots & x_N \\\ y_1 & \cdots & y_N \\\ 1 & \cdots & 1 \end{bmatrix} \tag{6}

Exemple spécifique 3: conversion affine carrée

La conversion Affin de $ N = 4 $ points entraîne une conversion Affin carrée. L'application de la transformation Affine, qui double $ (2, 3) $ dans la direction $ (x, y) $ et déplace $ (5, -1) $ en parallèle, vers les sommets du quadrilatère est la suivante.

w, h = 2, 1
points = np.array([[0, w, w, 0], [0, 0, h, h], [1, 1, 1, 1]], np.float32) # (3, 4)
affine = np.array([[2, 0, 5], [0, 3, -1], [0, 0, 1]])  # (3, 3)
dest = np.dot(affine, points)

plt.scatter(points[0,:], points[1,:], color="cyan")
plt.scatter(dest[0,:], dest[1,:], color="magenta")
plt.show()

affine_01.png

Exemple spécifique 4: quadrangle rotatif et boîte englobante

Ceci est un exemple qui peut être utilisé dans le prétraitement pour la détection d'objets. Lorsque vous faites pivoter l'image dans l'augmentation des données de détection d'objet, vous devez également faire pivoter le cadre englobant. Le cadre englobant peut être défini par deux points, le haut à gauche et le bas à droite, mais en prenant les quatre points du haut, vous pouvez facilement calculer le cadre englobant après rotation. En prenant les valeurs minimum et maximum pour x et y après la rotation, les coordonnées supérieure gauche et inférieure droite de la boîte englobante après rotation peuvent être obtenues.

La rotation est également l'une des transformations affines, et la matrice de transformation lors de la rotation de $ \ theta $ dans le sens antihoraire autour de l'origine est

\begin{bmatrix} \cos\theta & -\sin\theta & 0 \\\ \sin\theta & \cos\theta & 0 \\\ 0 & 0 & 1 \end{bmatrix} \tag{7}

Et lorsque les sommets de la Boîte englobante (carré) avant la rotation se déplacent vers $ (x_1 ', y_1'), \ cdots, (x_4 ', y_4') $ après rotation, un nouveau Bounding circonscrit le carré incliné. Les coordonnées de la boîte sont

Vous pouvez le trouver sur. La raison pour laquelle ce calcul doit être fait est que les sommets d'origine après rotation ne conviennent pas comme boîte englobante (car ils ne sont pas des rectangles parallèles à l'axe $ xy $) et doivent être ajustés. Veuillez voir la vidéo ci-dessous pour plus de détails.

affine_02.gif

L'intrigue ressemble à ceci: En raison du tracé, 5 points sont déplacés (chevauchant l'origine), mais pour le calcul uniquement, déplacer 4 points est OK.

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches

def rotate_box():
    w, h = 2, 1
    max_wh = max(w, h)
    points = np.array([[0, w, w, 0, 0], [0, 0, h, h, 0], [1, 1, 1, 1, 0]], np.float32) #Boîte englobante d'origine
    for theta in range(0, 360, 10):
        rad = np.radians(theta)
        rotate_matrix = np.array([
            [np.cos(rad), -np.sin(rad), 0],
            [np.sin(rad), np.cos(rad), 0],
            [0, 0, 1]], np.float32)
        dest_points = np.dot(rotate_matrix, points)[:2, :] #Carré après rotation
        rectangle = np.concatenate([np.min(dest_points, axis=-1),
                                    np.max(dest_points, axis=-1)]) #Nouvelle boîte englobante

        plt.clf()
        plt.plot(dest_points[0,:], dest_points[1,:], linewidth=2, marker="o")

        ax = plt.gca()
        rect = patches.Rectangle(rectangle[:2], *(rectangle[2:] - rectangle[:2]),
                                 linewidth=1, edgecolor="magenta", fill=False)
        ax.add_patch(rect)
        plt.ylim(-max_wh*2, max_wh*2)
        plt.xlim(-max_wh * 2, max_wh * 2)
        plt.title("degree = " + str(theta))
        plt.show()

Synthèse des transformations affines

Les transformations d'affine peuvent être combinées en prenant un produit matriciel. Lors de la conversion de $ A_1-> A_2 $, on prend le produit de $ A_2A_1P $ ($ P $ est une matrice de points). Notez que l'ordre est inversé. De plus, ** la loi sur les changes ne tient pas **, et si vous modifiez l'ordre, le résultat sera différent.

Par exemple, supposons que $ A_1 $ double $ x, y $, $ A_2 $ se déplace de 50 dans la direction $ x $ et 100 se déplace dans la direction $ y $. À ce stade, $ A_2A_1 (A_1 \ à A_2) $ est

A_2A_1 = \begin{bmatrix}1 & 0 & 50 \\\ 0 & 1 & 100 \\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}2 & 0 & 0 \\\ 0 & 2 & 0 \\\ 0 & 0 & 1 \end{bmatrix} = \begin{bmatrix}2 & 0 & 50 \\\ 0 & 2 & 100 \\\ 0 & 0 & 1 \end{bmatrix} \tag{8}

Cependant, $ A_1A_2 (A_2 \ à A_1) $ est

A_1A_2 = \begin{bmatrix}2 & 0 & 0 \\\ 0 & 2 & 0 \\\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix}1 & 0 & 50 \\\ 0 & 1 & 100 \\\ 0 & 0 & 1 \end{bmatrix} = \begin{bmatrix}2 & 0 & 100 \\\ 0 & 2 & 200 \\\ 0 & 0 & 1 \end{bmatrix} \tag{9}

Et la taille du mouvement parallèle change. Cela représente la différence entre «se déplacer en parallèle puis s'étendre, ou s'étendre et se déplacer ensuite en parallèle». Si vous ne connaissez pas la commande, c'est une bonne idée d'essayer un exemple simple comme celui-ci.

Exemple spécifique 5: Une personne insensée s'approche en tournant

Étant donné que la transformation Affin peut être appliquée à n'importe quel nombre de points plus tôt, il est normal d'avoir des milliers de points. Ici, "personne intelligente" (à partir de matériel gratuit)

atamanowaruihito.png

Pour pointer des données de groupe et tracer tout en synthétisant les transformations affines d'expansion et de rotation. Presque identique à l'exemple 4.

from PIL import Image

def atamanowaruihito():
    with Image.open("atamanowaruihito.png ") as img:
        img = img.resize((img.width // 2, img.height // 2))        
        img = img.convert("L").point(lambda x: 255 if x >= 128 else 0)  #Grisé, binarisation
        points = np.stack(np.where(np.array(img) == 0)[::-1], axis=0)  # yx ->xy pour matricer les points
        points[1,:] = img.height - points[1,:]  #Corrigez l'axe y du positif vers le bas au positif vers le haut(2, 5912)
        points = np.concatenate([points, np.ones_like(points[0:1, :])], axis=0) #Ajoutez 1 sur la 3ème ligne(3, 5912)

    for theta in range(0, 360, 1):
        rad = np.radians(theta)
        rotate_matrix = np.array([
            [np.cos(rad), -np.sin(rad), 0],
            [np.sin(rad), np.cos(rad), 0],
            [0, 0, 1]], np.float32) #Matrice de rotation
        scale_matrix = np.eye(3, dtype=np.float32) * (1 + theta / 180)
        #La synthèse de transformation d'affine est le produit matriciel de la matrice de transformation
        # A1->Pour A2, prenez le produit dans l'ordre de A2A1 (notez la commande)
        affine = np.dot(scale_matrix, rotate_matrix)  #Faire pivoter et développer
                
        dest_points = np.dot(affine, points)[:2, :] #Groupe de points après rotation
        rectangle = np.concatenate([np.min(dest_points, axis=-1),
                                    np.max(dest_points, axis=-1)]) #Boîte englobante pour les points

        plt.clf()
        plt.scatter(dest_points[0,:], dest_points[1,:], s=1)

        ax = plt.gca()
        rect = patches.Rectangle(rectangle[:2], *(rectangle[2:] - rectangle[:2]),
                                 linewidth=1, edgecolor="magenta", fill=False)
        ax.add_patch(rect)
        plt.ylim(-750, 750)
        plt.xlim(-750, 750)
        plt.show()

affine_03.gif

La boîte englobante peut être calculée de la même manière que l'exemple carré.

Appliquer plusieurs transformations affines en même temps

De là, le calcul du tenseur Envisagez une méthode d'application de plusieurs transformations Affin au même point en même temps au lieu de synthétiser des transformations Affin. C'est difficile à exprimer dans une expression, alors pensez au code.

S'il n'y avait qu'une seule transformation affine, la matrice de transformation aurait la forme (3, 3) ''. Mais que se passe-t-il s'il y a deux transformations, c'est-à-dire la forme (2, 3, 3) ''? En d'autres termes,

# points(4 points): (3, 4), affines: (2, 3, 3)
output = np.zeros((2, 3, 4)
for i in range(affines.shape[0]):
        output[i] = np.dot(affines[i], points)

Je veux calculer. En fait, il s'agit d'une seule ligne sans utiliser de boucle for

output = np.matmul(affines, points)

Il peut être calculé avec.

Exemple spécifique 6: Transformations affines multiples d'un quadrilatère

Essayons trois transformations affines dans l'exemple 3.

  1. Le carré d'origine tel qu'il est (conversion constante)
  2. Doublez $ (2, 3) $ dans la direction de $ (x, y) $ et déplacez $ (5, -1) $ en parallèle
  3. Doublez $ (3, 1) $ dans la direction $ (x, y) $ et déplacez $ (10, 2) $ en parallèle
from matplotlib import cm

def rectangle_multi():
    w, h = 2, 1
    points = np.array([[0, w, w, 0], [0, 0, h, h], [1, 1, 1, 1]], np.float32)  # (3, 4)
    a1 = np.eye(3) #Conversion égale
    a2 = np.array([[2, 0, 5], [0, 3, -1], [0, 0, 1]]) # (3, 3)
    a3 = np.array([[3, 0, 10], [0, 1, 2], [0, 0, 1]])  # (3, 3)
    affine = np.stack([a1, a2, a3], axis=0) # (3, 3, 3)
    dest = np.matmul(affine, points)  # (3, 3, 4)

    cmap = cm.get_cmap("tab10")
    for i in range(3):
        plt.scatter(dest[i,0,:], dest[i, 1,:], color=cmap(i))
    plt.show()

affine_04.png

J'ai pu effectuer trois transformations différentes en un seul calcul. De cette manière, la ** conversion un-à-plusieurs ** peut également être effectuée en utilisant une conversion affine en utilisant un tenseur.

Exemple spécifique 7: Anchor Box pour la détection d'objets

Dans la détection d'objet, le Bounding Box est prédit à partir de chaque point (Anchor) de la sortie du réseau neuronal. La boîte englobante correspond aux (coordonnées) de l'image brute, mais de nombreuses coordonnées apparaissent dans la détection d'objet. Par exemple

En d'autres termes, une conversion de point flexible "de coordonnées en coordonnées" est requise. Cette transformation de coordonnées peut être unifiée par la transformation affine par tenseur.

Considérez maintenant ce qui suit:

En règle générale, considérez deux conversions affines.

  1. Conversion d'affin du prétraitement (image originale → image d'entrée). 1,5 fois dans la direction $ x $ et 0,8 fois dans la direction $ y $. La forme de (3, 3).
  2. Image d'entrée → conversion d'ancre. La forme de (4, 4, 3, 3), avec la conversion affine de (3, 3) '' superposée sur 4x4 vertical et horizontal. (i, j,:,:) $ (0 \ leq i, j \ leq 3) La conversion affine à la position de $ est la suivante. Puisqu'il s'agit d'un mouvement parallèle vu de la boîte d'ancrage, le signe sera négatif.
\begin{bmatrix}1/16 & 0 & -0.5+i \\\ 0 & 1/16 & -0.5+j \\\ 0 & 0 & 1 \end{bmatrix} \tag{10}

Tout ce que vous avez à faire est de combiner les deux, 1 et 2. C'est délicieux. Si vous voulez faire la conversion inverse (ancre → image originale), prenez la matrice inverse du tenseur composite de 1 et 2, c'est-à-dire

inv_transform = np.linalg.inv(combined_affine)

Vous pouvez obtenir le tenseur de conversion inverse simplement en faisant. Si l'entrée de `` np.linalg.inv '' est un tenseur, empilez la matrice inverse pour les deux derniers axes.

En détection d'objet, la notation de $ (y, x) $ est plus pratique que la notation de $ (x, y) $ (car le tenseur de l'image est $ (B, H, W, C) $), donc mouvement parallèle Conversion d'affine de $ t_x, t_y $, mise à l'échelle $ s_x, s_y $

\begin{bmatrix}s_y & 0 & t_y \\\ 0 & s_x & t_X \\\ 0 & 0 & 1 \end{bmatrix} \tag{11}

Il est représenté par. Même si les axes sont permutés, cela fonctionne toujours comme une conversion affine.

def anchor_box():
    bounding_boxes = np.array([[10, 20, 30, 40], [30, 30, 50, 50]])  # (2, 4)
    points = bounding_boxes.reshape(-1, 2).T  # (4, 2) -> (2, 4)
    points = np.concatenate([points, np.ones_like(points[0:1,:])], axis=0)  # (3, 4)
    #Pensez en coordonnées yx
    a1 = np.array([[0.8, 0, 0], [0, 1.5, 0], [0, 0, 1]])  #0 dans la direction y.8 fois, 1 dans la direction x.5 fois
    #ancre
    offset_x, offset_y = np.meshgrid(-(np.arange(4) + 0.5), -(np.arange(4) + 0.5)) #Puisque l'origine de l'image d'entrée est vue depuis l'ancre, moins le mouvement parallèle
    a2 = np.zeros((4, 4, 3, 3)) + np.eye(3).reshape(1, 1, 3, 3) / 16.0 # (4, 4, 3, 3)Diffuser à
    a2[:,:,0,2] = offset_y
    a2[:,:, 1, 2] = offset_x
    a2[:,:, 2, 2] = 1.0
    #Synthèse d'affine
    affine = np.matmul(a2, a1)
    #Conversion d'affine
    raw_dest = np.matmul(affine, points)  # (4, 4, 3, 4)
    dest = raw_dest.swapaxes(-1, -2)[:,:,:,:2]  # (4, 4, 4, 3)Déménagement version Tensol-> (4, 4, 4, 2)
    dest = dest.reshape(4, 4, 2, 4)
    print("Coordonnées après conversion affine lorsque le cadre englobant de l'image d'origine est visualisé à chaque ancre")
    print(dest)

    #Conversion inverse et vérification
    raw_inv = np.matmul(np.linalg.inv(affine), raw_dest)
    inv = raw_inv.swapaxes(-1, -2)[:,:,:,:2]
    inv = inv.reshape(4, 4, 2, 4)  #Correspond aux cadres de délimitation
    print("La conversion inverse des coordonnées converties revient à la valeur d'origine")
    print(inv)

    #Conversion inverse affine (confirmation)
    inv_transoform = np.linalg.inv(affine)
    print("Conversion inverse affine (pour le débogage)")
    print(inv_transoform)

<détails>

Cliquez pour afficher la sortie </ summary>

Coordonnées après conversion affine lorsque le cadre englobant de l'image d'origine est visualisé à chaque ancre
[[[[ 0.      1.375   1.      3.25  ]
   [ 1.      2.3125  2.      4.1875]]

  [[ 0.      0.375   1.      2.25  ]
   [ 1.      1.3125  2.      3.1875]]

  [[ 0.     -0.625   1.      1.25  ]
   [ 1.      0.3125  2.      2.1875]]

  [[ 0.     -1.625   1.      0.25  ]
   [ 1.     -0.6875  2.      1.1875]]]


 [[[-1.      1.375   0.      3.25  ]
   [ 0.      2.3125  1.      4.1875]]

  [[-1.      0.375   0.      2.25  ]
   [ 0.      1.3125  1.      3.1875]]

  [[-1.     -0.625   0.      1.25  ]
   [ 0.      0.3125  1.      2.1875]]

  [[-1.     -1.625   0.      0.25  ]
   [ 0.     -0.6875  1.      1.1875]]]


 [[[-2.      1.375  -1.      3.25  ]
   [-1.      2.3125  0.      4.1875]]

  [[-2.      0.375  -1.      2.25  ]
   [-1.      1.3125  0.      3.1875]]

  [[-2.     -0.625  -1.      1.25  ]
   [-1.      0.3125  0.      2.1875]]

  [[-2.     -1.625  -1.      0.25  ]
   [-1.     -0.6875  0.      1.1875]]]


 [[[-3.      1.375  -2.      3.25  ]
   [-2.      2.3125 -1.      4.1875]]

  [[-3.      0.375  -2.      2.25  ]
   [-2.      1.3125 -1.      3.1875]]

  [[-3.     -0.625  -2.      1.25  ]
   [-2.      0.3125 -1.      2.1875]]

  [[-3.     -1.625  -2.      0.25  ]
   [-2.     -0.6875 -1.      1.1875]]]]
La conversion inverse des coordonnées converties revient à la valeur d'origine
[[[[10. 20. 30. 40.]
   [30. 30. 50. 50.]]

  [[10. 20. 30. 40.]
   [30. 30. 50. 50.]]

  [[10. 20. 30. 40.]
   [30. 30. 50. 50.]]

  [[10. 20. 30. 40.]
   [30. 30. 50. 50.]]]


 [[[10. 20. 30. 40.]
   [30. 30. 50. 50.]]

  [[10. 20. 30. 40.]
   [30. 30. 50. 50.]]

  [[10. 20. 30. 40.]
   [30. 30. 50. 50.]]

  [[10. 20. 30. 40.]
   [30. 30. 50. 50.]]]


 [[[10. 20. 30. 40.]
   [30. 30. 50. 50.]]

  [[10. 20. 30. 40.]
   [30. 30. 50. 50.]]

  [[10. 20. 30. 40.]
   [30. 30. 50. 50.]]

  [[10. 20. 30. 40.]
   [30. 30. 50. 50.]]]


 [[[10. 20. 30. 40.]
   [30. 30. 50. 50.]]

  [[10. 20. 30. 40.]
   [30. 30. 50. 50.]]

  [[10. 20. 30. 40.]
   [30. 30. 50. 50.]]

  [[10. 20. 30. 40.]
   [30. 30. 50. 50.]]]]
Conversion inverse affine (pour le débogage)
[[[[20.          0.         10.        ]
   [ 0.         10.66666667  5.33333333]
   [ 0.          0.          1.        ]]

  [[20.          0.         10.        ]
   [ 0.         10.66666667 16.        ]
   [ 0.          0.          1.        ]]

  [[20.          0.         10.        ]
   [ 0.         10.66666667 26.66666667]
   [ 0.          0.          1.        ]]

  [[20.          0.         10.        ]
   [ 0.         10.66666667 37.33333333]
   [ 0.          0.          1.        ]]]


 [[[20.          0.         30.        ]
   [ 0.         10.66666667  5.33333333]
   [ 0.          0.          1.        ]]

  [[20.          0.         30.        ]
   [ 0.         10.66666667 16.        ]
   [ 0.          0.          1.        ]]

  [[20.          0.         30.        ]
   [ 0.         10.66666667 26.66666667]
   [ 0.          0.          1.        ]]

  [[20.          0.         30.        ]
   [ 0.         10.66666667 37.33333333]
   [ 0.          0.          1.        ]]]


 [[[20.          0.         50.        ]
   [ 0.         10.66666667  5.33333333]
   [ 0.          0.          1.        ]]

  [[20.          0.         50.        ]
   [ 0.         10.66666667 16.        ]
   [ 0.          0.          1.        ]]

  [[20.          0.         50.        ]
   [ 0.         10.66666667 26.66666667]
   [ 0.          0.          1.        ]]

  [[20.          0.         50.        ]
   [ 0.         10.66666667 37.33333333]
   [ 0.          0.          1.        ]]]


 [[[20.          0.         70.        ]
   [ 0.         10.66666667  5.33333333]
   [ 0.          0.          1.        ]]

  [[20.          0.         70.        ]
   [ 0.         10.66666667 16.        ]
   [ 0.          0.          1.        ]]

  [[20.          0.         70.        ]
   [ 0.         10.66666667 26.66666667]
   [ 0.          0.          1.        ]]

  [[20.          0.         70.        ]
   [ 0.         10.66666667 37.33333333]
   [ 0.          0.          1.        ]]]]

Vous pouvez voir que les coordonnées de la boîte englobante d'origine sont restaurées même si l'image d'origine → ancre et ancre → image d'origine sont inversées. Il est fort qu'il n'y ait qu'une seule conversion inverse dans le calcul matriciel. Lorsque vous voulez voir si la conversion d'ancre fonctionne, il est facile de vérifier l'affine de conversion inverse (matrice inverse).

Exemple spécifique 8: Quoi qu'il en soit, une "personne intelligente" qui semble être malade

Vous pouvez également le faire en tirant parti de la transformation affine par le tenseur. Veuillez regarder la vidéo ci-dessous.

affine_05.gif

Je vais omettre l'explication, mais si vous êtes intéressé, veuillez jeter un œil au code.

<détails>

Cliquez pour voir le code </ summary>

def atamanowaruihito2():
    with Image.open("atamanowaruihito.png ") as img:
        img = img.resize((img.width // 2, img.height // 2))        
        img = img.convert("L").point(lambda x: 255 if x >= 128 else 0)  #Grisé, binarisation
        points = np.stack(np.where(np.array(img) == 0)[::-1], axis=0)  # yx ->xy pour matricer les points
        points[1,:] = img.height - points[1,:]  #Corrigez l'axe y du positif vers le bas au positif vers le haut(2, 5912)
        points = np.concatenate([points, np.ones_like(points[0:1, :])], axis=0) #Ajoutez 1 sur la 3ème ligne(3, 5912)

    #Nombre aléatoire pour la matrice de rotation
    a = np.random.uniform(1.0, 5.0, size=(4, 4))
    b = np.random.uniform(-180, 180, size=a.shape)
    #Nombre aléatoire pour la matrice d'expansion
    c = np.random.uniform(0.5, 1.5, size=a.shape)
    d = np.random.uniform(1.0, 5.0, size=a.shape)
    e = np.random.uniform(-180, 180, size=a.shape)
    f = np.random.uniform(0.5, 1.0, size=a.shape) + c
    #Nombre aléatoire pour un mouvement parallèle
    e = np.random.uniform(0, 200, size=a.shape)
    g = np.random.uniform(1.0, 5.0, size=a.shape)
    h = np.random.uniform(-180, 180, size=a.shape)
    
    for theta in range(0, 360, 10):
        #Conversion de rotation
        rad = np.radians(a * theta + b)
        rotate_tensor = np.broadcast_to(np.eye(3)[None, None,:], (4, 4, 3, 3)).copy()
        rotate_tensor[:,:, 0, 0] = np.cos(rad)
        rotate_tensor[:,:, 0, 1] = -np.sin(rad)
        rotate_tensor[:,:, 1, 0] = np.sin(rad)
        rotate_tensor[:,:, 1, 1] = np.cos(rad)
        #Conversion d'agrandissement
        rad = np.radians(d * theta + e)
        scale_tensor = np.broadcast_to(np.eye(3)[None, None,:], (4, 4, 3, 3)).copy()
        scale_tensor[:,:, 0, 0] = c * np.sin(rad) + f
        scale_tensor[:,:, 1, 1] = c * np.sin(rad) + f
        #Mouvement parallèle individuel
        rad = np.radians(g * theta + h)
        transform_tensor = np.broadcast_to(np.eye(3)[None, None,:], (4, 4, 3, 3)).copy()
        transform_tensor[:,:, 0, 2] = e * np.cos(rad)        
        transform_tensor[:,:, 1, 2] = e * np.sin(rad)
        #Mouvement parallèle global
        shift_x, shift_y = np.meshgrid(np.arange(4), np.arange(4))
        anchor_tensor = np.broadcast_to(np.eye(3)[None, None,:], (4, 4, 3, 3)).copy()
        anchor_tensor[:,:, 0, 2] = shift_x * 500
        anchor_tensor[:,:, 1, 2] = shift_y * 500

        #Rotation → Agrandissement → Mouvement parallèle → Mouvement parallèle global
        affine = np.matmul(anchor_tensor, transform_tensor)
        affine = np.matmul(affine, scale_tensor)
        affine = np.matmul(affine, rotate_tensor)
        dest_points = np.matmul(affine, points)[:, :, :2, :] #Groupe de points après rotation
        rectangle = np.concatenate([np.min(dest_points, axis=-1),
                                    np.max(dest_points, axis=-1)], axis=-1)  #Boîte englobante pour les points
                                    
        dest_points = dest_points.swapaxes(-1, -2).reshape(-1, 2)
        rectangle = rectangle.reshape(-1, 4)

        plt.clf()
        plt.scatter(dest_points[:, 0], dest_points[:, 1], s=1)

        ax = plt.gca()
        for i in range(rectangle.shape[0]):
            rect = patches.Rectangle(rectangle[i, :2], *(rectangle[i, 2:] - rectangle[i, :2]),
                                    linewidth=1, edgecolor="magenta", fill=False)
            ax.add_patch(rect)
        plt.ylim(-750, 2500)
        plt.xlim(-750, 2500)
        plt.title("theta = " + str(theta))
        plt.show()

Recommended Posts