2D Only Perspective Transform

One project that came up during my Master's program involved taking an arbitrarily manipulated pair of vectors (to form an oriented quad) in 2D and extending the information to derive the single-point perspective it represents. The goal was to subdivide the provided quad in a perspective-aware manner. The two main ways to approach this problem involve using either a recursive solution that leveraged vector intersection techniques or creating a linear transformation matrix that represented the simulated perspective directly. Since the matrix approach can provide a direct computation of arbitrary results, I opted to derive a solution for that.

Perspective mapping example

To make things easier, the solution maps a unit square to the square defined by the pair of vectors. In other words:

\begin{align} (0, 0, 1) &\mapsto A \\ (0, 1, 1) &\mapsto B \\ (1, 0, 1) &\mapsto C \\ (1, 1, 1) &\mapsto D \end{align}

Using the above notation, it is possible to start calculating the matrix which will take any 2D point in unit coordinates and convert it to projected coordinates.

\[ \left[ { \begin{array}{ccc} M_{11} & M_{12} & M_{13} \\ M_{21} & M_{22} & M_{23} \\ M_{31} & M_{32} & 1 \end{array} } \right] \]

With the mapping to point \(A\) being straightforward, two entries can already be solved for (subscripts \(x\) and \(y\) are for the 2D components):

\begin{align} M_{13} &= A_x \\ M_{23} &= A_y \end{align}

Using the unit mappings with the matrix and factoring in the perspective divide required for points gets the following system of equations for matrix elements to known points.

\begin{align} {M_{12} + A_x}\over{M_{32} + 1} &= B_x \\ \\ {M_{22} + A_y}\over{M_{32} + 1} &= B_y \\ \\ {M_{11} + A_x}\over{M_{31} + 1} &= C_x \\ \\ {M_{22} + A_x}\over{M_{31} + 1} &= C_y \\ \\ {M_{11} + M_{12} + A_x}\over{M_{31} + M_{32} + 1} &= D_x \\ \\ {M_{21} + M_{22} + A_x}\over{M_{31} + M_{32} + 1} &= D_y \end{align}

Multiplying both sides by the left-hand denominator and arranging all matrix terms to the left and all constant terms to the right changes the equations to:

\begin{align} M_{12} - B_{x}M_{32} &= B_x - A_x \\ M_{22} - B_{y}M_{32} &= B_y - A_y \\ M_{11} - C_{x}M_{31} &= C_x - A_x \\ M_{21} - C_{y}M_{31} &= C_y - A_y \\ M_{11} + M_{12} - D_{x}M_{31} - D_{x}M_{32} &= D_x - A_x \\ M_{21} + M_{22} - D_{y}M_{31} - D_{y}M_{32} &= D_y - A_y \end{align}

Setting up the equations to perform Gaussian Elimination on them yields:

\[ \begin{array}{cccccccc} M_{11} & M_{12} & M_{21} & M_{22} & M_{31} & M_{32} \\ 1 & 0 & 0 & 0 & -C_x & 0 & = & C_x - A_x \\ 0 & 1 & 0 & 0 & 0 & -B_x & = & B_x - A_x \\ 0 & 0 & 1 & 0 & -C_y & 0 & = & C_y - A_y \\ 0 & 0 & 0 & 1 & 0 & -B_y & = & B_y - A_y \\ 1 & 1 & 0 & 0 & -D_x & -D_x & = & D_x - A_x \\ 0 & 0 & 1 & 1 & -D_y & -D_y & = & D_y - A_y \end{array} \]

The top four rows are already in a nice enough form that their equations can be extracted directly:

\begin{align} M_{11} &= (1 + M_{31})C_x - A_x \\ M_{12} &= (1 + M_{32})B_x - A_x \\ M_{21} &= (1 + M_{31})C_y - A_y \\ M_{22} &= (1 + M_{32})B_y - A_y \end{align}

Subtracting out the first two rows from the fifth row and the middle two rows from the last row results in:

\[ \begin{array}{cccccccc} 0 & 0 & 0 & 0 & C_x - D_x & B_x - D_x & = & D_x + A_x - B_x - C_x \\ 0 & 0 & 0 & 0 & C_y - D_y & B_y - D_y & = & D_y + A_y - B_y - C_y \end{array} \]

These two equations serve as the starting point for determining the \(M_{31}\) and \(M_{32}\) terms. Focusing on simplifying the \(M_{31}\) term first through division yields:

\[ \begin{array}{cccccccc} 0 & 0 & 0 & 0 & 1 & {B_x - D_x}\over{C_x - D_x} & = & {D_x + A_x - B_x - C_x}\over{C_x - D_x} \\ 0 & 0 & 0 & 0 & 1 & {B_y - D_y}\over{C_y - D_y} & = & {D_y + A_y - B_y - C_y}\over{C_y - D_y} \end{array} \]

Subtraction of the top equation from the bottom and subsequent division results in the very large (but ultimately very simple) equation for \(M_{32}\):

