Transformation matrix of points in 2D coordinate system (translation, scaling, rotation, miscut)

This article reprints the article of the same name written by Zhuo Bufan on Zhizhihu. I have run the code described in it and added some comments to make it easier to understand.

1. Translation

In 2D space, we often need to translate a point to another position. Assume a point P in space, expressed as (x, y) in coordinates; translate it by tx in the x direction and ty in the y direction. Assume that the coordinates of the point after translation are (x’, y’), then the above point The translation operation can be summarized as the following formula:

Use a homogeneous matrix to express it as follows:

Implement the above process in code as follows:

#The file name can be named "panning.py", which supports Chinese character file operation.
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
#Usage: Return multi-dimensional structures, common ones such as 2D graphics and 3D graphics.
#The first return value is the distribution of the first dimension data in the final structure,
#The second return value is the distribution of the second dimension data in the final structure, and so on. (Distribution is presented in matrix form)
#mgrid[[1:3:3j, 4:5:2j]]
#3j: 3 points
#The step length is a complex number indicating the number of points, closed on the left and closed on the right
#The step size is a real number indicating the interval, left closed and right open
X, Y = np.mgrid[0:1:5j, 0:1:5j]
#The ravel function in numpy is used to turn a multi-dimensional array into a one-dimensional array
x, y = X.ravel(), Y.ravel()

def trans_translate(x, y, tx, ty):
    T = [[1, 0, tx],
         [0, 1, ty],
         [0, 0, 1]]
    T = np.array(T)
    P = np.array([x, y, [1] * x.size])
    The #np.dot() function mainly has two functions, vector dot product and matrix multiplication
    #np.dot(a, b), where a is a one-dimensional vector and b is a one-dimensional vector. Of course, a and b are both of type np.ndarray. At this time, because it is one-dimensional, it is a vector dot product. .
    return np.dot(T, P)
#Draw a chart with one row and four columns
fig, ax = plt.subplots(1, 4)
T_ = [[0, 0], [2.3, 0], [0, 1.7], [2, 2]]
for i in range(4):
    #The amount of translation is obtained from T_ (different for each table)
    tx, ty = T_[i]
    #Use the translation transformation function to obtain the transformed coordinates
    x_, y_, _ = trans_translate(x, y, tx, ty)
    ax[i].scatter(x_, y_)
    ax[i].set_title(r'$t_x={0:.2f}$ , $t_y={1:.2f}$'.format(tx, ty))

    ax[i].set_xlim([-0.5, 4])
    ax[i].set_ylim([-0.5, 4])
    ax[i].grid(alpha=0.5)
    ax[i].axhline(y=0, color='k')
    ax[i].axvline(x=0, color='k')
plt.show()

The running effect is as follows:

2. Scaling

In 2D space, another common operation on a point (x, y) is to perform a scaling operation relative to another point (px, py). We might as well have the scaling factor in the x direction be sx and the scaling factor in the y direction be sy, Then the scaling operation of the above point (x, y) relative to the point (px, py) can be summarized as the following formula:

Use a homogeneous matrix to express it as follows:

Implement the above process in code as follows:

#The file name can be named "zoom.py", which supports Chinese character file operation.
import matplotlib
import matplotlib.pyplot as plt
import numpy as np

X, Y = np.mgrid[0:1:5j, 0:1:5j]
x, y = X.ravel(), Y.ravel()

def trans_scale(x, y, px, py, sx, sy):
    T = [[sx, 0 , px*(1 - sx)],
         [0 , sy, py*(1 - sy)],
         [0 , 0 , 1 ]]
    T = np.array(T)
    P = np.array([x, y, [1]*x.size])
    The #np.dot() function mainly has two functions, vector dot product and matrix multiplication
    return np.dot(T, P)
#Draw a chart with one row and four columns
fig, ax = plt.subplots(1, 4)
S_ = [[1, 1], [1.8, 1], [1, 1.7], [2, 2]]
P_ = [[0, 0], [0, 0], [0.45, 0.45], [1.1, 1.1]]
for i in range(4):
    #The amount of scaling is obtained from S_, and the relative point is obtained from P_ (different for each table)
    sx, sy = S_[i]; px, py = P_[i]
    #Transformed point coordinates
    x_, y_, _ = trans_scale(x, y, px, py, sx, sy)
    ax[i].scatter(x_, y_)
    ax[i].scatter(px, py)
    ax[i].set_title(r'$p_x={0:.2f}$ , $p_y={1:.2f}$'.format(px, py) + '\\
'
                    r'$s_x={0:.2f}$ , $s_y={1:.2f}$'.format(sx, sy))
    
    ax[i].set_xlim([-2, 2])
    ax[i].set_ylim([-2, 2])
    ax[i].grid(alpha=0.5)
    ax[i].axhline(y=0, color='k')
    ax[i].axvline(x=0, color='k')

