Project Goals
3 min- Nest the smile cascade inside detected faces.
- React live: label, colour, or auto-snapshot on a smile.
- Tune the smile detector to reduce false triggers.
- Ship a complete, interactive vision app.
Warm-Up · Nested Detection
5 minfor each frame:
detect faces (on the whole gray frame)
for each face:
crop the lower-half of the face (smiles live near the mouth)
run smile cascade on that region
if a smile is found → reactRestrict each detector to where its target can be: faces in the whole frame, smiles only inside the lower face. This is faster AND cuts false positives. The whole project is just the pieces you already built, nested.
Plan · Faces, Then Smiles
14 minLoad both cascades
import cv2 face = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml") smile = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_smile.xml")
Smile detection needs strict params
The smile cascade is twitchy — it triggers on any mouth-shaped region. Use a high minNeighbors and a reasonable minSize to require a real smile:
smiles = smile.detectMultiScale(face_region_gray, scaleFactor=1.7, minNeighbors=22, # strict! minSize=(25, 25))
Search only the lower face
# within a detected face (x, y, w, h): lower = gray[y + h // 2 : y + h, x : x + w] # bottom half smiles = smile.detectMultiScale(lower, 1.7, 22, minSize=(25, 25)) is_smiling = len(smiles) > 0
React
colour = (0, 255, 0) if is_smiling else (200, 200, 200) label = "smile :)" if is_smiling else "..." cv2.rectangle(frame, (x, y), (x+w, y+h), colour, 2) cv2.putText(frame, label, (x, y-8), cv2.FONT_HERSHEY_SIMPLEX, 0.7, colour, 2)
Build · smile_detector.py
12 min# smile_detector.py — full interactive app import cv2, time face = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_frontalface_default.xml") smile = cv2.CascadeClassifier(cv2.data.haarcascades + "haarcascade_smile.xml") cap = cv2.VideoCapture(0) shots = 0 last_shot = 0 try: while True: ok, frame = cap.read() if not ok: break frame = cv2.flip(frame, 1) # selfie mirror gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) for (x, y, w, h) in face.detectMultiScale(gray, 1.2, 5, minSize=(80, 80)): lower = gray[y + h // 2 : y + h, x : x + w] smiles = smile.detectMultiScale(lower, 1.7, 22, minSize=(25, 25)) smiling = len(smiles) > 0 colour = (0, 255, 0) if smiling else (180, 180, 180) cv2.rectangle(frame, (x, y), (x + w, y + h), colour, 2) cv2.putText(frame, "smile :)" if smiling else "...", (x, y - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.7, colour, 2) # auto-snapshot on a smile, at most once per 2 seconds if smiling and time.time() - last_shot > 2: cv2.imwrite(f"smile_{shots}.jpg", frame) shots += 1 last_shot = time.time() cv2.imshow("Smile Detector (q to quit)", frame) if cv2.waitKey(1) & 0xFF == ord("q"): break finally: cap.release() cv2.destroyAllWindows() print(f"captured {shots} smiles")
Read the diff
Nested detection (smiles only in the lower face), strict smile params, a live colour/label reaction, and a debounced auto-snapshot (the 2-second cooldown stops it spamming 30 photos a second). This is a real, complete app built entirely from cascades you already know.
Extensions
13 minAdjust minNeighbors until it only fires on a real, broad smile (not a neutral mouth). Note the value that worked.
Draw a bar that fills based on how many smile-detections were found (more detections ≈ bigger smile). Map count → bar width.
Overlay a 😊 PNG (with transparency) above the face when smiling. Handle the alpha channel when pasting.
Stretch · Smile Photobooth
8 minBuild a 3-2-1 countdown that triggers when a smile is held for 1 second, then takes a framed photo with a caption. A real party photobooth in ~60 lines.
Recap
3 minYou shipped an interactive vision app by nesting detectors (faces → smiles in the lower face), using strict params to cut false triggers, reacting live, and debouncing the snapshot. No new APIs — just composition of the vision week. That closes computer vision; next we teach computers to read language.
Homework
4 minFinish the smile detector with at least one extension (smile-o-meter, emoji overlay, or photobooth countdown). Record a short clip or save a few auto-snapshots. Write a paragraph on what was finicky to tune and how you fixed it.
Extend smile_detector.py. Common finicky bit: the smile cascade's sensitivity — most students settle around minNeighbors 20-25.