Move every pixel somewhere else with one small matrix: shift, rotate, scale, shear, or change the point of view.
Affine transform: a linear mapping (plus a shift) that keeps points, straight lines and planes.
Preserved:
Not preserved: angles; distances (except along parallel lines).
Examples: translation, rotation, scaling, shearing. Note the grid: its lines stay straight and parallel.
2×3 matrix
$$M = \begin{bmatrix} a & b & t_x \\ c & d & t_y \end{bmatrix}$$Transformation
$$\begin{bmatrix} x' \\ y' \end{bmatrix} = \begin{bmatrix} a & b \\ c & d \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} + \begin{bmatrix} t_x \\ t_y \end{bmatrix}$$
import cv2
import numpy as np
# define 3 points in the source
pts1 = np.float32([[50, 50], [200, 50], [50, 200]]) # ans:
# define 3 points in the destination
pts2 = np.float32([[10, 100], [200, 50], [100, 250]]) # ans:
# compute the affine matrix
M = cv2.getAffineTransform(pts1, pts2) # ans:
# ans: M is a 2x3 matrix
# apply the transform
h, w = img.shape[:2]
result = cv2.warpAffine(img, M, (w, h)) # ans:
Perspective simulates a 3-D viewing angle.
Document scanning, bird's-eye view, rectification.
A 3×3 homography matrix.
3×3 homogeneous matrix
$$H = \begin{bmatrix} h_{11} & h_{12} & h_{13} \\ h_{21} & h_{22} & h_{23} \\ h_{31} & h_{32} & h_{33} \end{bmatrix}$$Transformation
$$\begin{bmatrix} x' \\ y' \\ 1 \end{bmatrix} = \frac{1}{w} \begin{bmatrix} h_{11} & h_{12} & h_{13} \\ h_{21} & h_{22} & h_{23} \\ h_{31} & h_{32} & h_{33} \end{bmatrix} \begin{bmatrix} x \\ y \\ 1 \end{bmatrix}$$\(w = h_{31}x + h_{32}y + h_{33}\): dividing by it is what makes far things smaller.
You need 4 point pairs to solve for it (8 unknowns, since \(h_{33} = 1\)).
# define 4 points in the source (corners of a document)
pts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]]) # ans:
# define 4 points in the destination (a rectangle)
pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]]) # ans:
# compute the perspective matrix
M = cv2.getPerspectiveTransform(pts1, pts2) # ans:
# ans: M is a 3x3 homography matrix
# apply the transform
result = cv2.warpPerspective(img, M, (300, 300)) # ans:
Rule: use affine if parallel lines stay parallel; perspective otherwise.
Shear slants the shape of the image.
Horizontal
$$M = \begin{bmatrix} 1 & s_x & 0 \\ 0 & 1 & 0 \end{bmatrix}$$Vertical
$$M = \begin{bmatrix} 1 & 0 & 0 \\ s_y & 1 & 0 \end{bmatrix}$$
# horizontal shear
M = np.float32([[1, 0.5, 0], # ans:
[0, 1, 0]]) # ans:
sheared = cv2.warpAffine(img, M, (w + int(h*0.5), h)) # ans:
# vertical shear
M = np.float32([[1, 0, 0],
[0.5, 1, 0]])
sheared = cv2.warpAffine(img, M, (w, h + int(w*0.5))) # ans:
# combined shear
M = np.float32([[1, 0.3, 0],
[0.2, 1, 0]])
sheared = cv2.warpAffine(img, M, (int(w*1.3), int(h*1.2))) # ans:
# ans: the output size grows so the slanted image still fits
Answer first, then press → to check.
What is an affine transform?
A linear mapping that preserves parallelism and ratios.
What size is the affine matrix?
2×3 (6 parameters).
How many points define an affine transform?
3 point pairs.
What is a perspective transform?
A 3-D projection that allows vanishing points.
How many points for a perspective transform?
4 point pairs.
What does affine preserve?
Parallelism, ratios of distances, straight lines.
What does perspective NOT preserve?
Parallelism: parallel lines can converge.
When do you use affine vs perspective?
Affine if parallelism is kept; perspective for 3-D effects.
What is shearing?
A slanting transformation.
What size is the perspective matrix?
A 3×3 homography (8 free parameters).
# affine transform from 3 points
pts1 = np.float32([[50, 50], [200, 50], [50, 200]])
pts2 = np.float32([[10, 100], [200, 50], [100, 250]])
M = cv2.getAffineTransform(pts1, pts2) # ans:
h, w = img.shape[:2]
result = cv2.warpAffine(img, M, (w, h)) # ans:
# perspective transform (document scan)
pts1 = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]])
pts2 = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])
M = cv2.getPerspectiveTransform(pts1, pts2) # ans:
result = cv2.warpPerspective(img, M, (300, 300)) # ans:
# horizontal shear by 0.3
M = np.float32([[1, 0.3, 0], # ans:
[0, 1, 0]]) # ans:
h, w = img.shape[:2]
sheared = cv2.warpAffine(img, M, (int(w + h*0.3), h)) # ans:
# combine rotation and scaling in one affine
center = (w // 2, h // 2)
M = cv2.getRotationMatrix2D(center, 45, 1.5) # ans:
result = cv2.warpAffine(img, M, (w, h)) # ans: