Bilder und Videos mit YuNet und CLI-Automatisierung annotieren
YuNet ist ein kompakter Gesichtsdetektor, der über OpenCV verfügbar ist. Eine kleine CLI kann damit lokale Bilder und kurze Videos mit konstanter Bildrate annotieren und grüne Rahmen um erkannte Gesichter zeichnen. Die folgenden Beispiele erhalten die Abmessungen der decodierten Bilder und geben jede Batch-Eingabe separat aus. Die Erkennung findet gesichtsähnliche Bereiche; sie identifiziert keine Personen und trifft keine Authentifizierungsentscheidung.
YuNet verstehen
YuNet bietet eine schlanke, CPU-orientierte Möglichkeit, Gesichter und Gesichtsmerkmale zu erkennen. Genauigkeit und Geschwindigkeit hängen von Bildgröße, Gesichtsgröße, Pose, Beleuchtung, Schwellenwert und Hardware ab. Die veröffentlichte Modellevaluation finden Sie in der Modelldokumentation von OpenCV Zoo. Diese Anleitung verarbeitet lokale Dateien; sie belegt keine Echtzeitleistung für Kameras.
YuNet schnell einrichten
Verwenden Sie Bash unter Linux, Python mit venv und pip, cURL sowie ein
Dateisystem, das Hardlinks unterstützt. Diese Anleitung wurde unter Linux x86-64 mit Python 3.14.7,
dem Wheel opencv-python-headless in Version 5.0.0.93
(OpenCV 5.0.0) und NumPy 2.4.6 getestet. Die Python-Patchversion und der Wheel-Build sind auf getestete
Versionen festgelegt; der Ablauf für Videos nutzt den booleschen Schreibstatus von OpenCV 5.
Verwenden Sie eine
unterstützte Python-Version mit einem kompatiblen Wheel von der
Release-Seite des Pakets.
Andere Kombinationen aus Laufzeitumgebung und Plattform müssen separat validiert werden.
Beginnen Sie in einem leeren Arbeitsverzeichnis. Diese CLI öffnet keine GUI-Fenster. Installieren
Sie daher das Headless-Wheel. Die Wheels von OpenCV teilen sich den Namensraum
cv2: Installieren Sie eine Variante pro Umgebung.
(
set -e
command -v python3 >/dev/null || { printf 'Missing prerequisite: python3\n' >&2; exit 1; }
if [ -e venv ] || [ -L venv ]; then
printf 'Choose a fresh directory; venv already exists\n' >&2
exit 1
fi
python3 -m venv venv
venv/bin/python -m pip install opencv-python-headless==5.0.0.93 numpy==2.4.6
)
Die Klammern halten die Fehlerbehandlung innerhalb einer Subshell. Schlägt das Erstellen der
Umgebung fehl, stoppt der Ablauf vor der Installation; schlägt die Installation fehl, kann ein
unvollständiges venv zurückbleiben. Fahren Sie in einem neuen leeren
Verzeichnis fort, statt diese Umgebung erneut zu verwenden. Jeder folgende Befehl benennt seinen
Interpreter explizit, sodass Sie die Umgebung nicht aktivieren müssen.
Laden Sie die getesteten Modelldaten aus der festgelegten Revision von OpenCV Zoo herunter und prüfen Sie ihre Prüfsumme, bevor Sie die Datei unter dem Namen bereitstellen, den die Skripte verwenden:
(
set -e
for tool in curl mktemp ln rm; do
command -v "$tool" >/dev/null || { printf 'Missing prerequisite: %s\n' "$tool" >&2; exit 1; }
done
test -x venv/bin/python
if [ -e face_detection_yunet.onnx ] || [ -L face_detection_yunet.onnx ]; then
printf 'Model already exists; verify it or choose a fresh directory\n' >&2
exit 1
fi
model_tmp=$(mktemp './.yunet-model.XXXXXX')
trap 'rm -f "$model_tmp"' EXIT
curl -fsSLo "$model_tmp" \
https://media.githubusercontent.com/media/opencv/opencv_zoo/47534e27c9851bb1128ccc0102f1145e27f23f98/models/face_detection_yunet/face_detection_yunet_2023mar.onnx
venv/bin/python - "$model_tmp" <<'PY'
import hashlib
from pathlib import Path
import sys
expected = '8f2383e4dd3cfbb4553ea8718107fc0423210dc964f9f4280604804ed2552fa4'
if hashlib.sha256(Path(sys.argv[1]).read_bytes()).hexdigest() != expected:
sys.exit('Unexpected model checksum')
PY
ln "$model_tmp" face_detection_yunet.onnx
)
Das Modell ist 232.589 Bytes groß. Schlägt die Anfrage oder die Prüfsummenprüfung fehl, wird der temporäre Download entfernt und der endgültige Dateiname bleibt unbelegt. Führen Sie denselben Block erneut aus, nachdem Sie den Fehler behoben haben. Ein vorhandenes Modell bleibt erhalten. Lassen Sie es im selben Verzeichnis wie alle drei folgenden Skripte.
Erkennung in Einzelbildern automatisieren
Speichern Sie diese gemeinsame Implementierung als face_tools.py. Die
FaceDetectorYN-API von OpenCV erwartet, dass ihre Eingabegröße zum
übergebenen Frame passt. Die Hilfsfunktion ändert die Größe des Frames für die Inferenz und skaliert
dann die erkannten Rahmen zum Einzeichnen auf die Größe des Originalbilds zurück.
from contextlib import contextmanager
import math
import os
from pathlib import Path
from tempfile import TemporaryDirectory
# Set before importing OpenCV so image decoding has an explicit pixel ceiling.
os.environ.setdefault('OPENCV_IO_MAX_IMAGE_PIXELS', '20000000')
import cv2
MODEL_PATH = Path(__file__).with_name('face_detection_yunet.onnx')
def create_detector():
return cv2.FaceDetectorYN.create(str(MODEL_PATH), '', (320, 320), 0.9, 0.3, 5000)
def annotate(detector, image, max_side=960):
height, width = image.shape[:2]
if width * height > 20_000_000:
raise ValueError('Frame exceeds the pixel limit')
scale = min(1.0, max_side / max(width, height))
small = cv2.resize(image, (max(1, round(width * scale)), max(1, round(height * scale))))
detector.setInputSize((small.shape[1], small.shape[0]))
_, faces = detector.detect(small)
count = 0 if faces is None else len(faces)
if faces is not None:
scale_x = width / small.shape[1]
scale_y = height / small.shape[0]
for face in faces:
x, y, w, h = face[:4]
left, top = max(0, round(x * scale_x)), max(0, round(y * scale_y))
right = min(width - 1, round((x + w) * scale_x))
bottom = min(height - 1, round((y + h) * scale_y))
cv2.rectangle(image, (left, top), (right, bottom), (0, 255, 0), 2)
return image, count
@contextmanager
def new_output(path):
output = Path(path).absolute()
if output.exists() or output.is_symlink():
raise ValueError('Output must not exist')
with TemporaryDirectory(prefix='.face-output-', dir=output.parent) as temporary:
candidate = Path(temporary) / ('result' + output.suffix)
yield candidate
if not candidate.is_file() or candidate.stat().st_size == 0:
raise ValueError('No nonempty output produced')
os.link(candidate, output)
def detect_image(detector, source, output):
source = Path(source)
if source.suffix.lower() not in {'.jpg', '.jpeg', '.png'}:
raise ValueError('Use a JPEG or PNG image')
if not source.is_file() or source.stat().st_size > 50 * 1024 * 1024:
raise ValueError('Use a regular image file no larger than 50 MiB')
image = cv2.imread(str(source))
if image is None:
raise ValueError('Cannot decode image')
annotated, count = annotate(detector, image)
with new_output(output) as candidate:
if not cv2.imwrite(str(candidate), annotated):
raise ValueError('Cannot encode output image')
return count
def detect_video(detector, source, output):
if not Path(source).is_file():
raise ValueError('Use a local video file')
capture = cv2.VideoCapture(str(Path(source).absolute()))
writer = None
frames = 0
detections = 0
try:
if not capture.isOpened():
raise ValueError('Cannot open video')
fps = capture.get(cv2.CAP_PROP_FPS)
width = int(capture.get(cv2.CAP_PROP_FRAME_WIDTH))
height = int(capture.get(cv2.CAP_PROP_FRAME_HEIGHT))
if not math.isfinite(fps) or fps <= 0 or width <= 0 or height <= 0:
raise ValueError('Invalid video timing or dimensions')
if width * height > 20_000_000 or width % 2 or height % 2:
raise ValueError('Use bounded, even video dimensions')
with new_output(output) as candidate:
writer = cv2.VideoWriter(str(candidate), cv2.VideoWriter_fourcc(*'mp4v'), fps, (width, height))
if not writer.isOpened():
raise ValueError('Cannot open video encoder')
while True:
ok, frame = capture.read()
if not ok:
break
if frame.shape[:2] != (height, width):
raise ValueError('Frame dimensions changed')
annotated, count = annotate(detector, frame)
if not writer.write(annotated):
raise ValueError('Cannot encode video frame')
frames += 1
detections += count
writer.release()
writer = None
if frames == 0:
raise ValueError('No frames decoded')
return frames, detections
finally:
capture.release()
if writer is not None:
writer.release()
Speichern Sie dieses aufrufende Skript als detect_faces.py im selben Verzeichnis:
import argparse
import sys
from face_tools import create_detector, detect_image, detect_video
import cv2
parser = argparse.ArgumentParser()
parser.add_argument('mode', choices=['image', 'video'])
parser.add_argument('input')
parser.add_argument('output')
args = parser.parse_args()
try:
detector = create_detector()
if args.mode == 'image':
print(f'Detected {detect_image(detector, args.input, args.output)} faces')
else:
frames, detections = detect_video(detector, args.input, args.output)
print(f'Processed {frames} frames with {detections} face detections')
except (OSError, ValueError, cv2.error):
sys.exit(f'{args.input}: Face detection failed; check input, model, codec, and destination')
Führen Sie venv/bin/python detect_faces.py image photo.jpg annotated.jpg mit Ihrer eigenen JPEG- oder PNG-Datei aus.
Öffnen Sie das Ergebnis und prüfen Sie, ob die grünen Rahmen die erwarteten Gesichter umschließen.
Ein gültiges Bild ohne erkannte Gesichter erzeugt ebenfalls eine Ausgabe und meldet null
Erkennungen. Verwenden Sie für die Ausgabe .jpg oder
.png und erstellen Sie zuerst das übergeordnete Verzeichnis; vorhandene
Ziele werden abgelehnt.
OpenCV decodiert diese Bilder als 8-Bit-Farbpixel. Transparenz, höhere Bittiefe, Farbprofile und Quellmetadaten bleiben nicht erhalten. Die Abmessungen beziehen sich auf das decodierte Bild, einschließlich der automatischen EXIF-Ausrichtung durch OpenCV. Die JPEG-Ausgabe ist verlustbehaftet; verwenden Sie PNG, wenn die Pixel außerhalb der Rahmen unverändert bleiben sollen. Im Test ließ sich eine JPEG-Datei trotz entfernter letzter Bytes erfolgreich decodieren, wobei fehlende Pixel aufgefüllt wurden. Prüfen Sie das gesamte Bild einschließlich seiner Ränder und verwenden Sie intakte, vertrauenswürdige Eingaben; diese CLI prüft nicht die Integrität der Eingaben.
Ordner im Batch verarbeiten
Verwenden Sie einen Detektor nacheinander für die JPEG- und PNG-Dateien auf der obersten Ebene eines
Ordners. Speichern Sie dies als batch_detect.py:
import argparse
from pathlib import Path
import sys
from face_tools import create_detector, detect_image
import cv2
parser = argparse.ArgumentParser()
parser.add_argument('input_directory')
parser.add_argument('output_directory')
args = parser.parse_args()
try:
source = Path(args.input_directory)
images = sorted(path for path in source.iterdir() if path.is_file() and not path.is_symlink()
and path.suffix.lower() in {'.jpg', '.jpeg', '.png'})
if not images:
sys.exit('No supported images found')
detector = create_detector()
output = Path(args.output_directory)
output.mkdir(mode=0o700)
failures = 0
for image in images:
try:
count = detect_image(detector, image, output / (image.name + '.jpg'))
print(f'{image.name}: {count} faces')
except (OSError, ValueError, cv2.error):
failures += 1
print(f'Failed: {image.name}', file=sys.stderr)
sys.exit(1 if failures else 0)
except (OSError, ValueError, cv2.error):
sys.exit('Cannot prepare batch; check model and directories')
Führen Sie venv/bin/python batch_detect.py images new-results aus. Das Ergebnisverzeichnis muss neu sein. Beim
Abgleichen der Dateiendungen wird die Groß- und Kleinschreibung ignoriert. Dateien werden nach
Dateinamen sortiert; Unterordner und Einträge mit symbolischen Links werden übersprungen. Die
vollständigen Quelldateinamen bleiben erhalten, bevor .jpg angehängt wird.
So erzeugen photo.jpg und photo.png
die Dateien photo.jpg.jpg und photo.png.jpg. Fehlgeschlagene Dateien
werden auf stderr benannt und führen zu einem Batch-Status ungleich null; erfolgreiche Ergebnisse
bleiben verfügbar. Wiederholen Sie den Vorgang mit einem anderen neuen Ergebnisverzeichnis.
Setzen Sie Pfade mit Leerzeichen in Anführungszeichen. Beginnt ein Eingabe- oder Zielname mit einem
Bindestrich, übergeben Sie einen Pfad wie ./-photo.png, damit
argparse den Namen nicht als Option interpretiert.
Videostreams verarbeiten
Führen Sie venv/bin/python detect_faces.py video input.mp4 annotated.mp4 für ein kurzes, lokales Video mit konstanter Bildrate
und geradzahligen decodierten Abmessungen aus. Die Ausgabe enthält nur das annotierte Video:
Audio wird nicht kopiert. Die Gesamtzahl der Erkennungen zählt Beobachtungen über alle Frames,
keine unterschiedlichen Personen. Das Zeitverhalten bei variabler Bildrate und das Wiederverbinden
mit einer Live-Kamera erfordern einen anderen Ablauf.
Ein fehlgeschlagener Leseversuch für einen Frame in OpenCV kann das Dateiende oder ein Decoderproblem bedeuten. Ein Testclip, der innerhalb eines späten Pakets abgeschnitten wurde, erzeugte ein kürzeres, abspielbares Ergebnis und eine Erfolgsmeldung. Der auf die getestete Version festgelegte Writer gibt für jeden Frame einen Status zurück, den diese CLI prüft. Manche Backends melden jedoch nur den Erfolg nach bestem Bemühen. Vergleichen Sie die Anzahl der decodierten Frames und die Bildrate des Ergebnisses mit der Quelle. Prüfen Sie, ob seine Dauer der Frame-Anzahl geteilt durch die Bildrate entspricht, und spielen Sie Anfang und Ende ab. Die Erfolgsmeldung allein belegt keine vollständige Medienintegrität. Rotationsmetadaten können die codierten Abmessungen verändern; vergleichen Sie die angezeigten, decodierten Frames, die in ihrer ausgerichteten Form in die Ausgabe codiert werden.
Geschwindigkeit für Echtzeit optimieren
Reduzieren Sie max_side in annotate, um die Größe der
Frames für die Inferenz tatsächlich zu ändern. Messen Sie dann den Zielkonflikt bei kleinen und
weit entfernten Gesichtern. Verwenden Sie einen Detektor pro Worker-Prozess; teilen Sie ihn nicht
zwischen gleichzeitigen Aufrufen. Ein CPU-Wheel bietet nicht automatisch CUDA-Unterstützung.
GPU-Beschleunigung erfordert einen kompatiblen OpenCV-Build, passende Hardware und eine separat
validierte Backend-Konfiguration.
Verwenden Sie für nicht vertrauenswürdige Medien isolierte Worker sowie Zeit-, Arbeitsspeicher- und Festplattenlimits für den gesamten Job. Die Prüfungen von Bildbytes und Pixeln begrenzen nicht jede Speicherbelegung des Videodecoders oder die gesamte Verarbeitungszeit.
Vergleichen Sie einen anderen Detektor anhand derselben repräsentativen Gesichter und Hardware, wenn YuNet Ihr Ziel für Genauigkeit oder Latenz nicht erreicht. Berücksichtigen Sie bei der Zeitmessung Decodierung, Größenänderung, Erkennung und Encoding; die Inferenzzeit allein zeigt nicht, ob die gesamte Pipeline mit einer Live-Quelle Schritt hält.
Für verwaltete Bildanalyse-Workflows finden Sie weitere Informationen im Service für künstliche Intelligenz von Transloadit.
