Picture of the authorMindect

Linear Transformations

In this notebook you will explore linear transformations, visualize their results and master matrix multiplication to apply various linear transformations.

Packages

Run the following cell to load the package you'll need.

import numpy as np
# OpenCV library for image transformations.
import cv2

1 - Transformations

A transformation is a function from one vector space to another that respects the underlying (linear) structure of each vector space. Referring to a specific transformation, you can use a symbol, such as TT. Specifying the spaces containing the input and output vectors, e.g. R2\mathbb{R}^2 and R3\mathbb{R}^3, you can write T:R2R3T: \mathbb{R}^2 \rightarrow \mathbb{R}^3. Transforming vector vR2v \in \mathbb{R}^2 into the vector wR3w\in\mathbb{R}^3 by the transformation TT, you can use the notation T(v)=wT(v)=w and read it as "T of v equals to w" or "vector w is an image of vector v with the transformation T".

The following Python function corresponds to the transformation T:R2R3T: \mathbb{R}^2 \rightarrow \mathbb{R}^3 with the following symbolic formula:

T([v1v2])=[3v102v2](1)T\begin{pmatrix} \begin{bmatrix} v_1 \\ v_2 \end{bmatrix}\end{pmatrix}= \begin{bmatrix} 3v_1 \\ 0 \\ -2v_2 \end{bmatrix} \tag{1}
def T(v):
    w = np.zeros((3,1))
    w[0,0] = 3*v[0,0]
    w[2,0] = -2*v[1,0]
    
    return w
 
v = np.array([[3], [5]])
w = T(v)
 
print("Original vector:\n", v, "\n\n Result of the transformation:\n", w)

2 - Linear Transformations

A transformation TT is said to be linear if the following two properties are true for any scalar kk, and any input vectors uu and vv:

  1. T(kv)=kT(v)T(kv)=kT(v),
  2. T(u+v)=T(u)+T(v)T(u+v)=T(u)+T(v).

In the example above TT is a linear transformation:

T([kv1kv2])=[3kv102kv2]=k[3v102v2]=kT(v),(2) T \begin{pmatrix}\begin{bmatrix} kv_1 \\ kv_2 \end{bmatrix}\end{pmatrix} = \begin{bmatrix} 3kv_1 \\ 0 \\ -2kv_2 \end{bmatrix} = k\begin{bmatrix} 3v_1 \\ 0 \\ -2v_2 \end{bmatrix} = kT(v),\tag{2} T(u+v)=T([u1+v1u2+v2])=[3(u1+v1)02(u2+v2)]=[3u102u2]+[3v102v2]=T(u)+T(v).(3)T (u+v) = T \begin{pmatrix}\begin{bmatrix} u_1 + v_1 \\ u_2 + v_2 \end{bmatrix}\end{pmatrix} = \begin{bmatrix} 3(u_1+v_1) \\ 0 \\ -2(u_2+v_2) \end{bmatrix} = \begin{bmatrix} 3u_1 \\ 0 \\ -2u_2 \end{bmatrix} + \begin{bmatrix} 3v_1 \\ 0 \\ -2v_2 \end{bmatrix} = T(u)+T(v).\tag{3}

You can change the values of kk or vectors uu and vv in the cell below, to check that this is true for some specific values.

u = np.array([[1], [-2]])
v = np.array([[2], [4]])
 
k = 7
 
print("T(k*v):\n", T(k*v), "\n k*T(v):\n", k*T(v), "\n\n")
print("T(u+v):\n", T(u+v), "\n T(u)+T(v):\n", T(u)+T(v))

Some examples of linear transformations are rotations, reflections, scaling (dilations), etc. In this lab you will explore a few of them.

3 - Transformations Defined as a Matrix Multiplication

Let L:RmRnL: \mathbb{R}^m \rightarrow \mathbb{R}^n be defined by a matrix AA, where L(v)=AvL(v)=Av, multiplication of the matrix AA (n×mn\times m) and vector vv (m×1m\times 1) resulting in the vector ww (n×1n\times 1).

Now try to guess, what should be the elements of matrix AA, corresponding to the transformation L:R2R3L: \mathbb{R}^2 \rightarrow \mathbb{R}^3:

