A Biblioteca Supervision: Uma Ferramenta Low-Code para Visão Computacional em Python

A biblioteca Supervision é uma solução Python de alto desempenho e baixo código, projetada para simplificar tarefas de visão computacional. Sua principal finalidade é oferecer uma interface intuitiva e eficaz para o gerenciamento de conjuntos de dados e a visualização de resultados de modelos de detecção, segmentação e rastreamento.

Para utilizar o Supervision, é necessário ter o Python 3.8 ou superior. A instalação pode ser feita de duas formas:

  • Para desenvolvimento com componentes de GUI (interface gráfica do usuário) que dependem do OpenCV para exibir imagens e vídeos:
pip install supervision[desktop]

  • Para implantação de aplicações sem a necessidade de uma interface gráfica:
pip install supervision

É importante notar que a API da biblioteca pode sofrer alterações frequentes devido às suas atualizações contínuas.

import supervision as sv

# Exibe a versão atual da biblioteca Supervision
print(sv.__version__)

'0.19.0'

  1. Processamento de Diferentes Tarefas de Visão Computacional

1.1 Detecção e Segmentação de Objetos

1.1.1 Análise de Resultados

A classe sv.Detections é fundamental para a análise das saídas de modelos de detecção e segmentação. Ela oferece métodos convenientes para carregar resultados de diversas estruturas, incluindo:

  • from_ultralytics (para YOLOv8)
  • from_detectron2 (para Detectron2)
  • from_mmdetection (para MMDetection)
  • from_yolov5 (para YOLOv5)
  • from_sam (para Segment Anything Model)
  • from_transformers (para HuggingFace Transformers)
  • from_paddledet (para PaddleDetection)

A seguir, um exemplo prático utilizando um modelo YOLOv8 para analisar e visualizar detecções em uma imagem:

import cv2
import supervision as sv
from ultralytics import YOLO
import numpy as np

# Carregar o modelo YOLOv8 pré-treinado
modelo_yolo = YOLO("yolov8n.pt") # Ou "yolov8n-seg.pt" para segmentação

# Carregar imagem
caminho_imagem = "img/dog.png" # Assumindo que a imagem 'dog.png' existe
imagem_original = cv2.imread(caminho_imagem)

# Realizar inferência
resultados_raw = modelo_yolo(imagem_original, verbose=False)[0]

# Converter os resultados para o formato Detections do Supervision
deteccoes_sv = sv.Detections.from_ultralytics(resultados_raw)

# Exibir os resultados iniciais
print("Detalhes das detecções:\n", deteccoes_sv)
print("Número de objetos detectados:", len(deteccoes_sv))

# Acessar a primeira detecção
primeira_deteccao = deteccoes_sv[0]
print("\nPrimeira detecção:\n", primeira_deteccao)

# Calcular a área de cada caixa delimitadora
areas_caixas = deteccoes_sv.box_area
print("\nÁrea das caixas delimitadoras:", areas_caixas)

# Preparar anotadores para visualização
anotador_caixas = sv.BoundingBoxAnnotator()
anotador_rotulos = sv.LabelAnnotator()

# Gerar rótulos a partir dos IDs de classe
rotulos = [
    modelo_yolo.model.names[id_classe]
    for id_classe in deteccoes_sv.class_id
]

# Anotar a imagem
imagem_anotada = anotador_caixas.annotate(
    scene=imagem_original.copy(), detections=deteccoes_sv)
imagem_anotada = anotador_rotulos.annotate(
    scene=imagem_anotada, detections=deteccoes_sv, labels=rotulos)

# Exibir a imagem anotada (requer ambiente de desktop ou backend de visualização)
sv.plot_image(imagem_anotada)

Observa-se que, em alguns casos, pode haver múltiplas detecções para o mesmo objeto, como um carro sendo classificado como 'car' e 'truck'. Para consolidar essas detecções, pode-se aplicar a Supressão Não Máxima (NMS) agnóstica à classe:

# Aplicar NMS para remover detecções duplicadas do mesmo objeto
deteccoes_filtradas = deteccoes_sv.with_nms(threshold=0.5, class_agnostic=True)

print("Classes detectadas após NMS:")
for id_classe in deteccoes_filtradas.class_id:
    print(modelo_yolo.model.names[id_classe])

1.1.2 Funções Auxiliares

O Supervision também oferece funções utilitárias para manipulação de caixas delimitadoras e máscaras:

