Skip to main content

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

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

Complete Face and Eye Detection

Based on OpenCV’s facedetect.cpp sample:
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()

Smile Detection

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

Profile Face Detection

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)

Improving Detection Accuracy

1

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

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

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

Temporal Filtering

For video, track faces across frames:
# Use object tracking to smooth detections
# Only accept detections that appear in multiple consecutive frames
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
For production applications requiring high accuracy, consider using deep learning-based face detection (see Deep Learning tutorial).

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

Build docs developers (and LLMs) love