Anotar imagens e vídeos com YuNet e automação via CLI
YuNet é um detector de faces compacto disponível pelo OpenCV. Uma pequena CLI pode usá-lo para anotar imagens locais e vídeos curtos com taxa de quadros constante, desenhando caixas verdes ao redor das faces detectadas. Os exemplos abaixo preservam as dimensões das imagens decodificadas e geram uma saída própria para cada entrada do lote. A detecção encontra regiões semelhantes a faces; ela não identifica pessoas nem fornece uma decisão de autenticação.
Entender o YuNet
YuNet oferece uma opção leve, voltada à CPU, para detectar faces e pontos de referência faciais. A precisão e a velocidade dependem do tamanho da imagem, do tamanho da face, da pose, da iluminação, do limiar e do hardware. Consulte a documentação do modelo no OpenCV Zoo para ver a avaliação publicada do modelo. Este tutorial processa arquivos locais; ele não comprova que a detecção acompanhe a entrada de câmeras em tempo real.
Configurar o YuNet rapidamente
Use Bash no Linux, Python com venv e pip, cURL e um sistema de arquivos que
suporte links físicos. Este tutorial foi testado no Linux x86-64 com Python 3.14.7, o pacote wheel
opencv-python-headless 5.0.0.93 (OpenCV 5.0.0) e NumPy 2.4.6. A versão de patch do Python e a
compilação do wheel são versões fixadas e testadas; o processamento de vídeo usa o status booleano
de gravação do OpenCV 5. Use uma
versão do Python com suporte do projeto com um wheel compatível da
página de versões do pacote.
Outras combinações de ambiente de execução e plataforma precisam de validação própria.
Comece em um diretório de trabalho vazio. Esta CLI não abre janelas de interface gráfica, então
instale o wheel sem interface gráfica. Os wheels do OpenCV compartilham o namespace
cv2: instale uma variante por ambiente.
(
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
)
Os parênteses mantêm o tratamento de falhas dentro de um subshell. Uma falha na criação do ambiente
interrompe o processo antes da instalação; uma falha na instalação pode deixar um
venv parcial. Continue em um novo diretório vazio em vez de reutilizar esse
ambiente. Cada comando abaixo indica seu interpretador explicitamente, então você não precisa
ativar o ambiente.
Baixe os bytes do modelo testado da revisão fixada do OpenCV Zoo e verifique a soma de verificação antes de disponibilizar o arquivo com o nome usado pelos scripts:
(
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
)
O modelo tem 232.589 bytes. Uma falha na requisição ou na verificação da soma de verificação remove o download temporário e deixa o arquivo com o nome final ausente; execute o mesmo bloco novamente após corrigir a falha. Um modelo existente é preservado. Mantenha-o junto dos três scripts abaixo.
Automatizar a detecção em uma única imagem
Salve esta implementação compartilhada como face_tools.py. A
API FaceDetectorYN do OpenCV espera que o tamanho de entrada
corresponda ao quadro recebido. A função auxiliar redimensiona o quadro de inferência e depois
reajusta a escala das caixas detectadas para desenhá-las na imagem original.
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()
Salve este script que chama a implementação como detect_faces.py no mesmo diretório:
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')
Execute venv/bin/python detect_faces.py image photo.jpg annotated.jpg com seu próprio JPEG ou PNG.
Abra o resultado e confira se as caixas verdes contornam as faces esperadas. Uma imagem válida sem
faces detectadas ainda gera uma saída e informa zero detecções. Use .jpg
ou .png para a saída e crie primeiro seu diretório pai; destinos existentes
são recusados.
O OpenCV decodifica essas imagens como pixels coloridos de 8 bits. Transparência, maior profundidade de bits, perfis de cor e metadados de origem não são preservados. As dimensões se referem à imagem decodificada, incluindo a orientação EXIF aplicada automaticamente pelo OpenCV. A saída JPEG tem perdas; use PNG quando precisar manter os pixels fora das caixas inalterados. Nos testes, um JPEG com os bytes finais removidos ainda foi decodificado com sucesso, com preenchimento dos pixels ausentes. Inspecione a imagem inteira, incluindo as bordas, e use entradas íntegras e confiáveis; esta CLI não valida a integridade da entrada.
Processar pastas em lote
Reutilize um detector sequencialmente para os arquivos JPEG e PNG no nível superior de uma pasta.
Salve este script como 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')
Execute venv/bin/python batch_detect.py images new-results. O diretório de resultados deve ser novo. A correspondência de
sufixos não diferencia maiúsculas de minúsculas, os arquivos são ordenados pelo nome e subpastas e
entradas de links simbólicos são ignoradas. Os nomes completos dos arquivos de origem são mantidos
antes de adicionar .jpg, então photo.jpg e
photo.png geram photo.jpg.jpg e photo.png.jpg.
Os nomes dos arquivos que falharam são informados em stderr e contribuem para um status de término
do lote diferente de zero; os resultados bem-sucedidos continuam disponíveis. Tente novamente usando
outro diretório de resultados novo.
Coloque entre aspas os caminhos que contêm espaços. Para um nome de entrada ou destino que comece
com hífen, passe um caminho como ./-photo.png para que
argparse não interprete o nome como uma opção.
Trabalhar com fluxos de vídeo
Execute venv/bin/python detect_faces.py video input.mp4 annotated.mp4 para um vídeo local curto com taxa de quadros constante e
dimensões decodificadas pares. A saída contém apenas o vídeo anotado: o áudio não é copiado.
O total de detecções conta observações ao longo dos quadros, não pessoas distintas. A temporização
com taxa de quadros variável e a reconexão de câmeras ao vivo exigem outro fluxo de trabalho.
Uma falha na leitura de um quadro pelo OpenCV pode indicar o fim do arquivo ou um problema no decodificador. Um clipe de teste cortado dentro de um pacote próximo ao final gerou um resultado mais curto que podia ser reproduzido e uma mensagem de sucesso. O gravador da versão fixada retorna um status por quadro, que esta CLI verifica, mas alguns backends apenas informam sucesso com base no melhor esforço possível. Compare a quantidade de quadros decodificados e a taxa de quadros do resultado com as da origem, confira se a duração corresponde à quantidade de quadros dividida pela taxa de quadros e reproduza o início e o fim. A mensagem de sucesso, sozinha, não comprova a integridade completa da mídia. Metadados de rotação podem alterar as dimensões codificadas; compare os quadros decodificados exibidos, que são codificados na saída com a orientação aplicada.
Ajustar a velocidade para tempo real
Reduza max_side em annotate para efetivamente redimensionar
os quadros de inferência e depois meça o equilíbrio entre velocidade e precisão em faces pequenas
e distantes. Mantenha um detector por processo executor; não o compartilhe entre chamadas
simultâneas. Um wheel para CPU não oferece suporte a CUDA automaticamente. A aceleração por GPU
exige uma compilação compatível do OpenCV, hardware e uma configuração de backend validada
separadamente.
Para mídias não confiáveis, use executores isolados e limites de tempo, memória e disco para todo o processamento. As verificações de bytes e pixels das imagens não limitam todas as alocações do decodificador de vídeo nem o tempo total de processamento.
Compare outro detector usando as mesmas faces representativas e o mesmo hardware se o YuNet não atingir sua meta de precisão ou latência. Inclua decodificação, redimensionamento, detecção e codificação no tempo decorrido; o tempo de inferência, sozinho, não indica se o fluxo completo de processamento acompanha uma fonte ao vivo.
Para fluxos de trabalho gerenciados de visão computacional, consulte o serviço de inteligência artificial da Transloadit.
