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.