Trong các bài trước, chúng ta dùng phương pháp dựa trên màu sắc và hình thái học (findContours, morphologyEx). Tuy nhiên, với các đối tượng phức tạp như khuôn mặt, mắt hay biển số xe, màu sắc và viền đơn thuần không đủ tin cậy.
Trước khi Deep Learning (YOLO, CNN) bùng nổ, Haar Cascade (thuật toán Viola-Jones) là phương pháp thống trị nhờ tốc độ cực nhanh, có thể chạy mượt mà ngay trên các CPU cấu hình thấp hoặc thiết bị nhúng.
Haar-like Features: Thay vì xét từng pixel, thuật toán dùng các ô chữ nhật đen-trắng liền kề nhau để tính độ chênh lệch sáng/tối. Ví dụ: vùng mắt người luôn tối hơn vùng trán và gò má; sống mũi luôn sáng hơn hai bên cánh mũi.
Integral Image (Ảnh tích lũy): Kỹ thuật toán học giúp tính tổng giá trị các pixel trong bất kỳ ô chữ nhật nào chỉ với 4 phép cộng/trừ số học, đạt độ phức tạp $O(1)$.
Cascade (Cơ chế tầng bậc): Một khuôn mặt phải vượt qua hàng chục bộ lọc (stages). Nếu vùng ảnh bị trượt ngay từ tầng 1 (không có đặc trưng mắt/mũi), thuật toán lập tức loại bỏ, không tốn tài nguyên kiểm tra các tầng sau.
Trong Google Colab, OpenCV đã đóng gói sẵn các file mô hình XML đã huấn luyện (pre-trained). Chúng ta sẽ tải trực tiếp file mô hình này từ kho chính thức của OpenCV, tải một bức ảnh mẫu và thực hiện nhận diện.
Dán đoạn mã sau vào cell mới trên Colab và chạy:
import cv2
import urllib.request
import os
from google.colab.patches import cv2_imshow
# =====================================================================
# BƯỚC 1: TẢI BỘ LỌC PRE-TRAINED HAAR CASCADE VÀ ẢNH MẪU
# =====================================================================
# 1. URL của bộ nhận diện khuôn mặt và mắt chính thức từ OpenCV
face_cascade_url = "https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_default.xml"
eye_cascade_url = "https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_eye.xml"
# Tải file XML mô hình nếu chưa có trên Colab
if not os.path.exists("haarcascade_frontalface_default.xml"):
urllib.request.urlretrieve(face_cascade_url, "haarcascade_frontalface_default.xml")
if not os.path.exists("haarcascade_eye.xml"):
urllib.request.urlretrieve(eye_cascade_url, "haarcascade_eye.xml")
# 2. Tải bức ảnh mẫu chân dung
sample_img_url = "https://raw.githubusercontent.com/opencv/opencv/master/samples/data/lena.jpg"
if not os.path.exists("face_sample.jpg"):
urllib.request.urlretrieve(sample_img_url, "face_sample.jpg")
print("✅ Đã chuẩn bị xong file mô hình XML và ảnh mẫu!")
# =====================================================================
# BƯỚC 2: KHỞI TẠO BỘ PHÂN LOẠI & TIỀN XỬ LÝ ẢNH
# =====================================================================
# Khởi tạo đối tượng CascadeClassifier
face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
eye_cascade = cv2.CascadeClassifier("haarcascade_eye.xml")
# Đọc ảnh
img = cv2.imread("face_sample.jpg")
# Haar Cascade chỉ hoạt động trên ảnh xám (Grayscale)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Tăng độ tương phản bằng cân bằng lược đồ màu (Histogram Equalization) để bắt đặc trưng tốt hơn
gray_equalized = cv2.equalizeHist(gray)
# =====================================================================
# BƯỚC 3: PHÁT HIỆN KHUÔN MẶT (DETECT MULTISCALE)
# =====================================================================
# Tham số quan trọng:
# - scaleFactor: Hệ số co giãn ảnh ở mỗi lần quét (1.1 nghĩa là giảm 10% mỗi lần quét)
# - minNeighbors: Số lượng khung hình lân cận cần xác nhận để tránh bắt nhầm (dương tính giả)
# - minSize: Kích thước nhỏ nhất của khuôn mặt cần quét
faces = face_cascade.detectMultiScale(
gray_equalized,
scaleFactor=1.1,
minNeighbors=5,
minSize=(60, 60)
)
print(f"👉 Tìm thấy {len(faces)} khuôn mặt trong ảnh.")
# Tạo bản sao ảnh để vẽ kết quả
output_img = img.copy()
for (x, y, w, h) in faces:
# 1. Vẽ khung chữ nhật xanh lá quanh khuôn mặt
cv2.rectangle(output_img, (x, y), (x + w, y + h), (0, 255, 0), 2)
cv2.putText(output_img, "Face", (x, y - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2)
# 2. Vùng quan tâm (Region of Interest - ROI): Mắt chỉ nằm TRONG khuôn mặt
# Dùng NumPy slicing để giới hạn không gian tìm kiếm, tránh tìm mắt ở ngoài thân hay nền
roi_gray = gray_equalized[y:y + h, x:x + w]
roi_color = output_img[y:y + h, x:x + w]
# Quét mắt trong vùng ROI của khuôn mặt
eyes = eye_cascade.detectMultiScale(
roi_gray,
scaleFactor=1.1,
minNeighbors=10,
minSize=(20, 20)
)
for (ex, ey, ew, eh) in eyes:
# Vẽ vòng tròn hoặc khung chữ nhật màu xanh dương quanh mắt
center = (ex + ew // 2, ey + eh // 2)
radius = int(round((ew + eh) * 0.25))
cv2.circle(roi_color, center, radius, (255, 0, 0), 2)
# =====================================================================
# BƯỚC 4: HIỂN THỊ KẾT QUẢ TRÊN COLAB
# =====================================================================
cv2_imshow(output_img)
detectMultiScalescaleFactor (thường chọn $1.05 \rightarrow 1.3$):
Khuôn mặt có thể ở gần camera (rất to) hoặc ở xa (rất nhỏ). Thuật toán phải tạo một tháp ảnh (Image Pyramid) với nhiều tỉ lệ thu nhỏ dần để quét. 1.1 nghĩa là sau mỗi bước, ảnh thu nhỏ đi 10%. Giá trị này càng nhỏ thì phát hiện càng kỹ nhưng tốc độ xử lý càng chậm.
minNeighbors (thường chọn $3 \rightarrow 8$):
Định nghĩa ngưỡng phát hiện ứng viên. Một vùng khuôn mặt chuẩn sẽ được các cửa sổ lân cận quét trúng nhiều lần. Nếu bạn bị tình trạng nhận diện nhầm vật thể lạ là khuôn mặt (False Positive), hãy nâng giá trị này lên (ví dụ từ 5 lên 7 hoặc 8).
Kỹ thuật trích xuất ROI (Region of Interest):
roi_gray = gray[y:y+h, x:x+w] là kỹ thuật cực kỳ quan trọng giúp giảm thiểu tối đa tải tính toán: Không bao giờ quét mắt trên toàn bộ tấm hình, mà chỉ quét bên trong tọa độ của khuôn mặt đã tìm thấy.