Cálculo de Intersection over Union (IOU)

import supervision as sv
import numpy as np

# Definindo duas caixas delimitadoras no formato (x_min, y_min, x_max, y_max)
caixa_A = np.array([[50, 50, 150, 150]])
caixa_B = np.array([[100, 100, 200, 200]])

# Calcular o IOU entre as caixas
iou_resultado = sv.box_iou_batch(caixa_A, caixa_B)
print("IOU entre as caixas:", iou_resultado)

Cálculo de Non-Maximum Suppression (NMS)

import supervision as sv
import numpy as np

# Definindo caixas com scores: (x_min, y_min, x_max, y_max, score)
caixas_com_score = np.array([[50, 50, 150, 150, 0.2], [100, 100, 200, 200, 0.5]])

# Aplicar NMS e obter um array booleano indicando quais caixas preservar
indices_para_manter = sv.box_non_max_suppression(caixas_com_score, 0.1)
print("Índices a serem mantidos após NMS:", indices_para_manter)

Geração de Máscara a partir de Polígono

import supervision as sv
import numpy as np

# Definindo vértices de um polígono
vertices_poligono = np.array([(10, 20), (50, 10), (90, 20), (70, 50), (30, 50)])

# Gerar uma máscara a partir do polígono com resolução (largura, altura)
mascara_gerada = sv.polygon_to_mask(vertices_poligono, (100, 60))

# Visualizar a máscara (pixels brancos para o polígono, pretos para o fundo)
sv.plot_image(mascara_gerada)

# Para converter uma máscara de volta para polígonos, use sv.mask_to_polygons(mask)

Filtro de Polígonos por Área

import supervision as sv
import numpy as np

# Criar uma lista de polígonos de exemplo
poligono_1 = np.array([[0, 0], [0, 1], [1, 1], [1, 0]]) # Área 1
poligono_2 = np.array([[0, 0], [0, 2], [2, 2], [2, 0]]) # Área 4
poligono_3 = np.array([[0, 0], [0, 3], [3, 3], [3, 0]]) # Área 9

lista_poligonos = [poligono_1, poligono_2, poligono_3]

# Filtrar polígonos com área mínima de 3.0 e sem limite máximo
poligonos_filtrados = sv.filter_polygons_by_area(lista_poligonos, min_area=3.0, max_area=None)

print("Número de polígonos originais:", len(lista_poligonos))
print("Número de polígonos filtrados:", len(poligonos_filtrados))

Redimensionamento de Caixas Delimitadoras

import numpy as np
import supervision as sv

# Caixas delimitadoras no formato (x_min, y_min, x_max, y_max)
caixas_originais = np.array([[10, 10, 20, 20], [30, 30, 40, 40]])

# Fator de escala (maior que 1 para ampliar, menor que 1 para reduzir)
fator_escala = 1.2

# Redimensionar as caixas
caixas_redimensionadas = sv.scale_boxes(caixas_originais, fator_escala)
print("Caixas redimensionadas:\n", caixas_redimensionadas)

1.2 Rastreamento de Objetos

O Supervision integra o rastreador de objetos ByteTrack, que difere dos métodos baseados em Re-ID, pois se concentra na utilização das informações das caixas delimitadoras fornecidas pelo detector de objetos. A precisão e estabilidade do detector influenciam diretamente o desempenho do ByteTrack.

A classe sv.ByteTrack é inicializada com os seguintes parâmetros:

  • track_thresh (float, opcional, padrão 0.25): Limite de confiança da detecção para considerar uma pista.
  • track_buffer (int, opcional, padrão 30): Número de quadros para manter uma pista perdida antes de removê-la.
  • match_thresh (float, opcional, padrão 0.8): Limite para associar pistas a detecções.
  • frame_rate (int, opcional, padrão 30): Taxa de quadros do vídeo.

Principais métodos de sv.ByteTrack:

  • reset(): Reinicia o estado interno do rastreador.
  • update_with_detections(detections): Atualiza o rastreador com novas detecções e retorna as detecções atualizadas, incluindo IDs de rastreamento.

Exemplo de uso para rastreamento de objetos em um vídeo:

import supervision as sv
from ultralytics import YOLO
import numpy as np

# Carregar modelo de detecção
modelo_detector = YOLO("yolov8n.pt")

# Inicializar o rastreador ByteTrack
rastreador = sv.ByteTrack()

