Gesichtserkennung in Echtzeit mit YuNet von OpenCV
Zeichnen Sie mit dem YuNet-Detektor von OpenCV Gesichtsrahmen und fünf Gesichtslandmarken in eine Videovorschau. Das folgende Python-Beispiel verarbeitet eine lokale Videodatei oder optional eine Webcam, ordnet Erkennungen ihrem ursprünglichen Frame zu und gibt die Videoquelle beim Beenden frei.
Warum Gesichtserkennung in Echtzeit?
Mit einer Streaming-Vorschau können Sie beobachten, wie sich Erkennungen verändern, wenn Gesichter sich bewegen, verschwinden oder kleiner werden. YuNet findet Gesichtspositionen; es erkennt keine Personen wieder und verfolgt sie nicht über mehrere Frames hinweg. Ob die Schleife mit einem Live-Videofeed Schritt hält, hängt von Ihrer Hardware und der Eingabeauflösung ab.
Umgebung einrichten
Diese Bash-Anleitung wurde unter Linux mit Python 3.11.2, der GUI-Variante von
opencv-python 4.13.0.92 und NumPy 2.2.6 getestet. Das sind getestete Versionen,
keine grundsätzlichen Mindestanforderungen. Sie benötigen eine Installation von
python3 mit venv und
pip sowie eine funktionierende Desktop-Anzeige. Andere Betriebssysteme
liegen außerhalb des getesteten Umfangs dieser Anleitung.
Die festgelegten Versionen kombinieren bewusst das Modell vom März 2023 mit OpenCV 4.x. OpenCV 5 ist verfügbar, doch seine ONNX-Runtime-Engine benötigt für variable Bildgrößen den neueren YuNet-Export mit dynamischen Dimensionen; siehe das YuNet-Modellrepository. Übernehmen Sie dieses Upgrade erst in das Beispiel, nachdem Sie Modell und Laufzeit gemeinsam getestet haben.
Fügen Sie diesen Block im Verzeichnis ein, in dem Sie yunet-stream erstellen
möchten. Er läuft in einer Subshell, sodass Ihre Shell bei Erfolg wie bei einem Fehler im
ursprünglichen Verzeichnis bleibt. Ein vorhandenes Projekt wird abgewiesen.
(
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
)
Wenn die Einrichtung fehlschlägt, prüfen Sie das teilweise angelegte Verzeichnis und wählen Sie vor
einem erneuten Versuch einen neuen Projektnamen. Ändern Sie dabei sowohl
mkdir als auch cd. Der Block entfernt keine
vorhandenen Dateien. Die neue virtuelle Umgebung hält andere OpenCV-Installationen aus diesem
Beispiel heraus: Die Anleitung zum OpenCV-Python-Paket verlangt,
dass Sie eine Distribution wählen. Headless-Pakete können dieses Vorschaufenster nicht bereitstellen.
Für den Modelldownload gilt ein Socket-Timeout von 60 Sekunden. Ein Timeout oder ein
Prüfsummenfehler stoppt die Einrichtung, bevor die Modelldatei bereitgestellt wird. Prüfen Sie Ihre
Verbindung, bevor Sie es in einem neuen Verzeichnis erneut versuchen.
Eine einfache Pipeline zur Gesichtserkennung im Videostream aufbauen
Speichern Sie dieses vollständige Programm als yunet-stream/stream.py. Es verwendet einen
Detektor sequenziell und begrenzt die OpenCV-Verarbeitung auf einen Thread. Videodecodierung und GUI
können eigene Threads verwenden; die Anwendung hat keine Warteschlange für Worker.
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)
Legen Sie ein lokales Video, das OpenCV decodieren kann, unter yunet-stream/input.mkv ab.
Fügen Sie dann Folgendes im übergeordneten Verzeichnis ein. Ein Dateiname mit Leerzeichen bleibt
ein einzelnes Argument in Anführungszeichen.
(
cd yunet-stream &&
.venv/bin/python stream.py --video './input.mkv'
)
Das Fenster YuNet stream zeigt grüne Rahmen und gelbe Landmarken auf erkannten Gesichtern. Frames ohne Gesichter bleiben ohne Markierungen. Aktivieren Sie dieses Fenster und drücken Sie q, um zu stoppen, oder drücken Sie Ctrl+C im Terminal. Gibt ein Lesevorgang bei einer Datei false zurück, endet die Schleife nach mindestens einem Frame. OpenCV verwendet dasselbe Signal für das Dateiende und Decodierungsfehler; das ist daher keine Integritätsprüfung der Aufnahme. Eine nicht verfügbare Videoquelle, ein leerer Frame oder ein Modellfehler führt zu einem Fehlerabbruch mit anschließender Ressourcenfreigabe.
Für eine Webcam verwenden Sie im selben Befehl --camera 0 statt
--video './input.mkv'. Diese Variante benötigt eine Kamera, eine Berechtigung des
Betriebssystems und eine Desktop-Anzeige. Das Beispiel mit der Videodatei wurde mit einer
GUI-Anzeige getestet; die Aufnahme mit einer physischen Webcam ist weiterhin ungeprüft. Eine
fehlende Anzeige kann dazu führen, dass das GUI-Backend den Prozess abbricht. Prüfen Sie diese
Voraussetzung daher vor dem Start.
Erweiterte Nutzung: mehrere Gesichter und Parameteranpassungen
Die Hilfsfunktion zum Zeichnen durchläuft bereits alle Erkennungen. Jede Zeile enthält
(x, y, width, height), fünf Landmarken mit (x, y) und einen Score
an Index 14, wie im OpenCV-Tutorial zur Gesichtserkennung
dokumentiert.
Um einen niedrigeren Score-Schwellenwert auszuprobieren, ergänzen Sie den Videobefehl um
--score 0.7. Dadurch können mehr Kandidaten übrig bleiben, darunter auch
Fehlerkennungen. --nms steuert die Unterdrückung überlappender Erkennungen;
der Standardwert ist 0.3. Der feste Wert von 5.000 für
top_k begrenzt die Kandidaten vor der Unterdrückung und verspricht keine
5.000 Gesichter. Vergleichen Sie Erkennungen anhand repräsentativen Videomaterials, bevor Sie diese
Einstellungen ändern.
Leistung optimieren
Ergänzen Sie den Befehl um --scale 0.5 --every 2, um beim ersten Frame und danach bei jedem
zweiten Frame die Inferenz auf einem kleineren Bild auszuführen. Die Vorschau zeigt weiterhin jeden
decodierten Frame. Übersprungene Frames haben keine Markierungen; sie übernehmen keine Erkennungen
des vorherigen Frames. Das Überspringen der Inferenz überspringt weder die Decodierung noch erzeugt
es eine Personenverfolgung. Ein kurzes Auftauchen kann dadurch unentdeckt bleiben.
Der Code rechnet Rahmen und Landmarken anhand der tatsächlichen Breite und Höhe nach der Größenänderung zurück. Getrennte Verhältnisse sind wichtig, wenn ungerade Abmessungen unterschiedlich gerundet werden. Verkleinerung kann auch die Erkennung kleiner Gesichter erschweren. Behalten Sie den einzelnen sequenziellen Detektor bei, bis Messungen einen anderen Aufbau rechtfertigen. Seine veränderliche Eingabegröße bei gleichzeitigen Aufrufen gemeinsam zu nutzen, würde eine separate Synchronisierungsstrategie erfordern.
Praxisbeispiel: Sicherheitsüberwachung
Eine Gesichtsvorschau kann Ihnen helfen, Videomaterial zu prüfen, bevor Sie eine Überwachungsanwendung entwickeln. Dieses Programm identifiziert keine Besucher, zeichnet kein Video auf, speichert keine Erkennungen dauerhaft und sendet keine Warnungen. Die Gesichtsanzahl in einem verarbeiteten Frame entspricht nicht der Anzahl unterschiedlicher Personen. Ein leeres oder übersprungenes Ergebnis belegt auch nicht, dass niemand die Szene durchquert hat.
Leistungsaspekte
Das Terminal meldet die durchschnittliche Zeit für detect() getrennt vom
Durchsatz der Schleife von der Erfassung bis zur Anzeige. Die Schleifenzeit umfasst Erfassung,
Größenänderung, Inferenz bei ausgewählten Frames, Zeichnen und GUI-Ereignisverarbeitung; sie schließt
Modellinitialisierung und abschließende Ressourcenfreigabe aus. Eine Videodatei wird ohne zeitliche
Taktung verarbeitet. Ihr Durchsatz entspricht daher weder der Bildrate der Aufnahme noch der
Latenz einer Live-Kamera.
Ein Durchlauf mit einer Videodatei prüft Erfassung, Inferenz und Anzeige. Er belegt weder die Webcam-Leistung noch die Erkennungsgenauigkeit. Messen Sie auf Ihrer Zielhardware und mit Ihrer Zielauflösung. Prüfen Sie anschließend schwierige Lichtverhältnisse, Verdeckungen, kleine Gesichter und Übergänge von Frames mit Gesichtern zu Frames ohne Gesichter. Eine schnelle Inferenzmessung allein zeigt nicht, dass die gesamte Anwendung Video dauerhaft in Echtzeit verarbeiten kann.
Fazit
Prüfen Sie vor der Verwendung eines neuen Videofeeds Rahmen und Landmarken in voller Größe. Wiederholen Sie die Prüfung anschließend mit allen Skalierungsoptionen oder Optionen zum Überspringen von Frames, die Sie verwenden möchten. Behalten Sie den Dateidurchlauf als wiederholbare Prüfung bei, wenn Sie das Modell oder die OpenCV-Version ändern. Die OpenCV-Videoanleitung erklärt die hier verwendeten APIs zur Erfassung und Anzeige.