L([v1v2])=[3v102v2]=[??????][v1v2](4)L\begin{pmatrix} \begin{bmatrix} v_1 \\ v_2 \end{bmatrix}\end{pmatrix}= \begin{bmatrix} 3v_1 \\ 0 \\ -2v_2 \end{bmatrix}= \begin{bmatrix} ? & ? \\ ? & ? \\ ? & ? \end{bmatrix} \begin{bmatrix} v_1 \\ v_2 \end{bmatrix} \tag{4}

To do that, write the transformation LL as AvAv and then perform matrix multiplication:

L([v1v2])=A[v1v2]=[a1,1a1,2a2,1a2,2a3,1a3,2][v1v2]=[a1,1v1+a1,2v2a2,1v1+a2,2v2a3,1v1+a3,2v2]=[3v102v2](5)L\begin{pmatrix} \begin{bmatrix} v_1 \\ v_2 \end{bmatrix}\end{pmatrix}= A\begin{bmatrix} v_1 \\ v_2 \end{bmatrix}= \begin{bmatrix} a_{1,1} & a_{1,2} \\ a_{2,1} & a_{2,2} \\ a_{3,1} & a_{3,2} \end{bmatrix} \begin{bmatrix} v_1 \\ v_2 \end{bmatrix}= \begin{bmatrix} a_{1,1}v_1+a_{1,2}v_2 \\ a_{2,1}v_1+a_{2,2}v_2 \\ a_{3,1}v_1+a_{3,2}v_2 \\ \end{bmatrix}= \begin{bmatrix} 3v_1 \\ 0 \\ -2v_2 \end{bmatrix}\tag{5}

Can you see now what should be the values of the elements ai,ja_{i,j} of matrix AA to make the equalities (5)(5) correct? Find out the answer in the following code cell:

def L(v):
    A = np.array([[3,0], [0,0], [0,-2]])
    print("Transformation matrix:\n", A, "\n")
    w = A @ v
    
    return w
 
v = np.array([[3], [5]])
w = L(v)
 
print("Original vector:\n", v, "\n\n Result of the transformation:\n", w)

Every linear transformation can be carried out by matrix multiplication. And vice versa, carrying out matrix multiplication, it is natural to consider the linear transformation that it represents. It means you can associate the matrix with the linear transformation in some way. This is a key connection between linear transformations and matrix algebra.

4 - Standard Transformations in a Plane

As discussed above in section 3, a linear transformation L:R2R2L: \mathbb{R}^2 \rightarrow \mathbb{R}^2 can be represented as a multiplication of a 2×22 \times 2 matrix and a coordinate vector vR2.v\in\mathbb{R}^2. Note that so far you have been using some random vector vR2.v\in\mathbb{R}^2. (e.g. v=[35]v=\begin{bmatrix}3 \\ 5\end{bmatrix}). To have a better intuition of what the transformation is really doing in the R2\mathbb{R}^2 space, it is wise to choose vector vv in a less random way.

A good choice would be vectors of a standard basis e1=[10]e_1=\begin{bmatrix}1 \\ 0\end{bmatrix} and e2=[01]e_2=\begin{bmatrix}0 \\ 1\end{bmatrix}. Let's apply linear transformation LL to each of the vectors e1e_1 and e2e_2: L(e1)=Ae1L(e_1)=Ae_1 and L(e2)=Ae2L(e_2)=Ae_2. If you put vectors {e1,e2}\{e_1, e_2\} into columns of a matrix and perform matrix multiplication

A[e1e2]=[Ae1Ae2]=[L(e1)L(e2)],(3)A\begin{bmatrix}e_1 & e_2\end{bmatrix}=\begin{bmatrix}Ae_1 & Ae_2\end{bmatrix}=\begin{bmatrix}L(e_1) & L(e_2)\end{bmatrix},\tag{3}

you can note that [e1e2]=[1001]\begin{bmatrix}e_1 & e_2\end{bmatrix}=\begin{bmatrix}1 & 0 \\ 0 & 1\end{bmatrix} (identity matrix). Thus, A[e1e2]=AI=AA\begin{bmatrix}e_1 & e_2\end{bmatrix} = AI=A, and

A=[L(e1)L(e2)].(4)A=\begin{bmatrix}L(e_1) & L(e_2)\end{bmatrix}.\tag{4}

This is a matrix with the columns that are the images of the vectors of the standard basis.

