AI Hints
Home›Computer Vision Tutorial›Classic Detectors
Computer Vision · Lesson 55 of 111

HOG Features

The method before deep learning.

⏱ 2 min read▶ Video at 1:28:00🎓 Free
Watch this lesson in the Computer Vision courseWatch on YouTube ↗Full Computer Vision course

In two thousand and five, two French researchers published a method for finding people in photos, and it was the best in the world for years. It didn't use a neural network. It used directions of edges. The idea's still worth knowing, because it shows you exactly what convolutional networks later learned for themselves.

HOG Features
From the video: HOG Features
  • 2005: finding people in photos
  • Best in the world for years
  • Directions of edges

Histogram of oriented gradients

Split the image into small cells. In each cell, measure which way the edges point, and build a little histogram of those directions. Then normalise neighbouring cells together, which removes the effect of brightness. Those histograms, joined up, describe the shape of the object. Feed them into a simple classifier, and you've got a detector.

Why HOG was special

We train two classifiers on handwritten digits. One sees raw pixels, and the other sees HOG features. Then here's the test. We make the test images dim and washed out, just as bad lighting would do. Run it. In normal light, raw pixels work well, at ninety six percent. In bad light, raw pixels collapse to twenty six percent. But HOG holds on at ninety four percent, on the same dim images. That's the power of good features. Hand designed invariance. Deep networks learn something similar by themselves, if you show them enough variety.

hog.pypython
import numpy as np
from skimage.feature import hog
from sklearn.model_selection import train_test_split
from sklearn.svm import LinearSVC
from sklearn.datasets import load_digits

digits = load_digits()
X = np.array([hog(i, pixels_per_cell=(2, 2), cells_per_block=(1, 1)) for i in digits.images])
print("Pixels:", digits.images[0].size, "HOG features:", X.shape[1])

idx_tr, idx_te = train_test_split(range(len(X)), random_state=0)
y = digits.target
hog_model = LinearSVC(max_iter=4000).fit(X[idx_tr], y[idx_tr])
raw_model = LinearSVC(max_iter=4000).fit(digits.data[idx_tr] / 16, y[idx_tr])

dim = digits.images[idx_te] * 0.3 + 4
X_dim = np.array([hog(i, pixels_per_cell=(2, 2), cells_per_block=(1, 1)) for i in dim])
print("Normal light, raw pixels:", round(raw_model.score(digits.data[idx_te] / 16, y[idx_te]), 3))
print("Bad light, raw pixels:   ", round(raw_model.score(dim.reshape(len(dim), -1) / 16, y[idx_te]), 3))
print("Bad light, HOG:          ", round(hog_model.score(X_dim, y[idx_te]), 3))
Output
Pixels: 64 HOG features: 144
Normal light, raw pixels: 0.964
Bad light, raw pixels:    0.262
Bad light, HOG:           0.942

Summary

Edge directions survive bad light. That's the end of classic detectors. Next, geometry, and a problem that every overhead camera has.

Key takeaways
  • Edge directions describe shape
  • Normalising removes brightness
  • Good features resist bad light
Next up: Fixing Perspective — making a tilted camera square