Skip to content

OpenCV

The foundational library for computer vision in Python. OpenCV (cv2) gives you fast I/O, resizing, color space conversion, filtering, edge detection, contour analysis, and video/webcam capture — the building blocks of every vision pipeline.

Table of Contents

Installation / Setup

pip install opencv-python          # or: uv add opencv-python
pip install opencv-contrib-python  # extra modules (SIFT, SURF, etc.)

OpenCV images are NumPy arrays in BGR order (not RGB) — this is the #1 gotcha.

Core Syntax

Image loading and saving

import cv2
import numpy as np

img = cv2.imread("photo.jpg")                   # BGR, dtype uint8
img_gray = cv2.imread("photo.jpg", cv2.IMREAD_GRAYSCALE)
cv2.imwrite("output.png", img)
Flag Description
cv2.IMREAD_COLOR BGR (default)
cv2.IMREAD_GRAYSCALE Single channel
cv2.IMREAD_UNCHANGED Preserve alpha channel
cv2.IMREAD_REDUCED_COLOR_2 Downsample by 2× on load

Image info

h, w = img.shape[:2]          # height, width
channels = img.shape[2] if len(img.shape) == 3 else 1
dtype = img.dtype
size_kb = img.nbytes / 1024

Image resizing

# fixed size
resized = cv2.resize(img, (640, 480))          # (width, height)

# scale factor
resized = cv2.resize(img, None, fx=0.5, fy=0.5, interpolation=cv2.INTER_AREA)

# keep aspect ratio with max size
h, w = img.shape[:2]
scale = 512 / max(h, w)
resized = cv2.resize(img, (int(w * scale), int(h * scale)))
Interpolation Use
cv2.INTER_NEAREST Fast, pixelated (thumbnails, masks)
cv2.INTER_LINEAR Default — good general purpose
cv2.INTER_AREA Shrinking (anti-aliased)
cv2.INTER_CUBIC Enlarging — smooth but slow

Cropping

# NumPy array slicing: img[row_start:row_end, col_start:col_end]
cropped = img[100:400, 200:500]     # rows 100–400, cols 200–500
center_crop = img[h//4:3*h//4, w//4:3*w//4]

Color spaces

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
hsv  = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
rgb  = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
lab  = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)

# back to BGR
bgr = cv2.cvtColor(rgb, cv2.COLOR_RGB2BGR)
Common conversions Code
BGR → Gray cv2.COLOR_BGR2GRAY
BGR → HSV cv2.COLOR_BGR2HSV (for color segmentation)
BGR → RGB cv2.COLOR_BGR2RGB (for matplotlib display)
BGR → LAB cv2.COLOR_BGR2LAB (perceptual operations)

Common Operations

Filtering and smoothing

# Gaussian blur — remove noise
blurred = cv2.GaussianBlur(img, (5, 5), sigmaX=1.5)

# Median filter — salt-and-pepper noise
clean = cv2.medianBlur(img, 5)

# Bilateral filter — edge-preserving denoising
smooth = cv2.bilateralFilter(img, d=9, sigmaColor=75, sigmaSpace=75)

Edge detection

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
edges = cv2.Canny(gray, threshold1=50, threshold2=150)

# Sobel gradient
grad_x = cv2.Sobel(gray, cv2.CV_64F, 1, 0, ksize=3)
grad_y = cv2.Sobel(gray, cv2.CV_64F, 0, 1, ksize=3)
magnitude = cv2.magnitude(grad_x, grad_y)

Thresholding

_, binary = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY)
_, otsu = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
adaptive = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
                                 cv2.THRESH_BINARY, blockSize=11, C=2)
Method Best for
Global threshold Uniform lighting
Otsu Bimodal histograms (auto threshold)
Adaptive Uneven lighting conditions

Morphological operations

kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
dilated  = cv2.dilate(binary, kernel, iterations=2)
eroded  = cv2.erode(binary, kernel, iterations=2)
opened  = cv2.morphologyEx(binary, cv2.MORPH_OPEN, kernel)    # remove noise
closed  = cv2.morphologyEx(binary, cv2.MORPH_CLOSE, kernel)   # fill gaps
gradient = cv2.morphologyEx(binary, cv2.MORPH_GRADIENT, kernel)  # edges

Contours

contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)

# draw all contours
img_copy = img.copy()
cv2.drawContours(img_copy, contours, -1, (0, 255, 0), 2)

# filter by area and draw bounding boxes
for cnt in contours:
    area = cv2.contourArea(cnt)
    if area > 500:
        x, y, w, h = cv2.boundingRect(cnt)
        cv2.rectangle(img_copy, (x, y), (x + w, y + h), (0, 0, 255), 2)
        # centroid
        M = cv2.moments(cnt)
        cx = int(M["m10"] / M["m00"])
        cy = int(M["m01"] / M["m00"])
Retrieval mode Behavior
cv2.RETR_EXTERNAL Outer contours only
cv2.RETR_TREE Full hierarchy
cv2.RETR_CCOMP Two-level hierarchy

Drawing and annotations

cv2.line(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2)
cv2.circle(img, (cx, cy), radius=30, color=(255, 0, 0), thickness=-1)  # filled
cv2.putText(img, "Hello", (50, 50), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)

Transforms

# rotation
M = cv2.getRotationMatrix2D((w / 2, h / 2), angle=45, scale=1.0)
rotated = cv2.warpAffine(img, M, (w, h))

# translation
M = np.float32([[1, 0, tx], [0, 1, ty]])
shifted = cv2.warpAffine(img, M, (w, h))

# perspective warp
pts_src = np.float32([[56, 65], [368, 52], [28, 387], [389, 390]])
pts_dst = np.float32([[0, 0], [300, 0], [0, 300], [300, 300]])
M = cv2.getPerspectiveTransform(pts_src, pts_dst)
warped = cv2.warpPerspective(img, M, (300, 300))

