Logo

What are matrices doing, again?

Jan 16, 2025

Here are two things that linear algebra offers us while using matrices:

Let’s examine each one individually in the sections below.

A matrix can encode a system of linear equations

Solving a system of linear equations by substitution method can be laborious:

(#1) 2x - 3y + z = -20
(#2) 4x + y - 2z = 29
(#3) x + 5y + 3z = 23

(isolate x in #3) x = 23 - 5y - 3z

(substitute x in #1) -18y - 8z = -66
(substitute x in #2) -19y - 10z = -63

(rinse and repeat)

(result) x = 3, y = 7, z = -5

We can reach the solution above with less work by representing the equations as a matrix:

(from #1) [ 2 -3  1 ]
(from #2) [ 4  1 -2 ]
(from #3) [ 1  5  3 ]

Then multiply x,y,z by this matrix, and bring the right side of values of the original system of equations to our right side (-20, 29, and 23):

[ x ]   [ 2 -3  1 ]   [ -20 ]
[ y ] x [ 4  1 -2 ] = [  29 ]
[ z ]   [ 1  5  3 ]   [  23 ]

(some gaussian elimination goes here)

[ x ]   [  3  ]
[ y ] = [  7  ]
[ z ]   [ -5  ]

We found the same result, but much more efficiently.

A matrix can encode a vector transformation

Another incredibly clever use of matrices is to transform a vector in its vector space. Imagine your vector as an arrow and a matrix as something that can rotate, shift, tilt, mirror, scale up, or scale down that arrow of yours. Even more: a single matrix can encode n vector transformations.

Take the following 2D plane representing an image of 5x5 pixels:

□ □ ■ □ □
□ ■ □ □ □
■ □ □ □ □
□ ■ □ □ □
□ □ ■ □ □

For simplicity, pixels have only two possible values, 0 stands for empty pixel square and 1 for filled pixel square.

Now that we have the character “<” (less then), in a low 5x5 resolution font, let’s demonstrate how a matrix can flip it horizontally, making it “>” (greater then).

First, let’s refer to the reflection matrix:

[ -1 0 ]
[  0 1 ]

This matrix flips the image horizontally, but not around the center axis of the image.

To fix this, we can use the following transformation that ‘shifts’ to the right the image to the side (by its exact width):

[ width - 1 ]
[     0     ]

Replacing width:

[ 4 ]
[ 0 ]

Combing both transformations into a single matrix, the 2x2 flip followed by the 2x1 shift:

[ -1 0 4 ]
[  0 1 0 ]

Time to apply it on the x,y values of the image:

[ new x ]   [ x ]   [ -1 0 4 ]
[ new y ] = [ y ] x [  0 1 0 ]
[   1   ]   [ 1 ]   [  0 0 1 ]    

new x = -1x + 0y + 4 = -x + 4
new y =  0x + 1y + 0 =  y

Notice how handy it was to make it a triple by appending 1. The constant 1 acts as ‘just add this number’ (+4 to x in our example). This third coordinate we just added is called “homogeneous”.

Multiplying a pixel from the original image by this matrix gives the new pixel location on the new flipped image. Do it for all pixels and you’ll get the expected “>”:

□ □ ■ □ □
□ □ □ ■ □
□ □ □ □ ■
□ □ □ ■ □
□ □ ■ □ □

Flipping an image file

Let’s perform the same vertical flipping but this time on an image file, by using Python. Our input will be this image of a hand pointing left:

Encoding the image as PPM image format (Portable Pixmap Format) will make our life easier. The file format couldn’t be simpler: it encodes exact pixel values without compression.

Converting our file to .ppm:

$ convert pointing-left.png pointing-left.ppm

The script that flips it:

from imageio.v2 import imread, imwrite # pip install imageio
import numpy as np                     # pip install numpy

def flip_horizontal(input_pixels):

    # image height and width in pixels
    height, width, _ = input_pixels.shape
    
    # declaring our 'flip then shift' transformation matrix
    matrix_row_1 = [-1, 0, width - 1]
    matrix_row_2 = [0, 1, 0]
    matrix_row_3 = [0, 0, 1]
    np_matrix = np.array([ matrix_row_1, matrix_row_2, matrix_row_3 ])

    # all possible x,y coordinate
    coordinates = np.array([[x, y, 1] for y in range(height) for x in range(width)])

    # apply the transformation matrix to the coordinates
    flipped_coordinates = coordinates @ np_matrix.T

    # declaring the output pixels
    output_pixels = np.zeros_like(input_pixels)

    # Map the pixels to their new positions
    for (x, y, _), (new_x, new_y, _) in zip(coordinates, flipped_coordinates):
        output_pixels[new_y, new_x] = input_pixels[y, x]

    return output_pixels

if __name__ == '__main__':

    # load the input image
    input_pixels = imread('pointing-left.ppm')

    # perform the flip
    output_pixels = flip_horizontal(input_pixels)

    # save the flipped image as output
    imwrite('pointing-right.ppm', output_pixels, format='PPM')

    print(f'Flipped image saved succesfully')

Run it and you should have the resulting image, pointing to the right:

It’s important to point out that the example above is just illustrative, in reality it is much easier to rely on NumPy’s slicing:

flipped_pixels = pixels[:, ::-1, :]