OpenCV detecta caras de dos maneras: con las cascadas Haar de toda la vida y con un detector basado en redes neuronales, YuNet. Lo hemos probado con los dos sobre la misma foto, y la diferencia se ve a simple vista.
También te puede interesar
CascadeClassifier ya no viene en el paquete normal. Con pip install opencv-python el código de siempre falla así:AttributeError: module 'cv2' has no attribute 'CascadeClassifier'Se ha trasladado al paquete
opencv-contrib-python, y los archivos XML de las cascadas ya no se instalan con él: hay que bajarlos aparte. Lo que el paquete normal trae ahora es un detector basado en redes neuronales, mejor en casi todo. Las dos opciones están en esta página.Instalarlo
pip install opencv-python-headless
La variante headless es sin ventanas: para procesar imágenes y guardarlas es todo lo que hace falta, y pesa menos. Si quieres mostrar la imagen en pantalla con cv2.imshow, instala opencv-python; las dos a la vez no, porque chocan.
Con YuNet, el detector moderno
YuNet necesita un archivo con el modelo, de unos 230 KB, que se baja del repositorio oficial de modelos de OpenCV: face_detection_yunet_2023mar.onnx. Tiene licencia MIT.
import cv2
imagen = cv2.imread("tripulacion.jpg")
alto, ancho = imagen.shape[:2]
detector = cv2.FaceDetectorYN.create("yunet.onnx", "", (ancho, alto), score_threshold=0.8)
_, caras = detector.detect(imagen)
print("caras encontradas:", 0 if caras is None else len(caras))
for c in caras:
x, y, w, h = map(int, c[:4])
print(f" en ({x}, {y}), {w}x{h} | confianza {c[14]:.2f}")
cv2.rectangle(imagen, (x, y), (x + w, y + h), (255, 100, 30), 3)
for i in range(5):
cv2.circle(imagen, (int(c[4 + 2 * i]), int(c[5 + 2 * i])), 5, (60, 200, 60), -1)
cv2.imwrite("resultado_yunet.jpg", imagen)salidacaras encontradas: 3 en (247, 324), 105x132 | confianza 0.94 en (532, 159), 93x123 | confianza 0.93 en (823, 338), 96x127 | confianza 0.92
Cada cara llega como una fila de 15 números: los cuatro primeros son el recuadro; los diez siguientes, cinco puntos —los dos ojos, la nariz y las dos comisuras de la boca—, y el último, la confianza del detector. score_threshold descarta lo que no llega a ese mínimo.
Con las cascadas Haar, como en los tutoriales antiguos
pip install opencv-contrib-python-headless
Y los archivos XML de las cascadas, que hay que bajar a mano del repositorio opencv_contrib: en OpenCV 5 ya no vienen dentro del paquete.
import cv2
detector = cv2.CascadeClassifier("haarcascade_frontalface_default.xml")
print("clasificador cargado:", not detector.empty())
imagen = cv2.imread("tripulacion.jpg")
gris = cv2.cvtColor(imagen, cv2.COLOR_BGR2GRAY)
caras = detector.detectMultiScale(gris, scaleFactor=1.1, minNeighbors=5, minSize=(60, 60))
print("caras encontradas:", len(caras))
for (x, y, w, h) in caras:
print(f" en ({x}, {y}), {w}x{h} píxeles")
cv2.rectangle(imagen, (x, y), (x + w, y + h), (255, 100, 30), 3)
cv2.imwrite("resultado.jpg", imagen)salidaclasificador cargado: True caras encontradas: 4 en (523, 164), 114x114 píxeles en (806, 354), 106x106 píxeles en (248, 332), 121x121 píxeles en (683, 596), 101x101 píxeles
Cuatro caras en una foto de tres personas.

Por qué falla la cascada
Una cascada Haar busca patrones de luces y sombras: una franja oscura —los ojos— sobre una más clara —las mejillas—, una zona clara en medio —la nariz—. Un bolsillo con su sombra cumple esos patrones lo bastante bien. Se puede afinar con minNeighbors, que es cuántas detecciones tienen que coincidir para darla por buena:
salidacon minNeighbors=1: 19 detecciones
Más bajo, más falsos positivos; más alto, más caras que se escapan. Siempre es un compromiso. YuNet, en cambio, aprendió a reconocer caras de verdad a partir de miles de ejemplos, y además devuelve una confianza que permite decidir. Para un proyecto nuevo, no hay mucho que pensar.
CascadeClassifier no avisa: crea un clasificador vacío, y el fallo llega después, en detectMultiScale, con un críptico Assertion failed: !empty(). Por eso el ejemplo comprueba detector.empty() nada más cargarlo. Y OpenCV guarda los colores en orden BGR, no RGB: el (255, 100, 30) del recuadro es azul, no naranja.Con la cámara
import cv2
camara = cv2.VideoCapture(0)
ancho = int(camara.get(cv2.CAP_PROP_FRAME_WIDTH))
alto = int(camara.get(cv2.CAP_PROP_FRAME_HEIGHT))
detector = cv2.FaceDetectorYN.create("yunet.onnx", "", (ancho, alto), score_threshold=0.8)
while True:
leido, cuadro = camara.read()
if not leido:
break
_, caras = detector.detect(cuadro)
for c in (caras if caras is not None else []):
x, y, w, h = map(int, c[:4])
cv2.rectangle(cuadro, (x, y), (x + w, y + h), (255, 100, 30), 2)
cv2.imshow("caras", cuadro)
if cv2.waitKey(1) & 0xFF == ord("q"):
break
camara.release()
cv2.destroyAllWindows()Es lo mismo de arriba, aplicado a cada fotograma. Este bloque no lo hemos podido ejecutar —el contenedor de pruebas no tiene cámara—, pero el detector y el dibujo son exactamente los de arriba. Necesita opencv-python, no la versión headless, por el imshow. Fíjate en el if not leido: la versión anterior de esta página lo ignoraba con _, img = cap.read(), y si la cámara fallaba el programa reventaba unas líneas más abajo con un error que no decía nada de la cámara.
Más información en inglés: Face detection with OpenCV in Python.
Todo el código se ejecutó con Python 3.14 en un contenedor limpio antes de publicar esta página; las salidas están copiadas de esa ejecución.