Practical Examples

Example 1 — Document scanner (edge detection + perspective warp)

import cv2
import numpy as np

img = cv2.imread("document.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
edges = cv2.Canny(blurred, 50, 150)

# find largest contour (the document)
contours, _ = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
largest = max(contours, key=cv2.contourArea)
peri = cv2.arcLength(largest, True)
approx = cv2.approxPolyDP(largest, 0.02 * peri, True)

# perspective transform (requires exactly 4 points)
if len(approx) == 4:
    pts = approx.reshape(4, 2).astype("float32")
    # sort: top-left, top-right, bottom-right, bottom-left
    s = pts.sum(axis=1)
    d = np.diff(pts, axis=1)
    ordered = np.array([pts[np.argmin(s)], pts[np.argmin(d)],
                        pts[np.argmax(s)], pts[np.argmax(d)]], dtype="float32")
    M = cv2.getPerspectiveTransform(ordered, np.float32([[0,0],[400,0],[400,550],[0,550]]))
    doc = cv2.warpPerspective(img, M, (400, 550))
    cv2.imwrite("scanned.jpg", doc)

Example 2 — Color-based object tracking (HSV)

import cv2
import numpy as np

cap = cv2.VideoCapture(0)
# define blue range in HSV
lower_blue = np.array([100, 150, 50])
upper_blue = np.array([130, 255, 255])

while True:
    ret, frame = cap.read()
    if not ret:
        break
    hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
    mask = cv2.inRange(hsv, lower_blue, upper_blue)
    mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, np.ones((5, 5), np.uint8))

    contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if contours:
        c = max(contours, key=cv2.contourArea)
        x, y, w, h = cv2.boundingRect(c)
        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
        cv2.putText(frame, "Blue Object", (x, y - 10),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)

    cv2.imshow("Tracking", frame)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break
cap.release()
cv2.destroyAllWindows()

Example 3 — Webcam snapshot with face detection

import cv2

face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml")
cap = cv2.VideoCapture(0)

while True:
    ret, frame = cap.read()
    if not ret:
        break
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30))
    for (x, y, w, h) in faces:
        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
    cv2.imshow("Webcam", frame)
    key = cv2.waitKey(1) & 0xFF
    if key == ord("s"):
        cv2.imwrite("snapshot.jpg", frame)
    elif key == ord("q"):
        break
cap.release()
cv2.destroyAllWindows()

Common Patterns

Pattern: read → process → display → save

img = cv2.imread("input.jpg")
processed = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)  # your processing
cv2.imshow("Result", processed)
cv2.waitKey(0)                                     # wait for key press
cv2.destroyAllWindows()
cv2.imwrite("output.jpg", processed)

Pattern: video processing pipeline

cap = cv2.VideoCapture("video.mp4")   # or 0 for webcam
fps = cap.get(cv2.CAP_PROP_FPS)
w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
writer = cv2.VideoWriter("out.mp4", cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))

while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break
    # process frame here
    writer.write(processed_frame)

cap.release()
writer.release()

Pattern: batch image processing

import os, glob, cv2

for path in glob.glob("images/*.jpg"):
    img = cv2.imread(path)
    resized = cv2.resize(img, (224, 224))
    out = os.path.join("resized", os.path.basename(path))
    cv2.imwrite(out, resized)

Best Practices

  • OpenCV uses BGR, not RGB — convert before matplotlib display.
  • Use cv2.IMREAD_COLOR as default; cv2.IMREAD_GRAYSCALE only when you need it.
  • cv2.imshow() requires a running event loop; use matplotlib for notebooks.
  • For webcam: always cap.release() and cv2.destroyAllWindows() on exit.
  • Save video with VideoWriter — you need the same frame size and FPS as the source.
  • cv2.waitKey(1) gives a 1ms delay for video; cv2.waitKey(0) blocks for a key press.
  • cv2.Canny thresholds are image-dependent — tune them for your data.
  • Use cv2.bilateralFilter for edge-preserving smoothing before contour detection.

Common Mistakes

Mistake Fix
Displaying BGR with matplotlib cv2.cvtColor(img, cv2.COLOR_BGR2RGB) first
cv2.imshow crashes in Jupyter Use cv2.imwrite then display with IPython
Contours are empty Check threshold: use Otsu or adaptive; ensure binary input
Webcam returns empty frames Check cap.isOpened(), try index 1 or 2
cv2.resize target size is (width, height) It's (width, height), not (height, width)
VideoWriter produces empty file Frame size must match cap.get(CAP_PROP_FRAME_WIDTH/HEIGHT)
findContours modifies the source image Pass a copy: findContours(binary.copy(), ...)
Saving uint64 or float images Convert to uint8: img.astype(np.uint8)

Troubleshooting

Symptom Cause / Fix
cv2.imread returns None File path is wrong or file doesn't exist — check os.path.exists()
cv2.imshow hangs Missing cv2.waitKey() call
Black video output Video codec issue — try mp4v or XVID fourcc
ModuleNotFoundError: No module named 'cv2' pip install opencv-python (not just opencv)
Very slow processing Use cv2.resize before heavy ops; avoid Python loops on pixels
Colors are wrong in output BGR/RGB confusion — check color space
  • pillow.md — PIL/Pillow for simpler image I/O
  • torchvision — transforms and pretrained models
  • yolo — object detection with YOLO
  • ultralytics — Ultralytics YOLOv8/v11 API
  • NumPy — array operations behind every cv2 call
  • matplotlib — displaying images in notebooks
  • Python — loops, functions, list comprehensions

Official Documentation

Last Updated

2026-09-06 — Initial version.