HOG Features
The method before deep learning.
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.

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