ONLamp.com    
 Published on ONLamp.com (http://www.onlamp.com/)
 See this if you're having trouble printing code examples


Numerically Python

Transformation Matrices

07/05/2000

This month we'll use NumPy and DISLIN to create, display, and manipulate a basic geometric image. At the heart of image manipulation are special matrices that can be used to create effects. You can use these effects separately or combine them for more complex operations. While the focus is on two-dimensional operations, the concepts extend to three (and more) dimensions. They are fundamental to many applications including computer games and computer-aided design (CAD) programs. They are the heart of linear algebra itself.

So what's a vector?

Linear algebra is the math of shaped collections of numbers. The mathematics we are most familiar with deals with numbers called scalars. We use scalars in our lives to represent things like the amount in our bank account ($10.08), the speed we drive (65 miles per hour), or the time it takes for popcorn to pop in the microwave oven (3 minutes 40 seconds). Each of these values is a magnitude of some quantity that interests us. Often we use combinations of values to represent more complex information. For example, there is a difference between the speed of an object (say your car) and its velocity. One is simple magnitude (speed: how fast you are going); the other implies both speed and direction (velocity). Your speed cannot be negative, but your velocity could be. Think about your car moving in reverse. You are moving so you have speed, but the direction is "backwards," giving you a negative velocity. Speed is a scalar value, but velocity is expressed as a vector, a combination of two numbers that imply both direction and magnitude. Assuming your car is driven on a flat road, only two numbers are needed to represent the direction of movement.

Considering the world as a flat plane, we can lay on it two reference directions. The arrows labeled x and y provide a reference for our vector [4,3].

Figure 1

The vector [4,3] is defined as being four units in the x direction and 3 units in the y direction (units, in this case, being miles traveled.) So how do we get from the motion of a car to a vector of [4,3]? Well we would say that the velocity of the car is [4,3] miles per hour. Which means that the car moved four miles in the x direction and three in the y in the last hour. The actual motion of the car is the length of the vector, computed via:

Equation: length = sqroot(x^2 + y^2)

This is just the Pythagorean theorem, which seems to pop up everywhere! Continuing with the example, the car moved a total of five miles in the course of a single hour and as such went five miles per hour.

If we want to describe motion of objects that can move in more dimensions (say an airplane - which can move "up" and "down" as well), we have to use vectors with more elements (three for the airplane).

Vectors are used to represent many things -- the velocity of an automobile is but one real example. In general, the concept of a vector is abstract. You don't need to know what a vector represents in order to manipulate it, although knowing sometimes helps you understand what you are doing. The mathematics for manipulating vectors can be considered separately from the implementation of a particular problem.

Instead of graphing velocity, we will use vectors to draw a basic image. We will then focus on the manipulation of this image via special matrices and matrix mathematics.

Vector mathematics

To see how this works, let's review some basic operations on vectors. The addition of two vectors can be shown graphically. It works as might be expected: Two vectors [4,3] and [1,2] add up to [5,5]. Here is how it looks in a graph:

Figure 2

Notice how the first vector starts from the origin (0,0). The second vector is then placed with its tail (the end with no arrow head) to the head of the first. The combination of the two is the same as a vector starting from the origin connecting to the head of the second one. Subtraction follows in a similar fashion, but reverses the direction of the second vector.

Graphing addition like this, a collection of vectors (each representing a line) can be used to create basic images, a vector version of connect-the-dots. Although our example is simple in nature, many vectors can be combined to generate complex drawings.

Let's try an example to illustrate what can be done. Following is a complete Python program that uses both the Numeric (NumPy) module as well as pxDislin. What is new from last month is the use of the dMultiLine function, which draws a line (vector) between points of x_list and y_list arguments. Essentially, the dMultiLine function is performing a vector addition operation. The function does not actually do the math, but it does graphically represent the operation. The points chosen trace out a simple box structure of a house centered on the plot axis.

from pxDislin import *
from Numeric import *

plot = dPlot()
axis = dAxis(-20,20,-20,20)
plot.add_axis(axis)

house_pts = array([[5,5,-5,-5,5,4,4,2,2,5,5,0,-5],
                      [-5,5,5,-5,-5,-5,-1,-1,-5,-5,5,8,5]])

house     = dMultiLine(house_pts[0],house_pts[1])
plot.add_object(house)

plot.show()

The generated plot is:

Figure 3: resulting plot.

The points that draw out the house are essentially a 2-by-13 matrix:

Points can be represented in a 2x13 matrix.

where the x coordinates are in the top row and the y coordinates are the bottom row. Each column of the matrix may be taken as a 2-by-1 vector, and as such the matrix is a collection of 13 column vectors.

Matrix multiplication

Matrix multiplication is fundamental to image manipulation. It is a mapping function. We "map" a vector from one frame of reference to another. Looking back to the first article in our series, the multiplication of two matrices was shown to be

Equation 2

Let's look at how this works for a 2-by-2 matrix multiplied by a 2-by-1 vector. Here is how we will reference the values in these matrices:

Equation 3

The result is a 2-by-1 vector transformed by matrix A. Expanding out the math below, notice the interaction between the terms: each value of the result is a combination of several values of the input.

Equation 4

Matrix A is a transformation matrix, and its values, when mapped to vector b, transform it. The correct choice of our transformation matrix is fundamental to creating the right effect for our 2-by-13 house matrix.

While we could manipulate each of the 2-by-1 vectors in our set of 13 individually, that would be sloppy. Linear algebra rescues us from the tedium. The matrix multiplication problem we looked at before readily extends to larger matrices. A 2-by-2 matrix multiplied by a 2-by-1 vector results in a 2-by-1 vector, but a 2-by-2 matrix multiplied by a 2-by-13 matrix results in another 2-by-13 matrix.

In essence each column of the original matrix is transformed by the matrix multiplication into the corresponding column of the result matrix. With matrix mathematics, we do the operation in one step!

Image manipulation

Let's add a transformation matrix to our example code before we generate the plot. To keep things simple, for the moment we'll use an identity matrix. Using an identity matrix is the equivalent of multiplying everything by 1 -- a kind of "do nothing" operation. However, it does provide us a way to make sure "the math works," and the identity matrix is the basis for most transformation matrices.

from pxDislin import *
from Numeric import *

plot = dPlot()
trans = array([[1,0],
                  [0,1]])

axis = dAxis(-20,20,-20,20)
plot.add_axis(axis)

house_pts_orig = array([[5,5,-5,-5,5,4,4,2,2,5,5,0,-5],
                  [-5,5,5,-5,-5,-5,-1,-1,-5,-5,5,8,5]])

house_pts = matrixmultiply(trans,house_pts_orig)

house     = dMultiLine(house_pts[0],house_pts[1])
plot.add_object(house)

plot.show()

The plot generated is the same as the first one shown.

Transformations

We'll look at four basic transformations. From these simple operations, more complex functions can be constructed.

Rotation

A rotation matrix will allow us to spin the image clockwise or counterclockwise around the origin. This matrix makes use of the trigonometric functions sine and cosine. The trigonometric functions take as an argument an angle of rotation; positive angles will give a counterclockwise rotation, negative values a clockwise one. Angles are commonly measured on two scales. Most people are experienced with units of degrees -- a complete circle has 360 degrees. Computers measure angles in units of radians. To a computer, a circle has radians. We will convert degrees to radians by multiplying the degrees of our angle by pi divided by 180. That will give us the correct values for our matrix. The rotation matrix itself is defined as:

Equation 5

where theta represents the angle of rotation we desire.

After replacing the identity matrix in the example code with the following and running the code, we see the house is now rotated counterclockwise by 30 degrees.

angle = 30 * pi/180
trans     = array([[cos(angle), -sin(angle)],
                   [sin(angle), cos(angle)]])
the house is now rotated counterclockwise by 30 degrees

Using the rotation matrix, any point or collection of points can be easily rotated around the center point (0,0).

Reflection
Next on the list of transformations is the reflection matrix. A reflection matrix will cause the image to be flipped, or reflected across an axis. An example of a reflection matrix that causes all the y values to be negated (thus flipping the image across the x axis) is:

Equation 6

Switching the rotation matrix with the reflection one generates the following:

Switching the rotation matrix with the reflection one.

Try to figure the reflection matrix that negates only the x coordinates. Is there a matrix that negates both the x and y coordinates at the same time?

Scale
Another simple transformation is the scale matrix. The identity matrix can be modified to have values of other than one on the diagonal. As such, it becomes a scale matrix, changing only the size of the drawing. This matrix can be used to both scale up, a>1, and scale down, a<1 :

Equation 7

Shear
The last matrix we will explore is the "shear" matrix. This causes a shear motion in one particular dimension.

Equation 8

The result can be seen below for a shear matrix where k=2.

Results of a shear matrix.

Combining transformations

Applying more than one transformation matrix can induce multiple actions. In addition, individual transformation matrices can be combined (via a matrix multiply) to form a complex action. Following is the result of combining a shear matrix with a reflection matrix.

Try a few combinations of your own -- see what you can come up with.

Translation

Currently in our plots, the house is centered on the origin of the plot (0,0). Using vector operations, you can move the house away from the center via translation. This is accomplished by simply adding an offset vector to each of the columns in the matrix. Adding a vector of [5,5] will move the drawing as shown:

Using translation

Give the combination of translation and transformation a try. What happens if you rotate a translated image?

End game

Hopefully this only helped whet your appetite for using NumPy and learning more about linear algebra. While not shown here, extending these techniques to more dimensions (three being most common) can allow more interesting applications. In three dimensions, solid objects can be modeled and rotations and reflections can be generated around multiple axes, creating complex object motions. Such actions are fundamental to graphics drawing, modeling packages, and 3D gaming. While the mathematics of linear algebra may be abstract, they are used in common applications. NumPy helps implement these actions clearly, making this difficult topic easier to understand. If you want to play with more complex examples, consult almost any elementary text on linear algebra or books on graphics or image manipulation.

Looking ahead

Next month we will look past the basic NumPy functions to the additional modules provided in the standard package. Using these modules, more complex programs can be written for a wide variety of applications.

Eric Hagemann specializes in fast algorithms for crunching numbers on all varieties of computers from embedded to mainframe.


Read more Numerically Python columns.

Discuss this article in the O'Reilly Network Python Forum.

Return to the Python DevCenter.

 

Copyright © 2009 O'Reilly Media, Inc.