This choice of vectors {e1,e2e_1, e_2} provides opportinuty for the visual representation of the linear transformation LL (you will see the examples below).

4.1 - Example 1: Horizontal Scaling (Dilation)

Horizontal scaling (factor 22 in this example) can be defined considering transformation of a vector e1=[10]e_1=\begin{bmatrix}1 \\ 0\end{bmatrix} into a vector [20]\begin{bmatrix}2 \\ 0\end{bmatrix} and leaving vector e2=[01]e_2=\begin{bmatrix}0 \\ 1\end{bmatrix} without any changes. The following function T_hscaling() corresponds to the horizontal scaling (factor 22) of a vector. The second function transform_vectors() applies defined transformation to a set of vectors (here two vectors).

def T_hscaling(v):
    A = np.array([[2,0], [0,1]])
    w = A @ v
    
    return w
    
    
def transform_vectors(T, v1, v2):
    V = np.hstack((v1, v2))
    W = T(V)
    
    return W
    
e1 = np.array([[1], [0]])
e2 = np.array([[0], [1]])
 
transformation_result_hscaling = transform_vectors(T_hscaling, e1, e2)
 
print("Original vectors:\n e1= \n", e1, "\n e2=\n", e2, 
      "\n\n Result of the transformation (matrix form):\n", transformation_result_hscaling)

You can get a visual understanding of the transformation, producing a plot which displays input vectors, and their transformations. Do not worry if the code in the following cell will not be clear - at this stage this is not important code to understand.

import matplotlib.pyplot as plt
 
def plot_transformation(T, e1, e2):
    color_original = "#129cab"
    color_transformed = "#cc8933"
    
    _, ax = plt.subplots(figsize=(7, 7))
    ax.tick_params(axis='x', labelsize=14)
    ax.tick_params(axis='y', labelsize=14)
    ax.set_xticks(np.arange(-5, 5))
    ax.set_yticks(np.arange(-5, 5))
    
    plt.axis([-5, 5, -5, 5])
    plt.quiver([0, 0],[0, 0], [e1[0], e2[0]], [e1[1], e2[1]], color=color_original, angles='xy', scale_units='xy', scale=1)
    plt.plot([0, e2[0], e1[0], e1[0]], 
             [0, e2[1], e2[1], e1[1]], 
             color=color_original)
    e1_sgn = 0.4 * np.array([[1] if i==0 else [i] for i in np.sign(e1)])
    ax.text(e1[0]-0.2+e1_sgn[0], e1[1]-0.2+e1_sgn[1], f'$e_1$', fontsize=14, color=color_original)
    e2_sgn = 0.4 * np.array([[1] if i==0 else [i] for i in np.sign(e2)])
    ax.text(e2[0]-0.2+e2_sgn[0], e2[1]-0.2+e2_sgn[1], f'$e_2$', fontsize=14, color=color_original)
    
    e1_transformed = T(e1)
    e2_transformed = T(e2)
    
    plt.quiver([0, 0],[0, 0], [e1_transformed[0], e2_transformed[0]], [e1_transformed[1], e2_transformed[1]], 
               color=color_transformed, angles='xy', scale_units='xy', scale=1)
    plt.plot([0,e2_transformed[0], e1_transformed[0]+e2_transformed[0], e1_transformed[0]], 
             [0,e2_transformed[1], e1_transformed[1]+e2_transformed[1], e1_transformed[1]], 
             color=color_transformed)
    e1_transformed_sgn = 0.4 * np.array([[1] if i==0 else [i] for i in np.sign(e1_transformed)])
    ax.text(e1_transformed[0][0]-0.2+e1_transformed_sgn[0], e1_transformed[1][0]-e1_transformed_sgn[1][0], 
            f'$T(e_1)$', fontsize=14, color=color_transformed)
    e2_transformed_sgn = 0.4 * np.array([[1] if i==0 else [i] for i in np.sign(e2_transformed)])
    ax.text(e2_transformed[0][0]-0.2+e2_transformed_sgn[0][0], e2_transformed[1][0]-e2_transformed_sgn[1][0], 
            f'$T(e_2)$', fontsize=14, color=color_transformed)
    
    plt.gca().set_aspect("equal")
    plt.show()
    
plot_transformation(T_hscaling, e1, e2)

You can observe that the polygon has been stretched in the horizontal direction as a result of the transformation.

