Histograms & contrast
calcHist — grayscale
For one channel, pass channel index list [0], mask (optional), hist size (bins), and range. Output shape is typically (bins, 1) unless you squeeze.
import cv2
import numpy as np
gray = cv2.imread("photo.jpg", cv2.IMREAD_GRAYSCALE)
hist = cv2.calcHist([gray], [0], None, [256], [0, 256])
hist = hist.flatten() # length 256
# With a rectangular ROI mask
h, w = gray.shape
mask = np.zeros((h, w), np.uint8)
mask[50:200, 100:300] = 255
hist_roi = cv2.calcHist([gray], [0], mask, [256], [0, 256]).flatten()
2D histogram (Hue vs Saturation)
bgr = cv2.imread("color.jpg")
hsv = cv2.cvtColor(bgr, cv2.COLOR_BGR2HSV)
hist2d = cv2.calcHist([hsv], [0, 1], None, [180, 256], [0, 180, 0, 256])
# hist2d shape (180, 256) — useful for skin or object color models
Normalize and cumulative view
Display or compare histograms by scaling to a fixed height. The cumulative distribution (CDF) of the normalized histogram drives global equalization.
import cv2
import numpy as np
hist = cv2.calcHist([gray], [0], None, [256], [0, 256]).flatten()
hist_n = hist / (hist.sum() + 1e-9)
cdf = np.cumsum(hist_n)
# Map intensity i -> round((L-1) * cdf[i]) — idea behind equalizeHist
Global equalization
cv2.equalizeHist applies the classic transform on a single 8-bit channel—strong lift for underexposed images but can over-amplify noise and clip highlights globally.
import cv2
gray = cv2.imread("dark.jpg", cv2.IMREAD_GRAYSCALE)
eq = cv2.equalizeHist(gray)
# Per-channel on BGR (use sparingly — shifts color balance)
bgr = cv2.imread("dark_color.jpg")
ycrcb = cv2.cvtColor(bgr, cv2.COLOR_BGR2YCrCb)
y, cr, cb = cv2.split(ycrcb)
y_eq = cv2.equalizeHist(y)
bgr_eq = cv2.cvtColor(cv2.merge([y_eq, cr, cb]), cv2.COLOR_YCrCb2BGR)
Equalizing only the Y (luma) channel preserves chroma more naturally than equalizing B, G, R separately.
CLAHE (contrast limited adaptive)
CLAHE tiles the image, equalizes each tile, then interpolates—clipLimit caps contrast amplification to reduce noise blow-up. Applying CLAHE to L* in LAB is a popular photo and medical-imaging tweak.
import cv2
bgr = cv2.imread("backlit.jpg")
lab = cv2.cvtColor(bgr, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
l2 = clahe.apply(l)
lab2 = cv2.merge([l2, a, b])
bgr2 = cv2.cvtColor(lab2, cv2.COLOR_LAB2BGR)
Another CLAHE setting
# Stronger local contrast (watch for halos and noise)
clahe_aggressive = cv2.createCLAHE(clipLimit=4.0, tileGridSize=(4, 4))
l3 = clahe_aggressive.apply(l)
compareHist
Compare two normalized histograms with correlation, Chi-square, intersection, or Bhattacharyya distance—handy for template color similarity or rough image retrieval.
import cv2
gray1 = cv2.imread("patch_a.jpg", cv2.IMREAD_GRAYSCALE)
gray2 = cv2.imread("patch_b.jpg", cv2.IMREAD_GRAYSCALE)
h1 = cv2.calcHist([gray1], [0], None, [256], [0, 256])
h2 = cv2.calcHist([gray2], [0], None, [256], [0, 256])
cv2.normalize(h1, h1, 0, 1, cv2.NORM_MINMAX)
cv2.normalize(h2, h2, 0, 1, cv2.NORM_MINMAX)
corr = cv2.compareHist(h1, h2, cv2.HISTCMP_CORREL)
bhatta = cv2.compareHist(h1, h2, cv2.HISTCMP_BHATTACHARYYA)
Plot with Matplotlib
import cv2
import matplotlib.pyplot as plt
gray = cv2.imread("photo.jpg", cv2.IMREAD_GRAYSCALE)
hist = cv2.calcHist([gray], [0], None, [256], [0, 256])
plt.figure(figsize=(8, 3))
plt.plot(hist, color="orange")
plt.xlim([0, 256])
plt.title("Grayscale histogram")
plt.xlabel("Intensity")
plt.ylabel("Count")
plt.tight_layout()
plt.savefig("hist.png", dpi=120)
Takeaways
- equalizeHist is global and fast; prefer CLAHE on L (LAB) or Y (YCrCb) for color photos.
- Raise
clipLimitand shrinktileGridSizefor stronger local effect—watch artifacts. - compareHist after
normalizegives simple similarity scores between regions or images.
Quick FAQ
clipLimit create visible seams. Larger tiles or lower clip limit reduce halos; bilateral smoothing is sometimes applied afterward (trade detail for smoothness).Feature detection (intro)
Keypoint + descriptor + matcher
- Detect interesting points (corners, blobs).
- Compute a descriptor per keypoint (sometimes using a canonical orientation and scale).
- Match descriptors with a distance (L2 for float vectors, Hamming for binary).
- Filter false matches (ratio test, geometric checks with homography or fundamental matrix).
| Method | Descriptor | Speed | Notes |
|---|---|---|---|
| Shi-Tomasi / goodFeaturesToTrack | None (points only) | Fast | Great for optical flow and tracking |
| FAST | Optional (e.g. ORB) | Very fast | Corner score on circle of pixels |
| ORB | Binary (BRIEF-like) | Fast | Free; rotation-aware |
| SIFT | 128-D float | Slower | Strong invariance; check build/license |
Shi-Tomasi corners — goodFeaturesToTrack
Returns up to maxCorners 2D points with high corner response—no descriptor. Often used with cv2.calcOpticalFlowPyrLK for video tracking.
import cv2
import numpy as np
gray = cv2.imread("room.jpg", cv2.IMREAD_GRAYSCALE)
pts = cv2.goodFeaturesToTrack(
gray, maxCorners=200, qualityLevel=0.01, minDistance=10, blockSize=7)
if pts is not None:
for p in pts:
x, y = p.ravel()
cv2.circle(gray, (int(x), int(y)), 3, 255, -1)
Tighter quality, larger separation
pts2 = cv2.goodFeaturesToTrack(
gray, maxCorners=80, qualityLevel=0.05, minDistance=20, blockSize=9)
FAST keypoints
cv2.FastFeatureDetector_create (or constructor) flags corners by comparing intensity on a Bresenham circle. Very fast; combine with ORB or BRIEF for descriptors.
import cv2
gray = cv2.imread("scene.jpg", cv2.IMREAD_GRAYSCALE)
fast = cv2.FastFeatureDetector_create(threshold=40, nonmaxSuppression=True)
kps = fast.detect(gray, None)
vis = cv2.drawKeypoints(gray, kps, None, color=(255, 0, 0),
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
ORB — detect, compute, draw
ORB builds oriented FAST keypoints and binary descriptors. Default is 256 bits; Hamming distance applies. Free to use in typical OpenCV builds.
import cv2
img = cv2.imread("book.jpg", cv2.IMREAD_GRAYSCALE)
orb = cv2.ORB_create(nfeatures=500, scaleFactor=1.2, nlevels=8)
kp, des = orb.detectAndCompute(img, None)
img_bgr = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
img_kp = cv2.drawKeypoints(img_bgr, kp, None, color=(0, 255, 0),
flags=cv2.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
des is None if no keypoints; shape (N, 32) for 256-bit ORB (8 bits per byte × 32).
Brute-force matching
BFMatcher with NORM_HAMMING for ORB. Use knnMatch and Lowe’s ratio test to reject ambiguous pairs.
import cv2
im1 = cv2.imread("obj1.jpg", cv2.IMREAD_GRAYSCALE)
im2 = cv2.imread("obj2.jpg", cv2.IMREAD_GRAYSCALE)
orb = cv2.ORB_create(800)
k1, d1 = orb.detectAndCompute(im1, None)
k2, d2 = orb.detectAndCompute(im2, None)
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False)
pairs = bf.knnMatch(d1, d2, k=2)
good = []
for m, n in pairs:
if m.distance < 0.75 * n.distance:
good.append(m)
vis = cv2.drawMatches(im1, k1, im2, k2, good[:50], None, flags=2)
flags=2 is NOT_DRAW_SINGLE_POINTS (hide unmatched keypoints). On newer OpenCV you can write cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS.
Cross-check (mutual nearest neighbor)
bf_strict = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf_strict.match(d1, d2)
matches = sorted(matches, key=lambda m: m.distance)
Practical tips
- Work in grayscale for classical detectors unless you use a color-aware variant.
- Resize very large images before matching—scale affects keypoint count and runtime.
- For panorama stitching, follow matching with RANSAC homography to kill outliers (covered in geometry topics).
- SIFT may live in
opencv-contrib-pythonand has patent/history considerations; ORB is the default free baseline.
Takeaways
goodFeaturesToTrack→ points only; pair with optical flow.- ORB gives keypoints + binary descriptors + fast Hamming match.
- Use ratio test or crossCheck before trusting correspondences.
Quick FAQ
minDistance at detection, or enforce geometric consistency (homography/fundamental matrix inliers).Chapter FAQ
Quick FAQ
clipLimit create visible seams. Larger tiles or lower clip limit reduce halos; bilateral smoothing is sometimes applied afterward (trade detail for smoothness).Quick FAQ
minDistance at detection, or enforce geometric consistency (homography/fundamental matrix inliers).