# Inicializar anotadores para caixas delimitadoras e rótulos
anotador_caixas_rastreio = sv.BoundingBoxAnnotator()
anotador_rotulos_rastreio = sv.LabelAnnotator()

# Função de callback para processar cada quadro do vídeo
def processar_quadro_rastreio(quadro: np.ndarray, indice: int) -> np.ndarray:
    resultados = modelo_detector(quadro, verbose=False)[0]
    
    # Converter resultados para Detections do Supervision
    deteccoes = sv.Detections.from_ultralytics(resultados)
    
    # Atualizar o rastreador com as detecções
    deteccoes = rastreador.update_with_deteccoes(deteccoes)

    # Gerar rótulos com os IDs de rastreamento
    rotulos_rastreio = [f"ID #{track_id}" for track_id in deteccoes.tracker_id]

    # Anotar o quadro
    quadro_anotado = anotador_caixas_rastreio.annotate(scene=quadro.copy(), detections=deteccoes)
    quadro_anotado = anotador_rotulos_rastreio.annotate(scene=quadro_anotado, detections=deteccoes, labels=rotulos_rastreio)
    return quadro_anotado

# Processar um vídeo de exemplo
sv.process_video(
    source_path="https://media.roboflow.com/supervision/video-examples/people-walking.mp4",
    target_path="output_rastreamento.mp4",
    callback=processar_quadro_rastreio
)

O exemplo utiliza yolov8n.pt; para maior estabilidade no rastreamento, modelos de detecção mais robustos são recomendados.

1.3 Classificação de Imagens

Para classificação de imagens, o Supervision oferece suporte limitado para resultados de modelos como CLIP, TIMM e YOLOv8, permitindo a extração dos resultados top-k e suas probabilidades.

import cv2
from ultralytics import YOLO
import supervision as sv

# Carregar imagem
imagem_gato = cv2.imread("img/cat.png") # Assumindo a imagem 'cat.png' existe

# Carregar modelo de classificação YOLOv8
modelo_classificacao = YOLO('yolov8n-cls.pt')

# Realizar inferência
saida_modelo = modelo_classificacao(imagem_gato, verbose=False)[0]

# Converter a saída para o formato Classifications do Supervision
classificacoes_sv = sv.Classifications.from_ultralytics(saida_modelo)
# Alternativamente, para outros modelos: sv.Classifications.from_clip() ou sv.Classifications.from_timm()

# Imprimir as 2 principais classificações (IDs de classe e probabilidades)
top_k_classes, top_k_probs = classificacoes_sv.get_top_k(k=2)
print("Top 2 classificações (IDs e Probabilidades):", top_k_classes, top_k_probs)

  1. Visualização de Dados e Funções de Apoio

2.1 Configurações de Cores

O Supervision oferece as classes sv.Color e sv.ColorPalette para definir e converter cores, facilitando a personalização visual.

import supervision as sv

# Cores pré-definidas
print("Cor ROBOFLOW:", sv.Color.ROBOFLOW)

# Obter valores BGR de uma cor
cor_amarela = sv.Color(r=255, g=255, b=0)
print("Amarelo em BGR:", cor_amarela.as_bgr())

# Obter valores RGB (já é o formato padrão, mas existe o método)
print("Amarelo em RGB:", cor_amarela.as_rgb())

# Obter representação hexadecimal
print("Amarelo em Hex:", cor_amarela.as_hex())

# Criar uma cor a partir de um valor hexadecimal
cor_magenta_from_hex = sv.Color.from_hex('#ff00ff')
print("Magenta de Hex:", cor_magenta_from_hex)

# Paletas de cores padrão
print("\nPaleta padrão:", sv.ColorPalette.DEFAULT)
# Outras paletas: sv.ColorPalette.ROBOFLOW, sv.ColorPalette.LEGACY

# Acessar cores por índice em uma paleta personalizada
paleta_custom = sv.ColorPalette.from_hex(['#ff0000', '#00ff00', '#0000ff']) # Vermelho, Verde, Azul
print("Segunda cor da paleta customizada (índice 1):", paleta_custom.by_idx(1))

# Criar paleta a partir de matplotlib
paleta_mpl = sv.ColorPalette.from_matplotlib('viridis', 4)
print("Paleta viridis (4 cores) de Matplotlib:", paleta_mpl)

2.2 Exemplos de Visualização de Resultados