4.2 - Example 2: Reflection about y-axis (the vertical axis)

Function T_reflection_yaxis() defined below corresponds to the reflection about y-axis:

def T_reflection_yaxis(v):
    A = np.array([[-1,0], [0,1]])
    w = A @ v
    
    return w
    
e1 = np.array([[1], [0]])
e2 = np.array([[0], [1]])
 
transformation_result_reflection_yaxis = transform_vectors(T_reflection_yaxis, e1, e2)
 
print("Original vectors:\n e1= \n", e1,"\n e2=\n", e2, 
      "\n\n Result of the transformation (matrix form):\n", transformation_result_reflection_yaxis)

You can visualize this transformation:

plot_transformation(T_reflection_yaxis, e1, e2)

5 - Application of Linear Transformations: Computer Graphics

There are many more standard linear transformations to explore. But now you have the required tools to apply them and visualize the results.

A large number of basic geometric shapes is used in computer graphics. Such shapes (e.g. triangles, quadrilaterals) are defined by their vertexes (corners). Linear transformations are often used to generate complex shapes from the basic ones, through scaling, reflection, rotation, shearing etc. It provides opportunity to manipulate those shapes efficiently.

The software responsible for rendering of graphics, has to process the coordinates of millions of vertexes. The use of matrix multiplication to manipulate coordinates helps to merge multiple transformations together, just applying matrix multiplication one by one in a sequence. And another advantage is that the dedicated hardware, such as Graphics Processing Units (GPUs), is designed specifically to handle these calculations in large numbers with high speed.

So, matrix multiplication and linear transformations give you a super power, especially on scale!

Here is an example where linear transformations could have helped to reduce the amount of work preparing the image:

All of the subleafs are similar and can be prepared as just linear transformations of one original leaf.

Let's see a simple example of two transformations applied to a leaf image. For the image transformations you can use an OpenCV library. First, upload and show the image:

img = cv2.imread('images/leaf_original.png', 0)
plt.imshow(img)

Of course, this is just a very simple leaf image (not a real example in preparation of the proper art work), but it will help you to get the idea how a few transformations can be applied in a row. Try to rotate the image 90 degrees clockwise and then apply a shear transformation, which can be visualized as:

Rotate the image:

image_rotated = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)
 
plt.imshow(image_rotated)

Applying the shear you will get the following output:

rows,cols = image_rotated.shape
# 3 by 3 matrix as it is required for the OpenCV library, don't worry about the details of it for now.
M = np.float32([[1, 0.5, 0], [0, 1, 0], [0, 0, 1]])
image_rotated_sheared = cv2.warpPerspective(image_rotated, M, (int(cols), int(rows)))
plt.imshow(image_rotated_sheared)

What if you will apply those two transformations in the opposite order? Do you think the result will be the same? Run the following code to check that:

image_sheared = cv2.warpPerspective(img, M, (int(cols), int(rows)))
image_sheared_rotated = cv2.rotate(image_sheared, cv2.ROTATE_90_CLOCKWISE)
plt.imshow(image_sheared_rotated)

Comparing last two images, you can clearly see that the outputs are different. This is because linear transformation can be defined as a matrix multiplication. Then, applying two transformations in a row, e.g. with matrices AA and BB, you perform multiplications B(Av)=(BA)vB(Av)=(BA)v, where vv is a vector. And remember, that generally you cannot change the order in the matrix multiplication (most of the time BAABBA\neq AB). Let's check that! Define two matrices, corresponding to the rotation and shear transformations:

M_rotation_90_clockwise = np.array([[0, 1], [-1, 0]])
M_shear_x = np.array([[1, 0.5], [0, 1]])
 
print("90 degrees clockwise rotation matrix:\n", M_rotation_90_clockwise)
print("Matrix for the shear along x-axis:\n", M_shear_x)

Now check that the results of their multiplications M_rotation_90_clockwise @ M_shear_x and M_shear_x @ M_rotation_90_clockwise are different:

print("M_rotation_90_clockwise by M_shear_x:\n", M_rotation_90_clockwise @ M_shear_x)
print("M_shear_x by M_rotation_90_clockwise:\n", M_shear_x @ M_rotation_90_clockwise)

This simple example shows that you need to be aware of the mathematical objects and their properties in the applications.

Congratulations on completing this lab!

On this page

Edit on Github Question? Give us feedback