OpenCVSeniorCoding

Как обнаруживать лица в OpenCV с помощью Haar cascades и DNN-детекторов?

Haar cascade (detectMultiScale) быстр на CPU, но чувствителен к наклону и освещению. DNN SSD ResNet-10 (dnn.readNetFromTensorflow + blobFromImage) точнее и устойчивее — предпочтителен для production-сценариев.

Два подхода к обнаружению лиц в OpenCV

OpenCV предоставляет два принципиально разных метода: классические Haar cascades (алгоритм Виолы–Джонса, 2001) и современные DNN-детекторы на основе SSD/ResNet. Выбор зависит от требований к скорости, точности и доступному железу.

Haar Cascades

import cv2
import numpy as np

# Файлы каскадов поставляются с OpenCV
# Путь: cv2.data.haarcascades + 'haarcascade_frontalface_default.xml'
face_cascade = cv2.CascadeClassifier(
    cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
)
eye_cascade = cv2.CascadeClassifier(
    cv2.data.haarcascades + "haarcascade_eye.xml"
)

img = cv2.imread("photo.jpg")
if img is None:
    raise FileNotFoundError("photo.jpg")

gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# Опциональная эквализация гистограммы улучшает качество при плохом освещении
gray_eq = cv2.equalizeHist(gray)

# scaleFactor=1.1  — уменьшение пирамиды на 10% каждый шаг
# minNeighbors=5   — минимум соседних прямоугольников для подтверждения
# minSize=(30,30)  — минимальный размер лица в пикселях
faces = face_cascade.detectMultiScale(
    gray_eq,
    scaleFactor=1.1,
    minNeighbors=5,
    minSize=(30, 30),
    flags=cv2.CASCADE_SCALE_IMAGE
)

print(f"Найдено лиц: {len(faces)}")
for (x, y, w, h) in faces:
    cv2.rectangle(img, (x, y), (x + w, y + h), (255, 0, 0), 2)
    # Поиск глаз внутри области лица
    roi_gray = gray[y:y+h, x:x+w]
    eyes = eye_cascade.detectMultiScale(roi_gray, scaleFactor=1.1, minNeighbors=10)
    for (ex, ey, ew, eh) in eyes:
        cv2.circle(img, (x + ex + ew//2, y + ey + eh//2), ew//2, (0, 255, 0), 2)

cv2.imwrite("result_haar.jpg", img)

DNN-детектор (SSD + ResNet-10)

import cv2
import numpy as np

# Модель: opencv_face_detector_uint8.pb + opencv_face_detector.pbtxt
# Скачать: https://github.com/opencv/opencv/tree/master/samples/dnn/face_detector
net = cv2.dnn.readNetFromTensorflow(
    "opencv_face_detector_uint8.pb",
    "opencv_face_detector.pbtxt"
)
# Для CUDA: net.setPreferableBackend(cv2.dnn.DNN_BACKEND_CUDA)
# net.setPreferableTarget(cv2.dnn.DNN_TARGET_CUDA)

img = cv2.imread("photo.jpg")
h, w = img.shape[:2]

# Предобработка: blob 300x300, mean subtraction (104, 117, 123) — BGR средние ImageNet
blob = cv2.dnn.blobFromImage(
    img, scalefactor=1.0, size=(300, 300),
    mean=(104.0, 117.0, 123.0), swapRB=False, crop=False
)
net.setInput(blob)

# Выход: (1, 1, N, 7) — N детекций, каждая: [batch, class, conf, x1, y1, x2, y2]
detections = net.forward()

confidence_threshold = 0.5
for i in range(detections.shape[2]):
    confidence = detections[0, 0, i, 2]
    if confidence < confidence_threshold:
        continue
    # Координаты нормализованы [0, 1]
    box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
    x1, y1, x2, y2 = box.astype(int)
    cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2)
    label = f"{confidence:.2f}"
    cv2.putText(img, label, (x1, y1 - 10),
                cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)

cv2.imwrite("result_dnn.jpg", img)

Сравнение методов

  • Haar cascade: ~5–10 мс/кадр на CPU, работает без GPU, но даёт много ложных срабатываний при наклоне лица >30°, плохом освещении, частичном перекрытии. Подходит для embedded/IoT.
  • DNN SSD ResNet-10: ~15–30 мс на CPU, ~2–5 мс на GPU, точность значительно выше, устойчив к наклону до 45°, но требует модельных файлов (~2.7 MB) и зависимости от DNN-модуля OpenCV.
  • Альтернативы: YuNet (cv2.FaceDetectorYN) — лёгкая DNN-модель, встроенная с OpenCV 4.5.4; dlib HOG+SVM — высокая точность, медленнее; MediaPipe Face Detection — оптимизирован для мобильных устройств.

Подводные камни

  • Haar cascade не работает с профилем лица: для обнаружения профиля нужен отдельный файл haarcascade_profileface.xml, а не frontalface_default.xml.
  • scaleFactor слишком маленький (<1.05) — резкий рост времени обработки; слишком большой (>1.3) — пропуск лиц нестандартного размера.
  • DNN blob mean subtraction: значения (104, 117, 123) специфичны для данной модели. Использование других значений или swapRB=True со значениями BGR-порядка даёт деградацию качества.
  • Координаты DNN выходят за границы: нормализованные координаты могут быть немного меньше 0 или больше 1 из-за паддинга — всегда применяйте np.clip(box, 0, [w, h, w, h]).
  • detectMultiScale на цветном изображении: метод ожидает grayscale; передача BGR-изображения приведёт к ошибке или неверным результатам.
  • Ложные срабатывания при minNeighbors=1–2: типичное значение 4–6; снижение увеличивает recall, но вместе с ним — число false positives.
  • YuNet как современная альтернатива: cv2.FaceDetectorYN.create() требует отдельной загрузки ONNX-файла; путь к модели должен быть абсолютным или корректно разрешаться относительно cwd.
  • Работа с видеопотоком: для реального времени применяйте ROI-трекинг (обновлять детектор каждые N=5 кадров, между кадрами использовать cv2.TrackerCSRT), иначе DNN-детектор не даст 30 FPS на CPU.

Common mistakes

  • Объяснять face detection только синтаксисом без shape, dtype, состояния или режима выполнения.
  • Игнорировать leakage, воспроизводимость, пустые входы и скрытые копии данных.
  • Не проверять production-симптомы: latency, память, ретраи, дрейф качества и несовпадение версий.

What the interviewer is testing

  • Может ли связать face detection с реальным контрактом входов и выходов.
  • Упоминает ли тесты, метрики, reproducibility и диагностику ошибок.
  • Видит ли различие между demo-кодом в ноутбуке и production-пайплайном.
  • Предлагает ли observability, rollback, ограничения стоимости и стратегию incident replay.

Sources

Related topics