Resize, crop, flip and rotate

OpenCV · Essentials · 10 min

Nearly every model and pipeline needs images at a fixed size and orientation, and the wrong interpolation costs quality.

Cropping needs no cv2 call: slice the array, img[y1:y2, x1:x2]. The result is a view, so drawing on it changes the original; add .copy() for an independent crop.

cv2.resize(img, dsize, fx=..., fy=..., interpolation=...) scales an image. Either pass an explicit size as `(width, height)` or pass dsize=None with scale factors fx, fy. To keep the aspect ratio, compute one side from the other.

The interpolation choice matters:

  • cv2.INTER_AREA: best for shrinking; averages pixels and avoids moire
  • cv2.INTER_LINEAR (default): fast and fine for moderate enlarging
  • cv2.INTER_CUBIC or INTER_LANCZOS4: sharper enlargements, slower
  • cv2.INTER_NEAREST: no blending; use it for label masks and segmentation maps, where averaging would invent classes that don't exist

cv2.flip(img, code) mirrors: 1 horizontally, 0 vertically, -1 both. Horizontal flips are a common data augmentation.

cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE) (also ROTATE_180, ROTATE_90_COUNTERCLOCKWISE) rotates by right angles exactly, swapping width and height for 90-degree turns. Arbitrary angles need an affine warp, covered later.

Many models also expect letterboxing: resize to fit inside the target while keeping aspect ratio, then pad the rest with cv2.copyMakeBorder.

Example

import cv2
import numpy as np

img = np.random.default_rng(0).integers(0, 256, (480, 640, 3), dtype=np.uint8)

crop = img[100:300, 200:500]                 # rows y, then cols x
print(crop.shape)                            # (200, 300, 3)

small = cv2.resize(img, (320, 240), interpolation=cv2.INTER_AREA)  # (w, h)!
print(small.shape)                           # (240, 320, 3)

half = cv2.resize(img, None, fx=0.5, fy=0.5)
print(half.shape)                            # (240, 320, 3)

target_w = 200                               # keep aspect ratio
h, w = img.shape[:2]
fit = cv2.resize(img, (target_w, round(h * target_w / w)), interpolation=cv2.INTER_AREA)
print(fit.shape)                             # (150, 200, 3)

mirrored = cv2.flip(img, 1)
print((mirrored[:, 0] == img[:, -1]).all())  # True
print(cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE).shape)   # (640, 480, 3)

Remember

  • Crop by slicing img[y1:y2, x1:x2] (a view)
  • cv2.resize takes (width, height) or scale factors
  • INTER_AREA to shrink, LINEAR/CUBIC to enlarge, NEAREST for masks
  • cv2.flip and cv2.rotate handle mirrors and right-angle turns

Watch out for

  • Resizing a segmentation mask with the default linear interpolation, which creates blended label values.

Check yourself

  1. `cv2.resize(img, (100, 50))` on a (200, 400, 3) image returns which shape?
  2. Which interpolation should you use to resize an integer class-label mask?

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/d54/group__imgproc__transform.html