O Supervision oferece uma rica coleção de anotadores para visualizar resultados, especialmente para detecção e rastreamento de objetos. Abaixo, exemplos dos principais estilos de anotação para caixas delimitadoras.

import cv2
import supervision as sv
from ultralytics import YOLO
import numpy as np

# Carregar modelo e imagem para demonstração
modelo_vis = YOLO("yolov8n.pt")
imagem_pessoas = cv2.imread("img/person.png") # Assumindo 'person.png' existe

# Realizar detecção e converter para Detections do Supervision
resultados_vis = modelo_vis(imagem_pessoas, verbose=False)[0]
deteccoes_vis = sv.Detections.from_ultralytics(resultados_vis)

print(f"Número de detecções na imagem de pessoas: {len(deteccoes_vis)}")

# Gerar rótulos para os anotadores
labels_vis = [
    modelo_vis.model.names[class_id]
    for class_id in deteccoes_vis.class_id
]

Anotação de Caixas Delimitadoras (BoundingBoxAnnotator)

# Configurar anotador de caixas padrão
# color_lookup: Estratégia de mapeamento de cores (INDEX, CLASS, TRACK)
anotador_retangular = sv.BoundingBoxAnnotator(
    color=sv.ColorPalette.DEFAULT, thickness=2, color_lookup=sv.ColorLookup.CLASS
)
quadro_anotado_ret = anotador_retangular.annotate(
    scene=imagem_pessoas.copy(), detections=deteccoes_vis
)
sv.plot_image(quadro_anotado_ret, (10, 10))

Anotação de Caixas com Cantos Arredondados (RoundBoxAnnotator)

# roundness: Porcentagem de arredondamento dos cantos
anotador_arredondado = sv.RoundBoxAnnotator(color_lookup=sv.ColorLookup.INDEX, roundness=0.6)
quadro_anotado_round = anotador_arredondado.annotate(
    scene=imagem_pessoas.copy(), detections=deteccoes_vis
)
sv.plot_image(quadro_anotado_round, (10, 10))

Anotação de Cantos de Caixa (BoxCornerAnnotator)

# corner_length: Comprimento de cada segmento de canto
anotador_cantos = sv.BoxCornerAnnotator(corner_length=12, color=sv.Color(r=255, g=255, b=0))
quadro_anotado_cantos = anotador_cantos.annotate(
    scene=imagem_pessoas.copy(), detections=deteccoes_vis
)
sv.plot_image(quadro_anotado_cantos, (10, 10))

Anotação com Máscara Colorida (ColorAnnotator)

# opacity: Opacidade da máscara colorida
anotador_mascara_cor = sv.ColorAnnotator(opacity=0.4)
quadro_anotado_mascara = anotador_mascara_cor.annotate(
    scene=imagem_pessoas.copy(), detections=deteccoes_vis
)
sv.plot_image(quadro_anotado_mascara, (10, 10))

Anotação com Círculos (CircleAnnotator)

anotador_circulo = sv.CircleAnnotator(color=sv.Color(r=255, g=255, b=128))
quadro_anotado_circulo = anotador_circulo.annotate(
    scene=imagem_pessoas.copy(), detections=deteccoes_vis
)
sv.plot_image(quadro_anotado_circulo, (10, 10))

Anotação com Pontos (DotAnnotator)

O DotAnnotator permite desenhar pontos em posições específicas das caixas delimitadoras. As posições disponíveis são:

for pos in sv.Position:
    print(pos)

CENTER, CENTER_LEFT, CENTER_RIGHT, TOP_CENTER, TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_CENTER, BOTTOM_RIGHT, CENTER_OF_MASS.

anotador_ponto = sv.DotAnnotator(radius=4, position=sv.Position.BOTTOM_CENTER, color=sv.Color.ROBOFLOW)
quadro_anotado_ponto = anotador_ponto.annotate(
    scene=imagem_pessoas.copy(), detections=deteccoes_vis
)
sv.plot_image(quadro_anotado_ponto, (10, 10))

Anotação com Triângulos (TriangleAnnotator)

# base/height: Largura e altura do triângulo
anotador_triangulo = sv.TriangleAnnotator(base=30, height=30, position=sv.Position.TOP_CENTER, color=sv.Color.BLUE)
quadro_anotado_tri = anotador_triangulo.annotate(
    scene=imagem_pessoas.pessoas.copy(), detections=deteccoes_vis
)
sv.plot_image(quadro_anotado_tri, (10, 10))

Anotação com Elipses (EllipseAnnotator)

