Détection de visages en temps réel avec YuNet d’OpenCV
Utilisez le détecteur YuNet d’OpenCV pour tracer des cadres autour des visages et cinq repères faciaux sur un aperçu vidéo. L’exemple Python ci-dessous traite un fichier vidéo local ou, en option, le flux d’une webcam, associe les détections à leur image source et libère la capture lorsque vous l’arrêtez.
Pourquoi détecter les visages en temps réel ?
Un aperçu en flux vidéo vous permet d’observer l’évolution des détections lorsque les visages bougent, disparaissent ou deviennent plus petits. YuNet localise les visages ; il ne reconnaît pas les personnes et ne suit pas une personne d’une image à l’autre. La capacité de la boucle à suivre le rythme d’un flux en direct dépend de votre matériel et de la résolution d’entrée.
Configurer votre environnement
Cette procédure Bash a été testée sous Linux avec Python 3.11.2, opencv-python 4.13.0.92
avec interface graphique et NumPy 2.2.6. Ces versions ont été testées ; ce ne sont pas des versions
minimales intrinsèquement requises. Vous devez disposer de python3 installé avec
venv et pip, ainsi que d’un affichage de bureau
opérationnel. Les autres systèmes d’exploitation sortent du périmètre testé de cette procédure.
Les versions épinglées conservent délibérément le modèle de mars 2023 avec OpenCV 4.x. OpenCV 5 est disponible, mais son moteur ONNX Runtime nécessite le nouvel export YuNet à dimensions dynamiques pour les tailles d’image variables ; consultez le dépôt du modèle YuNet. N’intégrez pas cette mise à niveau à cet exemple sans tester ensemble le modèle et le moteur d’exécution.
Collez ce bloc depuis le répertoire où vous souhaitez créer yunet-stream. Il
s’exécute dans un sous-shell : en cas de réussite comme d’échec, votre shell reste dans son répertoire
initial. Il refuse de continuer si le projet existe déjà.
(
set -eu
command -v python3 >/dev/null || { printf '%s\n' 'Install Python with venv and pip first.' >&2; exit 1; }
mkdir yunet-stream &&
cd yunet-stream &&
python3 -m venv .venv &&
.venv/bin/python -m pip install 'opencv-python==4.13.0.92' 'numpy==2.2.6' &&
.venv/bin/python - <<'PY'
from hashlib import sha256
from pathlib import Path
from urllib.request import urlopen
revision = '47534e27c9851bb1128ccc0102f1145e27f23f98'
filename = 'face_detection_yunet_2023mar.onnx'
url = f'https://media.githubusercontent.com/media/opencv/opencv_zoo/{revision}/models/face_detection_yunet/{filename}'
with urlopen(url, timeout=60) as response:
data = response.read()
expected = '8f2383e4dd3cfbb4553ea8718107fc0423210dc964f9f4280604804ed2552fa4'
if sha256(data).hexdigest() != expected:
raise SystemExit('Model checksum mismatch; do not run this download.')
Path(filename).write_bytes(data)
PY
)
Si la configuration échoue, inspectez le répertoire partiellement créé et choisissez un nouveau nom
de projet avant de recommencer, en modifiant à la fois mkdir et
cd. Le bloc ne supprime aucun fichier existant. Le nouvel environnement
virtuel isole cette procédure des autres installations d’OpenCV : le
guide du paquet Python OpenCV exige de choisir une seule distribution,
et les paquets sans interface graphique ne peuvent pas fournir cette fenêtre d’aperçu.
Le téléchargement du modèle utilise un délai d’expiration de 60 secondes pour la socket ; une
expiration ou un échec de vérification de la somme de contrôle arrête la configuration avant la
publication du fichier du modèle. Vérifiez votre connexion avant de réessayer dans un nouveau
répertoire.
Construire un pipeline simple de détection de visages en flux vidéo
Enregistrez ce programme complet sous yunet-stream/stream.py. Il utilise un seul détecteur de
manière séquentielle et limite le traitement OpenCV à un seul thread. Le décodage vidéo et
l’interface graphique peuvent avoir leurs propres threads ; l’application n’a pas de file d’attente
de workers.
import argparse
import sys
from time import perf_counter
import cv2
import numpy as np
def draw_faces(frame, faces):
if faces is None:
return
for face in faces:
x, y, width, height = face[:4].astype(np.int32)
cv2.rectangle(frame, (x, y), (x + width, y + height), (0, 255, 0), 2)
for point in face[4:14].astype(np.int32).reshape(-1, 2):
cv2.circle(frame, tuple(point), 2, (0, 255, 255), -1)
def run(args):
cv2.setNumThreads(1)
capture = None
displayed = inferred = 0
inference_seconds = loop_seconds = 0.0
source = args.video if args.video is not None else args.camera
try:
detector = cv2.FaceDetectorYN.create(
args.model, '', (320, 320), args.score, args.nms, 5000
)
capture = cv2.VideoCapture(source)
if not capture.isOpened():
raise RuntimeError(f'Could not open capture: {source!r}')
while True:
started = perf_counter()
valid, frame = capture.read()
if not valid:
if displayed == 0 or args.camera is not None:
raise RuntimeError(f'Capture stopped without a usable frame: {source!r}')
print('File capture ended; EOF and decode failure share this signal.')
break
if frame is None or frame.size == 0:
raise RuntimeError(f'Capture returned an empty frame: {source!r}')
# Results belong only to this decoded frame, including skipped frames.
faces = None
if displayed % args.every == 0:
height, width = frame.shape[:2]
small_width = round(width * args.scale)
small_height = round(height * args.scale)
if min(small_width, small_height) < 32:
raise RuntimeError('Scaled frames must be at least 32 by 32 pixels.')
small = cv2.resize(frame, (small_width, small_height))
detector.setInputSize((small_width, small_height))
inference_started = perf_counter()
_, faces = detector.detect(small)
inference_seconds += perf_counter() - inference_started
inferred += 1
if faces is not None:
faces[:, 0:14:2] *= width / small_width
faces[:, 1:14:2] *= height / small_height
draw_faces(frame, faces)
cv2.imshow('YuNet stream', frame)
key = cv2.waitKey(1) & 0xFF
displayed += 1
loop_seconds += perf_counter() - started
if key == ord('q'):
break
finally:
if capture is not None:
capture.release()
cv2.destroyAllWindows()
print(f'Frames displayed: {displayed}; frames inferred: {inferred}')
if inferred:
print(f'Mean detect() time: {1000 * inference_seconds / inferred:.2f} ms')
if loop_seconds > 0:
print(f'Capture-to-display loop: {displayed / loop_seconds:.2f} frames/s')
if args.video is not None:
print('File feed is unpaced; these timings do not prove webcam performance or accuracy.')
def main():
parser = argparse.ArgumentParser(description='Preview YuNet face detections.')
source = parser.add_mutually_exclusive_group(required=True)
source.add_argument('--video', help='Local video filename, not a camera index.')
source.add_argument('--camera', type=int, help='Camera device index.')
parser.add_argument('--model', default='face_detection_yunet_2023mar.onnx')
parser.add_argument('--scale', type=float, default=1.0)
parser.add_argument('--every', type=int, default=1)
parser.add_argument('--score', type=float, default=0.9)
parser.add_argument('--nms', type=float, default=0.3)
args = parser.parse_args()
if not 0 < args.scale <= 1 or args.every < 1:
parser.error('--scale must be in (0, 1] and --every must be positive.')
if not 0 < args.score <= 1 or not 0 < args.nms <= 1:
parser.error('--score and --nms must be in (0, 1].')
if args.camera is not None and args.camera < 0:
parser.error('--camera must be nonnegative.')
run(args)
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
print('Stopped by Ctrl+C.', file=sys.stderr)
sys.exit(130)
except cv2.error:
print('OpenCV failed. Check the model, decoded frames, and display.', file=sys.stderr)
sys.exit(1)
except RuntimeError as error:
print(str(error), file=sys.stderr)
sys.exit(1)
Placez une vidéo locale qu’OpenCV peut décoder à l’emplacement yunet-stream/input.mkv, puis
collez ce bloc depuis le répertoire parent. Un nom de fichier contenant des espaces reste un seul
argument entre guillemets.
(
cd yunet-stream &&
.venv/bin/python stream.py --video './input.mkv'
)
La fenêtre YuNet stream affiche des cadres verts et des repères jaunes sur les visages détectés. Les images sans visage restent sans annotations. Activez cette fenêtre et appuyez sur q pour arrêter, ou appuyez sur Ctrl+C dans le terminal. Une lecture du fichier renvoyant false termine la boucle après au moins une image ; OpenCV utilise ce même signal pour la fin du fichier et les échecs de décodage. Il ne s’agit donc pas d’une vérification de l’intégrité de l’enregistrement. Une capture indisponible, une image vide ou une erreur du modèle entraîne une sortie en échec et l’exécution du nettoyage.
Pour une webcam, utilisez --camera 0 à la place de --video './input.mkv' dans la même commande.
Ce mode nécessite une caméra, une autorisation du système d’exploitation et un affichage de bureau.
L’exemple utilisant un fichier a été testé avec un affichage graphique ; la capture depuis une webcam
physique reste non vérifiée. L’absence d’affichage peut entraîner l’arrêt brutal du processus par le
moteur de l’interface graphique. Vérifiez donc ce prérequis avant le lancement.
Utilisation avancée : gérer plusieurs visages et ajuster les paramètres
La fonction de dessin parcourt déjà toutes les détections. Chaque ligne contient
(x, y, width, height), cinq repères (x, y) et un score à l’indice 14,
comme indiqué dans le tutoriel de détection de visages d’OpenCV.
Pour essayer un seuil de score plus bas, ajoutez --score 0.7 à la commande vidéo.
Davantage de candidats peuvent être conservés, y compris des faux positifs.
--nms contrôle la suppression des détections qui se chevauchent ; sa valeur
par défaut est 0.3. Le paramètre top_k, fixé à
5 000, limite le nombre de candidats avant la suppression, sans promettre 5 000 visages. Comparez les
détections sur des séquences représentatives avant de modifier ces réglages.
Optimiser les performances
Ajoutez --scale 0.5 --every 2 à la commande pour effectuer l’inférence sur une image plus
petite, sur la première image puis une image sur deux. L’aperçu affiche toujours chaque image
décodée. Les images pour lesquelles l’inférence est sautée n’ont aucune annotation ; elles ne
réutilisent pas les détections de l’image précédente. Sauter l’inférence ne saute pas le décodage et
ne crée aucun suivi ; cela peut faire manquer une apparition brève.
Le code ramène les cadres et les repères aux coordonnées d’origine à partir de la largeur et de la hauteur réelles après redimensionnement. Des rapports distincts sont nécessaires lorsque des dimensions impaires sont arrondies différemment. Le sous-échantillonnage peut aussi rendre les petits visages plus difficiles à détecter. Conservez le détecteur unique utilisé séquentiellement jusqu’à ce que des mesures justifient une autre conception ; partager sa taille d’entrée mutable entre des appels concurrents nécessiterait une politique de synchronisation distincte.
Cas pratique : surveillance à des fins de sécurité
Un aperçu des visages peut vous aider à inspecter des séquences avant de créer une application de surveillance. Ce programme n’identifie pas les visiteurs, n’enregistre pas de vidéo, ne conserve pas les détections et n’envoie pas d’alertes. Le nombre de visages dans une image traitée n’est pas le nombre de personnes distinctes, et un résultat vide ou une inférence sautée ne prouve pas que personne n’a traversé la scène.
Considérations sur les performances
Le terminal indique le temps moyen passé dans detect() séparément du débit de
la boucle allant de la capture à l’affichage. Le chronométrage de la boucle inclut la capture, le
redimensionnement, l’inférence lorsqu’elle est sélectionnée, le dessin et la gestion des événements
de l’interface graphique ; il exclut l’initialisation du modèle et le nettoyage final. Un flux issu
d’un fichier s’exécute sans régulation du rythme : son débit ne correspond donc ni à la fréquence
d’images de l’enregistrement ni à la latence d’une caméra en direct.
Une exécution sur un fichier vidéo vérifie la capture, l’inférence et l’affichage. Elle ne permet pas d’établir les performances d’une webcam ni la précision de la détection. Mesurez les performances sur votre matériel cible et à la résolution visée, puis examinez les éclairages difficiles, les occultations, les petits visages et les transitions entre des images avec et sans visage. Une mesure d’inférence rapide ne suffit pas à démontrer que l’application complète peut traiter une vidéo en temps réel de manière soutenue.
Conclusion
Avant d’utiliser un nouveau flux, inspectez les cadres et les repères à pleine taille, puis répétez avec les options de mise à l’échelle ou de saut d’images que vous prévoyez d’utiliser. Conservez l’exécution sur fichier comme vérification reproductible lorsque vous changez le modèle ou la version d’OpenCV. Le guide vidéo d’OpenCV explique les API de capture et d’affichage utilisées ici.
