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)
# 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.