Build a real-time face detection application with Python and OpenCV. Detect faces in images, videos, and live webcam streams with Haar cascades, deep learning models, and face recognition features.
Build a real-time face detection system using OpenCV — from basic Haar cascades to deep learning-based detection with live webcam support.
Project Overview
What you'll build: Real-time face detector for images, video, and webcam
Skills practiced: OpenCV, computer vision, image processing, deep learning
Estimated time: 3–4 hours
Installation
pip install opencv-python opencv-contrib-python numpy pillow
pip install mediapipe # For high-accuracy detection
import cv2
import numpy as np
print(f"OpenCV version: {cv2.__version__}")
Step 1: Basic Face Detection with Haar Cascades
# face_detect_basic.py — Haar Cascade face detection
import cv2
import os
class HaarFaceDetector:
"""Face detector using classical Haar Cascade classifier."""
def __init__(self):
# OpenCV comes with pre-trained cascades
cascade_path = cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
self.face_cascade = cv2.CascadeClassifier(cascade_path)
# Optional: also detect eyes
eye_path = cv2.data.haarcascades + 'haarcascade_eye.xml'
self.eye_cascade = cv2.CascadeClassifier(eye_path)
# Profile face detector (side view)
profile_path = cv2.data.haarcascades + 'haarcascade_profileface.xml'
self.profile_cascade = cv2.CascadeClassifier(profile_path)
def detect(self, frame, draw_eyes=True):
"""
Detect faces in a frame.
Args:
frame: BGR image (numpy array)
draw_eyes: Also detect and draw eyes
Returns:
(annotated_frame, face_count, face_locations)
"""
# Convert to grayscale for detection
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
gray = cv2.equalizeHist(gray) # Improve contrast
# Detect faces
# scaleFactor: how much image size is reduced at each scale
# minNeighbors: how many neighbors each rectangle should have
# minSize: minimum face size to detect
faces = self.face_cascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(30, 30),
flags=cv2.CASCADE_SCALE_IMAGE
)
annotated = frame.copy()
face_locations = []
if len(faces) > 0:
for i, (x, y, w, h) in enumerate(faces):
face_locations.append((x, y, x+w, y+h))
# Draw face rectangle
cv2.rectangle(annotated, (x, y), (x+w, y+h), (0, 255, 0), 2)
# Draw face number label
label = f"Face {i+1}"
label_y = y - 10 if y > 20 else y + h + 20
cv2.putText(annotated, label, (x, label_y),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
if draw_eyes:
# Detect eyes within face region
face_region = gray[y:y+h, x:x+w]
eyes = self.eye_cascade.detectMultiScale(
face_region, scaleFactor=1.05, minNeighbors=4, minSize=(20, 20)
)
for (ex, ey, ew, eh) in eyes[:2]: # Max 2 eyes
cv2.circle(annotated, (x+ex+ew//2, y+ey+eh//2), ew//2, (255, 0, 0), 2)
# Draw info overlay
cv2.putText(annotated, f"Faces: {len(face_locations)}",
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0, 255, 255), 2)
return annotated, len(face_locations), face_locations
def detect_image(self, image_path, output_path=None):
"""Detect faces in an image file."""
frame = cv2.imread(image_path)
if frame is None:
print(f"❌ Could not read image: {image_path}")
return None
result, count, locations = self.detect(frame)
print(f" Found {count} face(s) in {image_path}")
for i, (x1, y1, x2, y2) in enumerate(locations):
print(f" Face {i+1}: ({x1},{y1}) → ({x2},{y2}), size={x2-x1}×{y2-y1}")
if output_path:
cv2.imwrite(output_path, result)
print(f" ✅ Saved to {output_path}")
# Display result
cv2.imshow('Face Detection', result)
print(" Press any key to close...")
cv2.waitKey(0)
cv2.destroyAllWindows()
return result, count, locations
Step 2: Live Webcam Detection
# webcam_detector.py — Real-time webcam face detection
import cv2
import time
import numpy as np
from collections import deque
class WebcamFaceDetector:
"""Real-time face detection from webcam."""
def __init__(self, camera_id=0, target_fps=30):
self.camera_id = camera_id
self.target_fps = target_fps
self.detector = HaarFaceDetector()
# Performance tracking
self.fps_history = deque(maxlen=30)
self.face_count_history = deque(maxlen=60)
# Recording
self.is_recording = False
self.video_writer = None
def _get_fps_color(self, fps):
"""Color-code FPS display."""
if fps >= 25: return (0, 255, 0) # Green
if fps >= 15: return (0, 165, 255) # Orange
return (0, 0, 255) # Red
def _draw_overlay(self, frame, fps, face_count, elapsed_time):
"""Draw information overlay on frame."""
h, w = frame.shape[:2]
# Semi-transparent top bar
overlay = frame.copy()
cv2.rectangle(overlay, (0, 0), (w, 70), (0, 0, 0), -1)
cv2.addWeighted(overlay, 0.5, frame, 0.5, 0, frame)
# FPS
fps_color = self._get_fps_color(fps)
cv2.putText(frame, f"FPS: {fps:.1f}", (10, 25),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, fps_color, 2)
# Face count
cv2.putText(frame, f"Faces: {face_count}", (10, 55),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
# Time
mins, secs = divmod(int(elapsed_time), 60)
time_str = f"{mins:02d}:{secs:02d}"
cv2.putText(frame, time_str, (w - 80, 25),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
# Recording indicator
if self.is_recording:
cv2.circle(frame, (w - 20, 55), 8, (0, 0, 255), -1)
cv2.putText(frame, "REC", (w - 55, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 2)
# Controls hint (bottom)
controls = "Q:Quit | S:Screenshot | R:Record | F:Flip | +/-:Scale"
cv2.putText(frame, controls, (5, h - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.4, (200, 200, 200), 1)
return frame
def start(self):
"""Start the webcam detection loop."""
cap = cv2.VideoCapture(self.camera_id)
if not cap.isOpened():
print("❌ Could not open webcam!")
print(" Try changing camera_id (0, 1, 2...)")
return
# Set camera properties
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720)
cap.set(cv2.CAP_PROP_FPS, self.target_fps)
actual_w = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
actual_h = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
print(f" ✅ Webcam opened: {actual_w}x{actual_h}")
print(" Controls: Q=Quit, S=Screenshot, R=Record, F=Flip")
prev_time = time.time()
start_time = time.time()
flip = False
screenshot_count = 0
scale = 1.0
try:
while True:
ret, frame = cap.read()
if not ret:
print(" ❌ Failed to read frame")
break
# Flip horizontally (mirror)
if flip:
frame = cv2.flip(frame, 1)
# Resize for faster processing
if scale != 1.0:
small = cv2.resize(frame, None, fx=scale, fy=scale)
else:
small = frame
# Detect faces
detected, face_count, locations = self.detector.detect(small)
# Scale back if needed
if scale != 1.0:
detected = cv2.resize(detected, (actual_w, actual_h))
# Calculate FPS
curr_time = time.time()
fps = 1.0 / (curr_time - prev_time + 1e-9)
prev_time = curr_time
self.fps_history.append(fps)
avg_fps = sum(self.fps_history) / len(self.fps_history)
elapsed = curr_time - start_time
# Draw overlay
display = self._draw_overlay(detected, avg_fps, face_count, elapsed)
# Record if active
if self.is_recording and self.video_writer:
self.video_writer.write(display)
cv2.imshow('Face Detection — PyBot', display)
# Handle keyboard input
key = cv2.waitKey(1) & 0xFF
if key == ord('q') or key == 27: # Q or Escape
break
elif key == ord('s'):
filename = f"screenshot_{screenshot_count:03d}.jpg"
cv2.imwrite(filename, display)
screenshot_count += 1
print(f" 📸 Screenshot saved: {filename}")
elif key == ord('f'):
flip = not flip
print(f" {'↔️ Flip ON' if flip else '↔️ Flip OFF'}")
elif key == ord('r'):
if not self.is_recording:
rec_file = f"recording_{int(time.time())}.avi"
fourcc = cv2.VideoWriter_fourcc(*'XVID')
self.video_writer = cv2.VideoWriter(rec_file, fourcc, 20.0, (actual_w, actual_h))
self.is_recording = True
print(f" 🔴 Recording started: {rec_file}")
else:
self.is_recording = False
if self.video_writer:
self.video_writer.release()
print(" ⏹️ Recording stopped")
elif key == ord('+') or key == ord('='):
scale = min(1.0, scale + 0.1)
elif key == ord('-'):
scale = max(0.3, scale - 0.1)
finally:
cap.release()
if self.video_writer:
self.video_writer.release()
cv2.destroyAllWindows()
print(f"\n Session ended. Detected faces in {len(self.fps_history)} frames.")
Step 3: Deep Learning Face Detection
# dl_face_detector.py — Deep learning-based detection (more accurate)
import cv2
import numpy as np
import urllib.request
class DLFaceDetector:
"""
Deep learning face detector using OpenCV DNN module.
Uses SSD MobileNet trained on face detection.
"""
MODEL_URL = "https://raw.githubusercontent.com/opencv/opencv_3rdparty/dnn_samples_face_detector_20170830/res10_300x300_ssd_iter_140000.caffemodel"
PROTO_URL = "https://raw.githubusercontent.com/opencv/opencv/master/samples/dnn/face_detector/deploy.prototxt"
def __init__(self, confidence_threshold=0.5):
self.confidence_threshold = confidence_threshold
self.net = self._load_model()
def _load_model(self):
"""Load or download the DNN model."""
proto_file = 'deploy.prototxt'
model_file = 'res10_300x300_ssd_iter_140000.caffemodel'
# Download if not present
for url, filename in [(self.PROTO_URL, proto_file), (self.MODEL_URL, model_file)]:
if not os.path.exists(filename):
print(f" Downloading {filename}...")
try:
urllib.request.urlretrieve(url, filename)
print(f" ✅ Downloaded {filename}")
except Exception as e:
print(f" ❌ Failed to download {filename}: {e}")
return None
try:
net = cv2.dnn.readNetFromCaffe(proto_file, model_file)
print(" ✅ DNN face detector loaded!")
return net
except Exception as e:
print(f" ❌ Could not load DNN model: {e}")
return None
def detect(self, frame):
"""
Detect faces using deep neural network.
More accurate than Haar Cascades, especially for:
- Partial occlusion
- Various angles
- Low light
"""
if self.net is None:
# Fallback to Haar
return HaarFaceDetector().detect(frame)
h, w = frame.shape[:2]
# Preprocess: create blob from image
blob = cv2.dnn.blobFromImage(
cv2.resize(frame, (300, 300)), 1.0,
(300, 300), (104.0, 177.0, 123.0) # Mean subtraction values
)
self.net.setInput(blob)
detections = self.net.forward()
annotated = frame.copy()
faces = []
for i in range(detections.shape[2]):
confidence = detections[0, 0, i, 2]
if confidence > self.confidence_threshold:
# Get bounding box
box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
x1, y1, x2, y2 = box.astype(int)
# Clip to frame boundaries
x1, y1 = max(0, x1), max(0, y1)
x2, y2 = min(w, x2), min(h, y2)
faces.append((x1, y1, x2, y2, confidence))
# Draw bounding box with confidence
color = (0, int(255 * confidence), 0)
cv2.rectangle(annotated, (x1, y1), (x2, y2), color, 2)
label = f"{confidence*100:.1f}%"
cv2.putText(annotated, label, (x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2)
cv2.putText(annotated, f"DNN Detector | Faces: {len(faces)}",
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
return annotated, len(faces), faces
# mediapipe_detector.py — Google MediaPipe (most accurate)
# pip install mediapipe
import mediapipe as mp
import cv2
import numpy as np
class MediaPipeFaceDetector:
"""
MediaPipe-based face detection.
Features: 6 keypoints (eyes, ears, nose tip, mouth), very fast on CPU.
"""
def __init__(self, model_selection=0, min_detection_confidence=0.5):
self.mp_face = mp.solutions.face_detection
self.mp_draw = mp.solutions.drawing_utils
# model_selection: 0=short range (<2m), 1=full range (<5m)
self.detector = self.mp_face.FaceDetection(
model_selection=model_selection,
min_detection_confidence=min_detection_confidence
)
print(" ✅ MediaPipe Face Detection ready!")
def detect(self, frame, draw_keypoints=True):
"""Detect faces with facial keypoints."""
h, w = frame.shape[:2]
rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
results = self.detector.process(rgb_frame)
annotated = frame.copy()
faces = []
if results.detections:
for detection in results.detections:
confidence = detection.score[0]
bbox = detection.location_data.relative_bounding_box
# Convert relative to pixel coordinates
x1 = int(bbox.xmin * w)
y1 = int(bbox.ymin * h)
bw = int(bbox.width * w)
bh = int(bbox.height * h)
x2, y2 = x1 + bw, y1 + bh
faces.append((x1, y1, x2, y2, confidence))
# Draw bounding box
cv2.rectangle(annotated, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.putText(annotated, f"{confidence*100:.1f}%", (x1, y1 - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2)
if draw_keypoints:
# Draw 6 keypoints
kp_names = ['right_eye', 'left_eye', 'nose_tip',
'mouth_center', 'right_ear_tragion', 'left_ear_tragion']
for kp_name in kp_names:
kp = self.mp_face.get_key_point(detection, kp_name)
if kp:
kp_x = int(kp.x * w)
kp_y = int(kp.y * h)
cv2.circle(annotated, (kp_x, kp_y), 4, (255, 0, 0), -1)
cv2.putText(annotated, f"MediaPipe | Faces: {len(faces)}",
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2)
return annotated, len(faces), faces
def blur_faces(self, frame, blur_strength=51):
"""Privacy feature: blur all detected faces."""
annotated, _, faces = self.detect(frame, draw_keypoints=False)
h, w = frame.shape[:2]
blurred = frame.copy()
for x1, y1, x2, y2, _ in faces:
# Expand region slightly
pad = 10
x1, y1 = max(0, x1-pad), max(0, y1-pad)
x2, y2 = min(w, x2+pad), min(h, y2+pad)
face_region = blurred[y1:y2, x1:x2]
if face_region.size > 0:
blurred[y1:y2, x1:x2] = cv2.GaussianBlur(
face_region, (blur_strength, blur_strength), 0
)
cv2.putText(blurred, f"Privacy Mode: {len(faces)} faces blurred",
(10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
return blurred
Step 5: Main App
# main.py — Face detection app entry point
import cv2
import sys
import os
def main():
print("=" * 55)
print(" 👁️ Python Face Detection App")
print("=" * 55)
print("\n 1. Detect from image file")
print(" 2. Detect from webcam (Haar Cascade)")
print(" 3. Detect from webcam (Deep Learning)")
print(" 4. Detect from webcam (MediaPipe)")
print(" 5. Blur faces (Privacy mode — webcam)")
print(" 6. Quit")
choice = input("\n > ").strip()
if choice == '1':
path = input(" Image path: ").strip()
detector = HaarFaceDetector()
detector.detect_image(path, output_path='detected_' + os.path.basename(path))
elif choice == '2':
print("\n Starting Haar Cascade webcam detection...")
cam = WebcamFaceDetector(camera_id=0)
cam.start()
elif choice == '3':
print("\n Loading DNN model (downloading if needed)...")
dl_detector = DLFaceDetector()
cam = WebcamFaceDetector(camera_id=0)
cam.detector = dl_detector
cam.start()
elif choice == '4':
print("\n Loading MediaPipe detector...")
try:
mp_detector = MediaPipeFaceDetector()
cam = WebcamFaceDetector(camera_id=0)
cam.detector = mp_detector
cam.start()
except ImportError:
print(" ❌ MediaPipe not installed. Run: pip install mediapipe")
elif choice == '5':
print("\n Starting Privacy Mode (face blurring)...")
mp_detector = MediaPipeFaceDetector()
cap = cv2.VideoCapture(0)
while cap.isOpened():
ret, frame = cap.read()
if not ret:
break
blurred = mp_detector.blur_faces(frame)
cv2.imshow('Privacy Mode — Face Blur', blurred)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
elif choice == '6':
print(" Goodbye!")
if __name__ == '__main__':
main()
Summary
In this project, you built:
- ✅ Haar Cascade face detector (classical CV)
- ✅ Real-time webcam detection with FPS counter
- ✅ Screenshot and video recording
- ✅ Deep learning (SSD MobileNet) detector
- ✅ MediaPipe face detection with keypoints
- ✅ Face blurring for privacy protection
Key skills practiced:
- OpenCV image loading and processing
- Camera capture and real-time video
- Object detection with cascades and DNN
- Drawing on images (rectangles, circles, text)
- MediaPipe for high-accuracy detection
*Next Project: AI Assistant →*