Face Detection with Cascade Classifiers
Learn how to detect faces, eyes, and other facial features in images and video using OpenCV’s pre-trained Haar cascade classifiers.Introduction to Face Detection
Face detection is one of the most common applications of computer vision. OpenCV provides robust pre-trained models that can detect faces in various conditions.Why Haar Cascades?
- Pre-trained models available for immediate use
- Fast enough for real-time detection
- No GPU required
- Works well for frontal faces
- Lightweight and easy to deploy
Basic Face Detection
Single Face Detection
- Python
- C++
import cv2 as cv
# Load the cascade classifier
face_cascade = cv.CascadeClassifier(cv.data.haarcascades +
'haarcascade_frontalface_default.xml')
# Read image
img = cv.imread('face.jpg')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# Detect faces
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30)
)
print(f"Found {len(faces)} face(s)")
# Draw rectangle around each face
for (x, y, w, h) in faces:
cv.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
cv.imshow('Face Detection', img)
cv.waitKey(0)
cv.destroyAllWindows()
#include <opencv2/opencv.hpp>
#include <opencv2/objdetect.hpp>
using namespace cv;
using namespace std;
int main() {
// Load cascade
CascadeClassifier face_cascade;
if(!face_cascade.load(samples::findFile(
"haarcascades/haarcascade_frontalface_default.xml"))) {
cout << "Error loading cascade" << endl;
return -1;
}
// Read image
Mat img = imread("face.jpg");
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
// Detect faces
vector<Rect> faces;
face_cascade.detectMultiScale(gray, faces, 1.1, 5, 0, Size(30, 30));
cout << "Found " << faces.size() << " face(s)" << endl;
// Draw rectangles
for(size_t i = 0; i < faces.size(); i++) {
rectangle(img, faces[i], Scalar(255, 0, 0), 2);
}
imshow("Face Detection", img);
waitKey(0);
return 0;
}
Complete Face and Eye Detection
Based on OpenCV’s facedetect.cpp sample:- Python
- C++
import cv2 as cv
from video import create_capture
from common import clock, draw_str
def detect(img, cascade):
"""Detect faces or eyes using cascade classifier"""
rects = cascade.detectMultiScale(
img,
scaleFactor=1.3,
minNeighbors=4,
minSize=(30, 30),
flags=cv.CASCADE_SCALE_IMAGE
)
if len(rects) == 0:
return []
# Convert to x1, y1, x2, y2 format
rects[:,2:] += rects[:,:2]
return rects
def draw_rects(img, rects, color):
"""Draw rectangles on image"""
for x1, y1, x2, y2 in rects:
cv.rectangle(img, (x1, y1), (x2, y2), color, 2)
def main():
import sys
import getopt
args, video_src = getopt.getopt(sys.argv[1:], '',
['cascade=', 'nested-cascade='])
try:
video_src = video_src[0]
except:
video_src = 0
args = dict(args)
cascade_fn = args.get('--cascade',
'haarcascades/haarcascade_frontalface_alt.xml')
nested_fn = args.get('--nested-cascade',
'haarcascades/haarcascade_eye.xml')
# Load cascades
cascade = cv.CascadeClassifier(cv.samples.findFile(cascade_fn))
nested = cv.CascadeClassifier(cv.samples.findFile(nested_fn))
# Open camera or video
cam = create_capture(video_src)
while True:
ret, img = cam.read()
if not ret:
break
# Convert to grayscale and equalize histogram
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
gray = cv.equalizeHist(gray)
# Measure detection time
t = clock()
# Detect faces
rects = detect(gray, cascade)
vis = img.copy()
draw_rects(vis, rects, (0, 255, 0)) # Green rectangles for faces
# Detect eyes within each face
if not nested.empty():
for x1, y1, x2, y2 in rects:
roi = gray[y1:y2, x1:x2]
vis_roi = vis[y1:y2, x1:x2]
subrects = detect(roi.copy(), nested)
draw_rects(vis_roi, subrects, (255, 0, 0)) # Blue for eyes
dt = clock() - t
# Display detection time
draw_str(vis, (20, 20), 'time: %.1f ms' % (dt*1000))
cv.imshow('Face Detection', vis)
# Press ESC to exit
if cv.waitKey(5) == 27:
break
print('Done')
cv.destroyAllWindows()
if __name__ == '__main__':
main()
#include <opencv2/opencv.hpp>
#include <opencv2/objdetect.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
#include <opencv2/videoio.hpp>
#include <iostream>
using namespace std;
using namespace cv;
void detectAndDraw(Mat& img, CascadeClassifier& cascade,
CascadeClassifier& nestedCascade,
double scale) {
double t = 0;
vector<Rect> faces;
Mat gray, smallImg;
cvtColor(img, gray, COLOR_BGR2GRAY);
double fx = 1 / scale;
resize(gray, smallImg, Size(), fx, fx, INTER_LINEAR_EXACT);
equalizeHist(smallImg, smallImg);
t = (double)getTickCount();
cascade.detectMultiScale(smallImg, faces,
1.1, 2, CASCADE_SCALE_IMAGE, Size(30, 30));
t = (double)getTickCount() - t;
printf("detection time = %g ms\n", t*1000/getTickFrequency());
for(size_t i = 0; i < faces.size(); i++) {
Rect r = faces[i];
Mat smallImgROI;
vector<Rect> nestedObjects;
Point center;
Scalar color = Scalar(0, 255, 0);
int radius;
double aspect_ratio = (double)r.width/r.height;
if(0.75 < aspect_ratio && aspect_ratio < 1.3) {
center.x = cvRound((r.x + r.width*0.5)*scale);
center.y = cvRound((r.y + r.height*0.5)*scale);
radius = cvRound((r.width + r.height)*0.25*scale);
circle(img, center, radius, color, 3, 8, 0);
}
else
rectangle(img,
Point(cvRound(r.x*scale), cvRound(r.y*scale)),
Point(cvRound((r.x + r.width-1)*scale),
cvRound((r.y + r.height-1)*scale)),
color, 3, 8, 0);
if(nestedCascade.empty())
continue;
smallImgROI = smallImg(r);
nestedCascade.detectMultiScale(smallImgROI, nestedObjects,
1.1, 2, CASCADE_SCALE_IMAGE, Size(30, 30));
for(size_t j = 0; j < nestedObjects.size(); j++) {
Rect nr = nestedObjects[j];
center.x = cvRound((r.x + nr.x + nr.width*0.5)*scale);
center.y = cvRound((r.y + nr.y + nr.height*0.5)*scale);
radius = cvRound((nr.width + nr.height)*0.25*scale);
circle(img, center, radius, color, 3, 8, 0);
}
}
imshow("result", img);
}
int main(int argc, const char** argv) {
VideoCapture capture;
Mat frame;
CascadeClassifier cascade, nestedCascade;
double scale = 1;
if(!cascade.load(samples::findFile(
"haarcascades/haarcascade_frontalface_alt.xml"))) {
cerr << "ERROR: Could not load classifier cascade" << endl;
return -1;
}
if(!nestedCascade.load(samples::findFile(
"haarcascades/haarcascade_eye_tree_eyeglasses.xml")))
cerr << "WARNING: Could not load classifier for nested objects" << endl;
if(!capture.open(0)) {
cout << "Capture from camera failed" << endl;
return 1;
}
cout << "Video capturing started..." << endl;
while(capture.read(frame)) {
if(frame.empty())
break;
Mat frame1 = frame.clone();
detectAndDraw(frame1, cascade, nestedCascade, scale);
char c = (char)waitKey(10);
if(c == 27 || c == 'q' || c == 'Q')
break;
}
return 0;
}
Smile Detection
- Python
- C++
import cv2 as cv
# Load cascades
face_cascade = cv.CascadeClassifier(cv.data.haarcascades +
'haarcascade_frontalface_default.xml')
smile_cascade = cv.CascadeClassifier(cv.data.haarcascades +
'haarcascade_smile.xml')
cap = cv.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret:
break
gray = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
# Detect faces
faces = face_cascade.detectMultiScale(gray, 1.3, 5)
for (x, y, w, h) in faces:
cv.rectangle(frame, (x, y), (x+w, y+h), (255, 0, 0), 2)
# Get face ROI for smile detection
roi_gray = gray[y:y+h, x:x+w]
roi_color = frame[y:y+h, x:x+w]
# Detect smiles (only in lower half of face)
smiles = smile_cascade.detectMultiScale(
roi_gray[h//2:, :], # Lower half
scaleFactor=1.8,
minNeighbors=20,
minSize=(25, 25)
)
# Draw smile detection
for (sx, sy, sw, sh) in smiles:
cv.rectangle(roi_color, (sx, sy + h//2),
(sx+sw, sy+sh + h//2), (0, 255, 0), 2)
cv.putText(frame, 'Smiling!', (x, y-10),
cv.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 0), 2)
cv.imshow('Smile Detection', frame)
if cv.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv.destroyAllWindows()
#include <opencv2/opencv.hpp>
#include <opencv2/objdetect.hpp>
using namespace cv;
using namespace std;
int main() {
CascadeClassifier face_cascade, smile_cascade;
face_cascade.load(samples::findFile(
"haarcascades/haarcascade_frontalface_default.xml"));
smile_cascade.load(samples::findFile(
"haarcascades/haarcascade_smile.xml"));
VideoCapture cap(0);
Mat frame, gray;
while(cap.read(frame)) {
cvtColor(frame, gray, COLOR_BGR2GRAY);
vector<Rect> faces;
face_cascade.detectMultiScale(gray, faces, 1.3, 5);
for(size_t i = 0; i < faces.size(); i++) {
Rect face = faces[i];
rectangle(frame, face, Scalar(255, 0, 0), 2);
// Get face ROI
Mat faceROI = gray(face);
// Detect smile in lower half
Rect lowerHalf(0, face.height/2,
face.width, face.height/2);
Mat smileROI = faceROI(lowerHalf);
vector<Rect> smiles;
smile_cascade.detectMultiScale(smileROI, smiles,
1.8, 20, 0, Size(25, 25));
if(!smiles.empty()) {
putText(frame, "Smiling!",
Point(face.x, face.y - 10),
FONT_HERSHEY_SIMPLEX, 0.9,
Scalar(0, 255, 0), 2);
}
}
imshow("Smile Detection", frame);
if(waitKey(1) == 'q')
break;
}
return 0;
}
Profile Face Detection
- Python
- C++
import cv2 as cv
# Load both frontal and profile cascades
frontal_cascade = cv.CascadeClassifier(cv.data.haarcascades +
'haarcascade_frontalface_default.xml')
profile_cascade = cv.CascadeClassifier(cv.data.haarcascades +
'haarcascade_profileface.xml')
img = cv.imread('group.jpg')
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# Detect frontal faces
frontal_faces = frontal_cascade.detectMultiScale(gray, 1.1, 5)
print(f"Frontal faces: {len(frontal_faces)}")
# Detect profile faces (left)
profile_faces = profile_cascade.detectMultiScale(gray, 1.1, 5)
# Flip image to detect right-facing profiles
gray_flipped = cv.flip(gray, 1)
profile_faces_flipped = profile_cascade.detectMultiScale(gray_flipped, 1.1, 5)
# Flip coordinates back
width = img.shape[1]
profile_faces_right = [(width - x - w, y, w, h)
for (x, y, w, h) in profile_faces_flipped]
print(f"Profile faces (left): {len(profile_faces)}")
print(f"Profile faces (right): {len(profile_faces_right)}")
# Draw all detections
for (x, y, w, h) in frontal_faces:
cv.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)
for (x, y, w, h) in profile_faces:
cv.rectangle(img, (x, y), (x+w, y+h), (255, 0, 0), 2)
for (x, y, w, h) in profile_faces_right:
cv.rectangle(img, (x, y), (x+w, y+h), (0, 0, 255), 2)
cv.imshow('Face Detection (Green=Frontal, Blue=Left, Red=Right)', img)
cv.waitKey(0)
#include <opencv2/opencv.hpp>
#include <opencv2/objdetect.hpp>
using namespace cv;
using namespace std;
int main() {
Mat img = imread("group.jpg");
Mat gray;
cvtColor(img, gray, COLOR_BGR2GRAY);
CascadeClassifier frontal_cascade, profile_cascade;
frontal_cascade.load("haarcascade_frontalface_default.xml");
profile_cascade.load("haarcascade_profileface.xml");
vector<Rect> frontal_faces, profile_faces, profile_right;
// Detect frontal
frontal_cascade.detectMultiScale(gray, frontal_faces, 1.1, 5);
// Detect left profiles
profile_cascade.detectMultiScale(gray, profile_faces, 1.1, 5);
// Detect right profiles
Mat gray_flipped;
flip(gray, gray_flipped, 1);
profile_cascade.detectMultiScale(gray_flipped, profile_right, 1.1, 5);
// Flip coordinates back
for(auto& r : profile_right) {
r.x = img.cols - r.x - r.width;
}
// Draw detections
for(auto& r : frontal_faces)
rectangle(img, r, Scalar(0, 255, 0), 2);
for(auto& r : profile_faces)
rectangle(img, r, Scalar(255, 0, 0), 2);
for(auto& r : profile_right)
rectangle(img, r, Scalar(0, 0, 255), 2);
imshow("Face Detection", img);
waitKey(0);
return 0;
}
Improving Detection Accuracy
Preprocessing
# Convert to grayscale
gray = cv.cvtColor(img, cv.COLOR_BGR2GRAY)
# Equalize histogram for better contrast
gray = cv.equalizeHist(gray)
# Apply slight Gaussian blur to reduce noise
gray = cv.GaussianBlur(gray, (3, 3), 0)
Parameter Tuning
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.1, # Smaller = more thorough, slower
minNeighbors=5, # Higher = fewer false positives
minSize=(30, 30), # Minimum face size
maxSize=(300, 300), # Maximum face size
flags=cv.CASCADE_SCALE_IMAGE
)
Multi-scale Detection
Try multiple scale factors and combine results:
all_faces = []
for scale in [1.05, 1.1, 1.2, 1.3]:
faces = face_cascade.detectMultiScale(gray, scale, 5)
all_faces.extend(faces)
# Remove duplicates using Non-Maximum Suppression
# (implementation depends on your needs)
Best practices for face detection:
- Always convert to grayscale first
- Use histogram equalization for better contrast
- Start with scaleFactor=1.1 and minNeighbors=5
- Adjust minSize based on expected face sizes
- For video, resize frames for faster processing
- Use nested detection (face → eyes) to verify results
Limitations of Haar cascades:
- Works best with frontal faces
- Struggles with occlusions (sunglasses, masks, hands)
- Sensitive to lighting conditions
- Less accurate than deep learning methods
- Can produce false positives
Next Steps
- Learn about Deep Learning face detection for better accuracy
- Explore Object Detection for detecting other objects
- Try face recognition and facial landmarks detection
