Image Processing Cheat Sheet
A dense reference for classical image processing with OpenCV and scikit-image: point operations, histograms, thresholds, filters, edges, frequencies, morphology, contours, and warps, each with the call to use and when to use it.
Related chapters: Point operations and histograms · Convolution and filtering · Edges and gradients · The frequency domain · Morphology and classical segmentation · Geometric transformations
import cv2
import numpy as np
from skimage import exposure , filters , feature , morphology , measure , segmentation , transform , restoration
Conventions that cause most bugs
Topic
OpenCV
scikit-image / NumPy
Channel order
BGR from cv2.imread
RGB
Pixel type
Usually uint8 in 0–255
Usually float in [0, 1] (most functions convert; preserve_range=True keeps values)
Points
(x, y) = (column, row)
[row, col] for indexing; (row, col) for most coordinates
Sizes (dsize)
(width, height)
shape = (height, width)
Filtering
filter2D is correlation
ndimage.correlate vs ndimage.convolve
Default border
BORDER_REFLECT_101 (dcb|abcd|cba)
ndimage: "reflect" (cba|abcd|dcb); "mirror" = OpenCV's default
Foreground
Nonzero
True
Hue range (8-bit)
0–179
0–1 (color.rgb2hsv)
Component count
connectedComponents includes background label 0
measure.label(...).max() = number of objects
uint8 arithmetic wraps around: use cv2.add, cv2.subtract, or np.clip(x.astype(np.int16) + k, 0, 255).astype(np.uint8). Keep derivative and sharpening outputs in float: ddepth=cv2.CV_64F.
Point operations
Operation
Formula
OpenCV
scikit-image
When to use
Brightness/contrast
\(g = \alpha f + \beta\)
cv2.convertScaleAbs(img, alpha=a, beta=b)
exposure.rescale_intensity
Simple global adjustment; watch clipping
Percentile stretch
map \([p_2, p_{98}] \to [0, 255]\)
np.percentile + cv2.normalize or a LUT
exposure.rescale_intensity(img, in_range=(p2, p98))
Default display enhancement; robust to outliers
Gamma
\(g = 255\,(f/255)^\gamma\)
cv2.LUT(img, lut)
exposure.adjust_gamma(img, gamma)
Brighten shadows (\(\gamma < 1\) ) or darken (\(\gamma > 1\) ) without clipping
Lookup table
lut[img]
cv2.LUT(img, lut)
lut[img]
Any per-pixel curve on 8-bit data; compose curves as B[A]
Invert
\(255 - f\)
cv2.bitwise_not(img)
util.invert(img)
Dark objects on light backgrounds
Histograms
Task
OpenCV
scikit-image / NumPy
Notes
Histogram
cv2.calcHist([img], [0], None, [256], [0, 256])
exposure.histogram(img, source_range="dtype"), np.bincount(img.ravel(), minlength=256)
skimage defaults to the image's min-max range
CDF
np.cumsum(hist) / hist.sum()
exposure.cumulative_distribution(img)
The equalizing transform
Global equalization
cv2.equalizeHist(img)
exposure.equalize_hist(img)
Very flat images; amplifies noise
CLAHE
cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)).apply(img)
exposure.equalize_adapthist(img, clip_limit=0.01)
Uneven lighting, medical, underwater; the default choice
Match a reference
—
exposure.match_histograms(img, ref)
Normalizing a batch to one look
Color images
Equalize L of Lab or Y of YCrCb
Same
Never equalize R, G, B separately
Thresholding
Method
OpenCV
scikit-image
When to use
Fixed
cv2.threshold(img, t, 255, cv2.THRESH_BINARY)
img > t
Controlled lighting only
Otsu
cv2.threshold(img, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
filters.threshold_otsu(img)
Bimodal histogram, even lighting
Multi-Otsu
—
filters.threshold_multiotsu(img, classes=3)
Three or more intensity classes
Triangle
cv2.THRESH_TRIANGLE
filters.threshold_triangle(img)
Small objects, one dominant peak
Adaptive mean/Gaussian
cv2.adaptiveThreshold(img, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, block, C)
filters.threshold_local(img, block_size, offset=C)
Uneven lighting, documents
Sauvola
—
filters.threshold_sauvola(img, window_size=25)
Documents with stains or faint text
Color range
cv2.inRange(hsv, lo, hi)
color.rgb2hsv + comparisons
Objects that differ by hue; red needs two ranges
Use THRESH_BINARY_INV (or ~mask) when the objects are darker than the background. Otsu: \(\sigma_B^2(t) = \omega_0\omega_1(\mu_0 - \mu_1)^2\) , maximized over \(t\) . Adaptive: block a few times the stroke width, \(C\) a few times the noise level.
Filters
Filter
OpenCV
scikit-image / SciPy
When to use
Any kernel (correlation)
cv2.filter2D(img, cv2.CV_64F, k)
ndimage.correlate(img, k)
Custom kernels; flip k for convolution
Separable kernel
cv2.sepFilter2D(img, -1, kx, ky)
two ndimage.correlate1d calls
Rank-1 kernels: \(2K\) instead of \(K^2\) per pixel
Box (mean)
cv2.blur(img, (k, k))
ndimage.uniform_filter(img, k)
Fast local means; cost independent of size
Gaussian
cv2.GaussianBlur(img, (0, 0), sigma)
filters.gaussian(img, sigma)
Default smoothing, pre-blur before edges or downsampling
Median
cv2.medianBlur(img, k)
filters.median(img, morphology.disk(r))
Salt-and-pepper noise; keeps edges
Bilateral
cv2.bilateralFilter(img, d, sigmaColor, sigmaSpace)
restoration.denoise_bilateral
Smooth while keeping edges
Non-local means
cv2.fastNlMeansDenoising(img, None, h)
restoration.denoise_nl_means
Best classical Gaussian-noise removal
Unsharp mask
cv2.addWeighted(img, 1 + a, blur, -a, 0)
filters.unsharp_mask(img, radius, amount)
Sharpening; too much creates halos
FFT convolution
cv2.filter2D (automatic for large kernels)
scipy.signal.fftconvolve
Kernels larger than about 15 × 15
Common kernels
Kernel
Values
Sum
Separable?
Purpose
Box 3 × 3
\(\frac{1}{9}\begin{smallmatrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \end{smallmatrix}\)
1
Yes: \(\frac13[1, 1, 1]\)
Mean blur
Binomial 3 × 3
\(\frac{1}{16}\begin{smallmatrix} 1 & 2 & 1 \\ 2 & 4 & 2 \\ 1 & 2 & 1 \end{smallmatrix}\)
1
Yes: \(\frac14[1, 2, 1]\)
Small Gaussian
Pyramid 5-tap
\(\frac{1}{16}[1, 4, 6, 4, 1]\) (1D)
1
Yes
pyrDown/pyrUp smoothing
Central difference
\(\frac12[-1, 0, 1]\)
0
1D
Derivative (correlation form)
Sobel \(x\)
\(\begin{smallmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{smallmatrix}\)
0
Yes: \([1, 2, 1]^\top[-1, 0, 1]\)
Gradient, gain 8
Scharr \(x\)
\(\begin{smallmatrix} -3 & 0 & 3 \\ -10 & 0 & 10 \\ -3 & 0 & 3 \end{smallmatrix}\)
0
Yes
Accurate gradient direction
Laplacian
\(\begin{smallmatrix} 0 & 1 & 0 \\ 1 & -4 & 1 \\ 0 & 1 & 0 \end{smallmatrix}\)
0
No (rank 2)
Second derivative, zero crossings
Sharpen
\(\begin{smallmatrix} 0 & -1 & 0 \\ -1 & 5 & -1 \\ 0 & -1 & 0 \end{smallmatrix}\)
1
No
Identity minus Laplacian
Gaussian
\(\frac{1}{2\pi\sigma^2}e^{-(u^2 + v^2)/2\sigma^2}\) , radius \(\approx 3\sigma\)
1
Yes
Isotropic blur; \(\sigma^2\) adds when composed
Blur kernels sum to 1 (brightness unchanged); derivative kernels sum to 0 (flat regions give 0).
Edges
Task
OpenCV
scikit-image
Notes
Derivatives
cv2.Sobel(img, cv2.CV_64F, 1, 0, ksize=3), cv2.Scharr(...)
filters.sobel_v, filters.sobel_h (divided by 4), filters.scharr
Keep float output
Magnitude, direction
cv2.cartToPolar(gx, gy, angleInDegrees=True)
np.hypot(gx, gy), np.arctan2(gy, gx)
Direction is perpendicular to the edge
Laplacian
cv2.Laplacian(img, cv2.CV_64F)
filters.laplace
Very noise-sensitive
LoG
GaussianBlur + Laplacian
ndimage.gaussian_laplace(img, sigma)
Blob and zero-crossing detection
DoG
difference of two GaussianBlurs
filters.difference_of_gaussians(img, s1, s2)
Cheap LoG; band-pass
Canny
cv2.Canny(blurred, low, high, L2gradient=True)
feature.canny(img, sigma, low, high)
OpenCV doesn't blur; ratio high:low of 2–3
Canny = smooth, gradient, non-maximum suppression, double threshold, hysteresis. Thresholds are in gradient units (Sobel gain 8), so they depend on contrast; use_quantiles=True in scikit-image is the most robust.
Frequency domain
Task
Call
Notes
2D FFT / inverse
F = np.fft.fft2(img), np.fft.ifft2(F).real
Unnormalized forward, \(1/HW\) inverse
Real-input FFT
np.fft.rfft2, np.fft.irfft2
Half the work for real images
Center the spectrum
np.fft.fftshift(F), np.fft.ifftshift
Frequency 0 to the middle
Display
np.log1p(np.abs(np.fft.fftshift(F)))
Log scale, always
Frequencies
np.fft.fftfreq(n)
Cycles per pixel, Nyquist = 0.5
Convolution theorem
ifft2(fft2(f) * fft2(h_padded))
Circular; pad for linear, or signal.fftconvolve
Gaussian low-pass
\(H = e^{-D^2/2D_0^2}\)
Equals spatial \(\sigma = N/(2\pi D_0)\) ; no ringing
High-pass
\(1 - H_{\text{low}}\)
Edges and texture
Notch
Multiply small holes at interference peaks
Periodic noise
Registration
cv2.phaseCorrelate(a, b), registration.phase_cross_correlation
Translation from the phase
Downsample safely
cv2.resize(img, dsize, interpolation=cv2.INTER_AREA), transform.resize(..., anti_aliasing=True)
Low-pass before subsampling
Gaussian pyramid
cv2.pyrDown(img)
5-tap blur + drop every other pixel
Laplacian pyramid
L = G - cv2.pyrUp(cv2.pyrDown(G), dstsize=G.shape[::-1])
Exactly invertible; blending, compression
Morphology
Operation
OpenCV
scikit-image
What it does
Structuring element
cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (k, k))
morphology.disk(r), morphology.footprint_rectangle((k, k))
Shape and scale of the probe
Erosion
cv2.erode(m, se)
morphology.erosion / binary_erosion
Shrink; remove thin parts (min filter)
Dilation
cv2.dilate(m, se)
morphology.dilation
Grow; fill small gaps (max filter)
Opening
cv2.morphologyEx(m, cv2.MORPH_OPEN, se)
morphology.opening
Remove specks and spurs
Closing
cv2.MORPH_CLOSE
morphology.closing
Fill holes and gaps
Gradient
cv2.MORPH_GRADIENT
dilation - erosion
Outlines
White / black top-hat
cv2.MORPH_TOPHAT / cv2.MORPH_BLACKHAT
morphology.white_tophat / black_tophat
Remove uneven background
Fill holes
ndimage.binary_fill_holes(m)
morphology.remove_small_holes
Solid objects
Remove small objects
filter connectedComponentsWithStats by area
morphology.remove_small_objects(m, max_size=n)
Drop noise blobs
Reconstruction
—
morphology.reconstruction(seed, mask)
Opening that keeps exact shapes
Skeleton
—
morphology.skeletonize(m)
One-pixel-wide medial lines
Components, contours, and segmentation
Task
OpenCV
scikit-image
Notes
Label components
n, labels, stats, cents = cv2.connectedComponentsWithStats(m, connectivity=8)
measure.label(m, connectivity=2)
OpenCV's n includes the background
Region properties
stats[:, cv2.CC_STAT_AREA]
measure.regionprops_table(labels, img, properties=(...))
Area, centroid, eccentricity, solidity...
Contours
cv2.findContours(m, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
measure.find_contours(img, level)
OpenCV points are (x, y); skimage (row, col)
Area, perimeter
cv2.contourArea(c), cv2.arcLength(c, True)
regionprops: area, perimeter
Circularity \(4\pi A/P^2\)
Polygon simplification
cv2.approxPolyDP(c, 0.02 * peri, True)
measure.approximate_polygon(c, tol)
Finds corners (quadrilaterals)
Hull, boxes, ellipse
cv2.convexHull, cv2.boundingRect, cv2.minAreaRect, cv2.fitEllipse
regionprops: area_convex, bbox, orientation
Solidity = area / hull area
Moments
cv2.moments(c), cv2.HuMoments(m)
measure.moments, measure.moments_hu
Centroid \(= (m_{10}/m_{00}, m_{01}/m_{00})\)
Distance transform
cv2.distanceTransform(m, cv2.DIST_L2, 5)
ndimage.distance_transform_edt(m)
Peaks at object centers
Watershed
cv2.watershed(bgr, markers_int32)
segmentation.watershed(-dist, markers, mask=m)
Always use markers
Markers
threshold the distance
feature.peak_local_max(dist, min_distance=d, labels=lab)
Local maxima adapt to object size
Region growing
cv2.floodFill
segmentation.flood(img, seed, tolerance=t)
Magic wand
k-means colors
cv2.kmeans(X, k, None, criteria, attempts, cv2.KMEANS_PP_CENTERS)
sklearn.cluster.KMeans
Cluster in Lab
Superpixels
—
segmentation.slic(img, n_segments)
Compact color regions
Task
OpenCV
scikit-image
Notes
Resize
cv2.resize(img, (w, h), interpolation=...)
transform.resize(img, (h, w))
INTER_AREA to shrink, INTER_CUBIC to enlarge
Rotation matrix
cv2.getRotationMatrix2D((cx, cy), deg, scale)
transform.rotate(img, deg)
OpenCV: positive = counterclockwise
Affine (3 pairs)
cv2.getAffineTransform(src, dst)
transform.AffineTransform.from_estimate(src, dst)
6 DOF; keeps parallel lines
Homography (4 pairs)
cv2.getPerspectiveTransform(src, dst)
transform.ProjectiveTransform.from_estimate(src, dst)
8 DOF; keeps straight lines
Homography (many, outliers)
cv2.findHomography(src, dst, cv2.RANSAC, 3.0)
measure.ransac
Level 2
Warp an image
cv2.warpAffine(img, M, (w, h)), cv2.warpPerspective(img, H, (w, h))
transform.warp(img, inverse_map)
skimage wants the inverse
Warp points
cv2.perspectiveTransform(pts.reshape(-1, 1, 2), H), cv2.transform
tform(pts)
Divide by \(w\) for homographies
Arbitrary map
cv2.remap(img, map_x, map_y, cv2.INTER_LINEAR)
transform.warp(img, coords)
Inverse mapping
Interpolation
OpenCV flag
Use for
Nearest
INTER_NEAREST
Masks and label maps
Bilinear
INTER_LINEAR
Default
Bicubic
INTER_CUBIC
Quality enlargement (may overshoot; clip)
Lanczos
INTER_LANCZOS4
Highest-quality enlargement
Area
INTER_AREA
Shrinking (anti-aliased)
Compose matrices and resample once. Order rectification corners top-left, top-right, bottom-right, bottom-left. Homogeneous: point \((x, y, 1)\) , line \((a, b, c)\) ; the line through two points and the intersection of two lines are both cross products.