# start_angle/end_angle: Ângulo inicial e final da elipse
anotador_elipse = sv.EllipseAnnotator(start_angle=-45, end_angle=215, color=sv.Color.GREEN)
quadro_anotado_elipse = anotador_elipse.annotate(
    scene=imagem_pessoas.copy(), detections=deteccoes_vis
)
sv.plot_image(quadro_anotado_elipse, (10, 10))

Anotação com Barra de Porcentagem (PercentageBarAnnotator)

# Exibe a confiança da detecção como uma barra
anotador_barra_perc = sv.PercentageBarAnnotator(
    border_color=sv.Color(r=128, g=0, b=0),
    position=sv.Position.BOTTOM_CENTER,
    width=100,
    height=20
)
quadro_anotado_barra = anotador_barra_perc.annotate(
    scene=imagem_pessoas.copy(), detections=deteccoes_vis
)
sv.plot_image(quadro_anotado_barra, (10, 10))

Anotação de Rótulos de Texto (LabelAnnotator)

# Personalização completa do texto do rótulo
anotador_rotulo_custom = sv.LabelAnnotator(
    color=sv.Color(r=255, g=255, b=255),  # Cor de fundo do rótulo
    text_color=sv.Color(r=128, g=0, b=128),  # Cor do texto
    text_scale=1.5,  # Tamanho do texto
    text_position=sv.Position.TOP_CENTER,  # Posição do texto
    text_thickness=2,  # Espessura do texto
    text_padding=8  # Preenchimento do texto
)

quadro_anotado_rotulo = anotador_rotulo_custom.annotate(
    scene=imagem_pessoas.copy(), detections=deteccoes_vis, labels=labels_vis
)
sv.plot_image(quadro_anotado_rotulo, (10, 10))

Pixelização de Objetos (PixelateAnnotator)

# pixel_size: Tamanho do bloco de pixelização
anotador_pixelizar = sv.PixelateAnnotator(pixel_size=12)
quadro_pixelizado = anotador_pixelizar.annotate(
    scene=imagem_pessoas.copy(), detections=deteccoes_vis
)

# Pode-se sobrepor outros anotadores, como os rótulos
quadro_pixelizado = anotador_rotulo_custom.annotate(
    scene=quadro_pixelizado, detections=deteccoes_vis, labels=labels_vis
)
sv.plot_image(quadro_pixelizado, (10, 10))

2.3 Funções Auxiliares

2.3.1 Relacionadas a Vídeos

Leitura de Informações do Vídeo

import supervision as sv

# Obter informações básicas de um arquivo de vídeo
info_video = sv.VideoInfo.from_video_path(video_path="https://media.roboflow.com/supervision/video-examples/people-walking.mp4")
print("Informações do vídeo:", info_video)

Geração e Gravação de Quadros de Vídeo

import supervision as sv
from tqdm import tqdm

caminho_video_origem = "https://media.roboflow.com/supervision/video-examples/people-walking.mp4"
informacoes_video = sv.VideoInfo.from_video_path(caminho_video_origem)

# Gerador de quadros: stride define o intervalo de quadros, start/end definem o range
gerador_quadros = sv.get_video_frames_generator(source_path=caminho_video_origem, stride=10, start=0, end=100)
caminho_video_saida = "video_processado_out.mp4"

