Affine and perspective warps

OpenCV · Advanced · 15 min

Aligning, straightening and augmenting images all rely on warps, and the matrix conventions are easy to get wrong.

An affine transform (rotation, scale, translation, shear) keeps parallel lines parallel and is a 2x3 float matrix. Build one and apply it:

M = cv2.getRotationMatrix2D(center, angle_deg, scale)   # positive = counter-clockwise
out = cv2.warpAffine(img, M, (w, h))

cv2.getAffineTransform(src3, dst3) builds the matrix from three point pairs. The output size is the canvas: rotating within the original size cuts off corners, so for a full rotation compute the new bounding size and add a translation to the matrix.

A perspective transform (homography) is a 3x3 matrix that maps any quadrilateral to any other. It is how you turn a photo of a tilted document or a whiteboard into a flat top-down view: cv2.getPerspectiveTransform(src4, dst4) from four corners (float32 arrays), then cv2.warpPerspective(img, H, (w, h)).

To transform points rather than images, use cv2.transform(pts, M) for affine matrices and cv2.perspectiveTransform(pts, H) for homographies; points need shape (n, 1, 2) and a float dtype.

Warps sample source pixels for each destination pixel, so flags= picks interpolation (INTER_NEAREST for masks), and borderMode/borderValue fill uncovered regions. cv2.invertAffineTransform and np.linalg.inv(H) map back.

Example

import cv2
import numpy as np

img = np.zeros((100, 200, 3), np.uint8)
cv2.rectangle(img, (80, 30), (120, 70), (255, 255, 255), -1)

M = cv2.getRotationMatrix2D((100, 50), 90, 1.0)   # 90 deg counter-clockwise
print(M.shape)                                    # (2, 3)
rot = cv2.warpAffine(img, M, (200, 100))
print(rot.shape)                                  # (100, 200, 3) same canvas

src = np.float32([[20, 10], [180, 30], [190, 90], [10, 80]])   # tilted quad
dst = np.float32([[0, 0], [300, 0], [300, 200], [0, 200]])     # flat page
H = cv2.getPerspectiveTransform(src, dst)
page = cv2.warpPerspective(img, H, (300, 200))
print(page.shape)                                 # (200, 300, 3)

pts = src.reshape(-1, 1, 2)
print(cv2.perspectiveTransform(pts, H).reshape(-1, 2).round().astype(int))
# [[  0   0] [300   0] [300 200] [  0 200]]

Remember

  • Affine = 2x3 matrix, applied with warpAffine; perspective = 3x3, warpPerspective
  • getRotationMatrix2D uses degrees, positive is counter-clockwise
  • Four point pairs give a homography that can flatten a document
  • Transform points with cv2.transform / perspectiveTransform, shape (n, 1, 2) float

Watch out for

  • Passing int32 point arrays to getPerspectiveTransform, which requires float32.

Check yourself

  1. How many point correspondences does cv2.getPerspectiveTransform need?
  2. You rotate an image by 30 degrees with warpAffine and the corners are cut off. Why?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.opencv.org/4.x/da/d6e/tutorial_py_geometric_transformations.html