plt.show()

The running effect is as follows:

3. Rotation

In 2D space, another commonly used operation on a point (x, y) is to rotate relative to another point (px, py). Generally speaking, counterclockwise is positive and clockwise is negative. Assume that the rotation angle is beta. , then the operation of the rotation angle beta of the above point (x, y) relative to the point (px, py) can be summarized as the following formula:

Use a homogeneous matrix to express it as follows:

The running code is as follows:

#The file name can be named "rotation.py", which supports Chinese character file operation.
import matplotlib
import matplotlib.pyplot as plt
import numpy as np

X, Y = np.mgrid[0:1:5j, 0:1:5j]
x, y = X.ravel(), Y.ravel()

def trans_rotate(x, y, px, py, beta):
    beta = np.deg2rad(beta)
    T = [[np.cos(beta), -np.sin(beta), px*(1 - np.cos(beta)) + py*np.sin(beta)],
         [np.sin(beta), np.cos(beta), py*(1 - np.cos(beta)) - px*np.sin(beta)],
         [0 , 0 , 1 ]]
    T = np.array(T)
    P = np.array([x, y, [1]*x.size])
    The #np.dot() function mainly has two functions, vector dot product and matrix multiplication
    return np.dot(T, P)
#Draw a chart with one row and four columns
fig, ax = plt.subplots(1, 4)

R_ = [0, 225, 40, -10]
P_ = [[0, 0], [0, 0], [0.5, -0.5], [1.1, 1.1]]

for i in range(4):
    #The rotation angle is obtained from R_, and the relative point is obtained from P_ (different for each table)
    beta = R_[i]; px, py = P_[i]
    #Transformed point coordinates
    x_, y_, _ = trans_rotate(x, y, px, py, beta)
    ax[i].scatter(x_, y_)
    ax[i].scatter(px, py)
    ax[i].set_title(r'$\beta={0}°$ , $p_x={1:.2f}$ , $p_y={2:.2f}$'.format(beta, px , py))
    
    ax[i].set_xlim([-2, 2])
    ax[i].set_ylim([-2, 2])
    ax[i].grid(alpha=0.5)
    ax[i].axhline(y=0, color='k')
    ax[i].axvline(x=0, color='k')

plt.show()

The running effect is as follows:

4. Shearing

In 2D space, another commonly used operation on a point (x, y) is to perform a staggered cutting operation relative to another point (px, py). Staggered cutting is generally used for deformation processing of elastic objects. Assume that the staggered cutting parameter along the x direction is lambdax and the staggered cutting parameter along the y direction is lambday. Then the staggered cutting operation of the above point (x, y) relative to the point (px, py) can be summarized as the following formula:

Use a homogeneous matrix to express it as follows:

The running code is as follows:

#The file name can be named "wrong cut.py", which supports the operation of Chinese character files.
import matplotlib
import matplotlib.pyplot as plt
import numpy as np

X, Y = np.mgrid[0:1:5j, 0:1:5j]
x, y = X.ravel(), Y.ravel()

def trans_shear(x, y, px, py, lambdax, lambday):
    T = [[1 , lambdax, -lambdax*px],
         [lambday, 1 , -lambday*py],
         [0 , 0 , 1 ]]
    T = np.array(T)
    P = np.array([x, y, [1]*x.size])
    The #np.dot() function mainly has two functions, vector dot product and matrix multiplication
    return np.dot(T, P)
#Draw a chart with one row and four columns
fig, ax = plt.subplots(1, 4)

L_ = [[0, 0], [2, 0], [0, -2], [-2, -2]]
P_ = [[0, 0], [0, 0], [0, 1.5], [1.1, 1.1]]

for i in range(4):
     #The miscut parameters are obtained from L_, and the relative points are obtained from P_ (different for each table)
    lambdax, lambday = L_[i]; px, py = P_[i]
    x_, y_, _ = trans_shear(x, y, px, py, lambdax, lambday)
    ax[i].scatter(x_, y_)
    ax[i].scatter(px, py)
    ax[i].set_title(r'$p_x={0:.2f}$ , $p_y={1:.2f}$'.format(px, py) + '\\
'
                    r'$\lambda_x={0:.2f}$ , $\lambda_y={1:.2f}$'.format(lambdax, lambday))

    ax[i].set_xlim([-3, 3])
    ax[i].set_ylim([-3, 3])
    ax[i].grid(alpha=0.5)
    ax[i].axhline(y=0, color='k')
    ax[i].axvline(x=0, color='k')

plt.show()

The running effect is as follows:

5. Summary

With the above translation, rotation, scaling and miscut matrices, we can obtain the arbitrarily transformed coordinates of point P on the two-dimensional plane through matrix multiplication. To help understand the reverse calculation of the transformation matrix, this question describes the forward transformation.