# Escrever os quadros processados em um novo arquivo de vídeo
with sv.VideoSink(target_path=caminho_video_saida, video_info=informacoes_video) as coletor_video:
    for quadro_gerado in tqdm(gerador_quadros, total=informacoes_video.total_frames // 10): # Estimativa para tqdm
        coletor_video.write_frame(frame=quadro_gerado)
print(f"Vídeo salvo em: {caminho_video_saida}")

Cálculo de FPS (Quadros por Segundo)

import supervision as sv

gerador_frames_fps = sv.get_video_frames_generator(source_path="https://media.roboflow.com/supervision/video-examples/people-walking.mp4")
monitor_fps = sv.FPSMonitor()

for frame_medicao in gerador_frames_fps:
    monitor_fps.tick() # Registra o tempo atual

# Calcular e exibir o FPS médio
fps_medio = monitor_fps.fps
print("FPS médio calculado:", fps_medio)

2.3.2 Relacionadas a Imagens

Salvamento de Imagens

import supervision as sv
import cv2
import os

diretorio_saida_imagens = 'imagens_salvas_exemplo'
# Criar um ImageSink para salvar imagens sequencialmente
with sv.ImageSink(
    target_dir_path=diretorio_saida_imagens,
    overwrite=True, # Sobreescrever se o diretório já existe
    image_name_pattern="imagem_seq_{:05d}.png" # Padrão de nome de arquivo
) as coletor_imagens:
    # Gerar alguns quadros para salvar (usando um vídeo como fonte)
    video_exemplo_path = "https://media.roboflow.com/supervision/video-examples/people-walking.mp4"
    for idx, img_frame in enumerate(sv.get_video_frames_generator(source_path=video_exemplo_path, stride=50, start=0, end=200)):
        coletor_imagens.save_image(image=img_frame)
        if idx >= 3: # Salvar apenas algumas para o exemplo
            break
print(f"Imagens salvas em: {diretorio_saida_imagens}")

Recorte de Imagens com Base em Caixas Delimitadoras

import supervision as sv
import cv2
from ultralytics import YOLO
import os

modelo_yolo_crop = YOLO("yolov8n.pt")
imagem_para_cortar = cv2.imread("img/person.png") # Assumindo 'person.png' existe

resultados_crop = modelo_yolo_crop(imagem_para_cortar, verbose=False)[0]
detecções_crop = sv.Detections.from_ultralytics(resultados_crop)

diretorio_recortes = 'recortes_objetos'
# Criar um ImageSink para salvar as imagens recortadas
with sv.ImageSink(target_dir_path=diretorio_recortes, overwrite=True) as coletor_recortes:
    for bbox_coords in detecções_crop.xyxy:
        # Recortar a imagem usando as coordenadas da caixa delimitadora
        imagem_recortada = sv.crop_image(image=imagem_para_cortar, xyxy=bbox_coords)
        coletor_recortes.save_image(image=imagem_recortada)
print(f"Imagens recortadas salvas em: {diretorio_recortes}")

2.4 Outras Funções

O Supervision oferece funcionalidades adicionais que não foram detalhadas aqui, como:

  • Classes para conversão de diversos formatos de conjuntos de dados (detecção de objetos e classificação de imagens) - veja a documentação de datasets.
  • Classes para cálculo de métricas de avaliação de detecção de objetos - veja a documentação de métricas.
  • Classes para desenho de formas geométricas diversas - veja a documentação de draw utils.
  1. Ferramentas para Tarefas Práticas

3.1 Contagem de Objetos em Linhas

A classe sv.LineZone permite contar o número de objetos que cruzam uma linha predefinida. O mecanismo envolve detecção e rastreamento de objetos, verificando se o ponto central da caixa delimitadora cruza a linha.

import supervision as sv
from ultralytics import YOLO
import numpy as np

modelo_linha_zona = YOLO("yolov8n.pt")
rastreador_linha_zona = sv.ByteTrack()

caminho_video_linha_zona = "https://media.roboflow.com/supervision/video-examples/vehicles.mp4"
gerador_quadros_linha_zona = sv.get_video_frames_generator(caminho_video_linha_zona, start=0, end=500)
info_video_linha_zona = sv.VideoInfo.from_video_path(caminho_video_linha_zona)

largura = info_video_linha_zona.width
altura = info_video_linha_zona.height

# Definir a linha de contagem (do lado esquerdo para o direito, no meio da altura)
ponto_inicio_linha = sv.Point(x=0, y=int(altura / 2))
ponto_fim_linha = sv.Point(x=largura, y=int(altura / 2))

# Inicializar o contador de linha
contador_de_linha = sv.LineZone(start=ponto_inicio_linha, end=ponto_fim_linha)

# Inicializar anotadores
anotador_tracos = sv.TraceAnnotator()
anotador_rotulos_linha = sv.LabelAnnotator(text_scale=2, text_color=sv.Color.BLACK)
anotador_zona_linha = sv.LineZoneAnnotator(thickness=4, text_thickness=4, text_scale=1)

with sv.ImageSink(target_dir_path='output_contagem_linha', overwrite=True, image_name_pattern="frame_linha_{:05d}.png") as sink_linha_zona:
    for quadro_atual in gerador_quadros_linha_zona:
        resultado_frame = modelo_linha_zona(quadro_atual, verbose=False)[0]
        deteccoes_frame = sv.Detections.from_ultralytics(resultado_frame)
        
        # Atualizar o rastreador
        deteccoes_frame = rastreador_linha_zona.update_with_deteccoes(deteccoes_frame)
        
        # Ativar o contador de linha
        cruzou_entrada, cruzou_saida = contador_de_linha.trigger(deteccoes_frame)
        
        # Gerar rótulos para as detecções (ID de rastreamento e nome da classe)
        rotulos_detalhes = [
            f"#{track_id} {modelo_linha_zona.model.names[class_id]}"
            for class_id, track_id in zip(deteccoes_frame.class_id, deteccoes_frame.tracker_id)
        ]
        
        # Anotar o quadro
        quadro_anotado_linha = anotador_tracos.annotate(scene=quadro_atual.copy(), detections=deteccoes_frame)
        quadro_anotado_linha = anotador_rotulos_linha.annotate(scene=quadro_anotado_linha, detections=deteccoes_frame, labels=rotulos_detalhes)
        quadro_anotado_linha = anotador_zona_linha.annotate(quadro_anotado_linha, line_counter=contador_de_linha)
        
        # sv.plot_image(quadro_anotado_linha) # Descomentar para visualizar em tempo real
        sink_linha_zona.save_image(image=quadro_anotado_linha)

# Imprimir as contagens finais
print(f"Total de objetos que entraram: {contador_de_linha.in_count}")
print(f"Total de objetos que saíram: {contador_de_linha.out_count}")

3.2 Detecção e Rastreamento em Regiões Específicas

A classe sv.PolygonZone permite monitorar a presença e a contagem de objetos dentro de áreas poligonais predefinidas. É útil para analisar tráfego em interseções, pessoas em zonas restritas, etc.

import numpy as np
import supervision as sv
from ultralytics import YOLO

modelo_zona_poligono = YOLO('yolov8n.pt')

caminho_video_zona = "https://media.roboflow.com/supervision/video-examples/vehicles-2.mp4"
info_video_zona = sv.VideoInfo.from_video_path(caminho_video_zona)
print("Informações do vídeo para zona poligonal:", info_video_zona)

gerador_quadros_zona = sv.get_video_frames_generator(caminho_video_zona)

# Definir polígonos para as zonas de interesse
poligonos_zonas = [
  np.array([
    [718, 595],[927, 592],[851, 1062],[42, 1059]
  ]),
  np.array([
    [987, 595],[1199, 595],[1893, 1056],[1015, 1062]
  ])
]

# Definir paleta de cores para as zonas e caixas
paleta_cores_zonas = sv.ColorPalette.DEFAULT

# Inicializar objetos PolygonZone para cada polígono
objetos_zona = [
    sv.PolygonZone(
        polygon=poligono,
        frame_resolution_wh=info_video_zona.resolution_wh
    )
    for poligono in poligonos_zonas
]

# Inicializar anotadores de zona para cada objeto_zona
anotadores_zona_pol = [
    sv.PolygonZoneAnnotator(
        zone=zona,
        color=paleta_cores_zonas.by_idx(idx_zona),
        thickness=4,
        text_thickness=8,
        text_scale=4,
        display_in_zone_count=True # Exibir a contagem de objetos na zona
    )
    for idx_zona, zona in enumerate(objetos_zona)
]

# Inicializar anotadores de caixa para cada zona
anotadores_caixa_zona = [
    sv.BoxAnnotator(
        color=paleta_cores_zonas.by_idx(idx_caixa),
        thickness=4,
        text_thickness=4,
        text_scale=2
    )
    for idx_caixa in range(len(poligonos_zonas))
]

with sv.ImageSink(target_dir_path='output_zona_poligonal', overwrite=True, image_name_pattern="frame_zona_{:05d}.png") as sink_zona:
    for quadro_processar in gerador_quadros_zona:
        # Realizar inferência com YOLOv8, ajustando o tamanho da imagem para melhor precisão
        resultados_zona = modelo_zona_poligono(quadro_processar, imgsz=1280, verbose=False)[0]
        detecções_zona = sv.Detections.from_ultralytics(resultados_zona)

        quadro_com_zonas = quadro_processar.copy()
        for zona_obj, anot_zona, anot_caixa in zip(objetos_zona, anotadores_zona_pol, anotadores_caixa_zona):
            # Identificar quais detecções estão dentro da zona
            mascara_na_zona = zona_obj.trigger(detections=detecções_zona)
            detecções_na_zona = detecções_zona[mascara_na_zona]
            
            # Anotar apenas os objetos dentro da zona
            quadro_com_zonas = anot_caixa.annotate(scene=quadro_com_zonas, detections=detecções_na_zona)
            quadro_com_zonas = anot_zona.annotate(scene=quadro_com_zonas)
        
        # sv.plot_image(quadro_com_zonas, (16, 16)) # Descomentar para visualizar
        sink_zona.save_image(image=quadro_com_zonas)

3.3 Inferência com Fatiamento (Slicing Aided Hyper Inference - SAHI)

Para otimizar a detecção de objetos pequenos, o Supervision oferece suporte à inferência com fatiamento (SAHI). Esta técnica divide a imagem em várias fatias, executa a inferência em cada fatia e na imagem completa, e então combina e filtra os resultados com NMS.

import cv2
import supervision as sv
from ultralytics import YOLO
import numpy as np

modelo_sahi = YOLO("yolov8n.pt")
imagem_para_sahi = cv2.imread("img/person.png") # Assumindo 'person.png' existe

# Detecção original sem fatiamento
resultados_sem_sahi = modelo_sahi(imagem_para_sahi, verbose=False)[0]
detecções_sem_sahi = sv.Detections.from_ultralytics(resultados_sem_sahi)
print(f"Detecções antes do SAHI: {len(detecções_sem_sahi)}")

# Função de callback que será aplicada a cada fatia da imagem
def inferencia_por_fatia(imagem_fatiada: np.ndarray) -> sv.Detections:
    resultado_fatia = modelo_sahi(imagem_fatiada, verbose=False)[0]
    return sv.Detections.from_ultralytics(resultado_fatia)

# Configurar o objeto InferenceSlicer
fatiador_inferencia = sv.InferenceSlicer(
    callback=inferencia_por_fatia,        # Função para processar cada fatia
    slice_wh=(320, 320),                   # Largura e altura das fatias
    overlap_ratio_wh=(0.3, 0.3),           # Taxa de sobreposição entre fatias
    iou_threshold=0.4,                     # Limite de IOU para NMS na combinação
    thread_workers=4                       # Número de threads para processamento paralelo
)

# Executar a inferência com fatiamento
detecções_com_sahi = fatiador_inferencia(imagem_para_sahi)
print(f"Detecções após o SAHI: {len(detecções_com_sahi)}")

3.4 Suavização de Trajetórias

A classe sv.DetectionsSmoother é uma ferramenta útil para suavizar as trajetórias de rastreamento de objetos em vídeos. Ela mantém um histórico das detecções para cada objeto rastreado e utiliza esse histórico para fornecer previsões mais suaves e consistentes.

import supervision as sv
from ultralytics import YOLO
import numpy as np

caminho_video_suavizacao = "https://media.roboflow.com/supervision/video-examples/grocery-store.mp4"
info_video_suavizacao = sv.VideoInfo.from_video_path(video_path=caminho_video_suavizacao)
gerador_quadros_suavizacao = sv.get_video_frames_generator(source_path=caminho_video_suavizacao)

modelo_suavizacao = YOLO("yolov8n.pt")
rastreador_suavizacao = sv.ByteTrack(frame_rate=info_video_suavizacao.fps)

# Inicializar o suavizador de detecções
# length: Número máximo de quadros a serem considerados para suavização de cada trajetória
suavizador = sv.DetectionsSmoother(length=4)

anotador_suavizacao = sv.BoundingBoxAnnotator()

with sv.VideoSink("output_suavizacao.mp4", video_info=info_video_suavizacao) as coletor_suavizacao:
    for quadro_process_suave in gerador_quadros_suavizacao:
        resultado_suave = modelo_suavizacao(quadro_process_suave, verbose=False)[0]
        detecções_suave = sv.Detections.from_ultralytics(resultado_suave)
        
        # Atualizar o rastreador
        detecções_suave = rastreador_suavizacao.update_with_deteccoes(detecções_suave)
        
        # Aplicar suavização às trajetórias de rastreamento
        detecções_suave = suavizador.update_with_deteccoes(detecções_suave)

        quadro_anotado_suave = anotador_suavizacao.annotate(quadro_process_suave.copy(), detecções_suave)
        
        # sv.plot_image(quadro_anotado_suave, (16, 16)) # Descomentar para visualizar
        coletor_suavizacao.write_frame(quadro_anotado_suave)

Tags: supervision visão-computacional deteccao-objetos rastreamento-objetos YOLOv8

Publicado em 8-11 04:09