\begin{align} \left( { \frac{B_y - D_y}{C_y - D_y} - \frac{B_x - D_x}{C_x - D_x} } \right) M_{32} &= \frac{D_y + A_y - B_y - C_y}{C_y - D_y} - \frac{D_x + A_x - B_x - C_x}{C_x - D_x} \\ M_{32} &= \frac{\frac{D_y + A_y - B_y - C_y}{C_y - D_y} - \frac{D_x + A_x - B_x - C_x}{C_x - D_x}}{\frac{B_y - D_y}{C_y - D_y} - \frac{B_x - D_x}{C_x - D_x}} \end{align}

Dividing out the \(M_{32}\) term instead before subtracting the bottom equation from the top and dividing solves for \(M_{31}\).

\[ \begin{array}{cccccccc} 0 & 0 & 0 & 0 & {C_x - D_x}\over{B_x - D_x} & 1 & = & {D_x + A_x - B_x - C_x}\over{B_x - D_x} \\0 & 0 & 0 & 0 & {C_y - D_y}\over{B_y - D_y} & 1 & = & {D_y + A_y - B_y - C_y}\over{B_y - D_y} \end{array} \]$$ M_{31} = \frac{\frac{D_x + A_x - B_x - C_x}{B_x - D_x} - \frac{D_y + A_y - B_y - C_y}{B_y - D_y}}{\frac{C_x - D_x}{B_x - D_x} - \frac{C_y - D_y}{B_y - D_y}} $$

Continuing forward with \(M_{31}\) and using algebraic techniques to consolidate and simplify terms brings about:

\begin{align} M_{31} =& \frac{(D_x + A_x - B_x - C_x)(B_y - D_y) - (D_y + A_y - B_y - C_y)(B_x - D_x)}{(B_x - D_x)(B_y - D_y)} \\ &\cdot \frac{(B_x - D_x)(B_y - D_y)}{(B_y - D_y)(C_x - D_x) - (B_x - D_x)(C_y - D_y)} \\ \\ =& \frac{(D_x + A_x - B_x - C_x)(B_y - D_y) - (D_y + A_y - B_y - C_y)(B_x - D_x)}{(B_y - D_y)(C_x - D_x) - (B_x - D_x)(C_y - D_y)} \\ \\ =& { \scriptsize \frac{(A_x - C_x)(B_y - D_y) - (B_x - D_x)(B_y - D_y) - (A_y - C_y)(B_x - D_x) + (B_x - D_x)(B_y - D_y)}{(B_y - D_y)(C_x - D_x) - (B_x - D_x)(C_y - D_y)} } \\ \\ =& \frac{(A_x - C_x)(B_y - D_y) - (A_y - C_y)(B_x - D_x)}{(B_y - D_y)(C_x - D_x) - (B_x - D_x)(C_y - D_y)} \end{align}

Doing the same for \(M_{32}\):

\begin{align} M_{32} =& \frac{(D_y + A_y - B_y - C_y)(C_x - D_x) - (D_x + A_x - B_x - C_x)(C_y - D_y)}{(C_y - D_y)(C_x - D_x)} \\ &\cdot \frac{(C_y - D_y)(C_x - D_x)}{(B_y - D_y)(C_x - D_x) - (B_x - D_x)(C_y - D_y)} \\ \\ =& \frac{(D_y + A_y - B_y - C_y)(C_x - D_x) - (D_x + A_x - B_x - C_x)(C_y - D_y)}{(B_y - D_y)(C_x - D_x) - (B_x - D_x)(C_y - D_y)} \\ \\ =& {\scriptsize \frac{(A_y - B_y)(C_x - D_x) - (C_x - D_x)(C_y - D_y) - (A_x - B_x)(C_y - D_y) + (C_x - D_x)(C_y - D_y)}{(B_y - D_y)(C_x - D_x) - (B_x - D_x)(C_y - D_y)} } \\ \\ =& \frac{(A_y - B_y)(C_x - D_x) - (A_x - B_x)(C_y - D_y)}{(B_y - D_y)(C_x - D_x) - (B_x - D_x)(C_y - D_y)} \end{align}

With the equations simplified it becomes a little easier to see that it is a combination of several vectors based on the provided \(A, B, C, D\) points. Also notable is that \(M_{31}\) and \(M_{32}\) share a denominator. Using \(^{\curvearrowleft}\) to be mean rotating a vector counter-clockwise by \(90^{\circ}\), the equations for each matrix component become:

\begin{align} u &= \overrightarrow{DC} \cdot \overrightarrow{DB}^{\curvearrowleft} \\ M_{31} &= \frac{\overrightarrow{CA} \cdot \overrightarrow{DB}^{\curvearrowleft}}{u} \\ M_{32} &= \frac{\overrightarrow{DC} \cdot \overrightarrow{BA}^{\curvearrowleft}}{u} \\ \\ M_{11} &= (1 + M_{31})C_x - A_x \\ M_{12} &= (1 + M_{32})B_x - A_x \\ M_{21} &= (1 + M_{31})C_y - A_y \\ M_{22} &= (1 + M_{32})B_y - A_y \end{align}

Placing these calculated values in the matrix allows for transforming arbitrary points from normalized space into the projected space defined by \(\overrightarrow{AB}\) and \(\overrightarrow{CD}\). It is important to divide by the resulting \(z\) term to normalize the resulting 2D coordinate just as you would divide by the resulting \(w\) term for a 3D perspective transformation.

Show Comments