Resumo

  • Vídeo não chega pronto: cada frame precisa ser decodificado, convertido de cor e copiado antes de virar tensor. A 30 fps por câmera, esse caminho roda o tempo todo, antes de qualquer IA.
  • Medi com um YOLO no fim do caminho: o caminho do frame consome 75% do tempo de um stream, e na escala de um DVR o decode em software quebra o tempo real perto de 20 câmeras, enquanto o NVDEC sustenta 32 usando 2 núcleos.
  • Recomendação direta: torchcodec no ecossistema PyTorch, PyAV quando precisar de controle, OpenCV para protótipo. Decord, não.

1. O custo que roda antes de qualquer modelo

Todo pipeline de visão computacional sobre vídeo tem duas metades: a que aparece nas reuniões, que é o modelo, e a que aparece na fatura de infraestrutura, que é tudo o que acontece antes de o frame existir.

Vídeo é dado comprimido. Uma câmera 1080p a 30 fps produz cerca de 187 MB por segundo de pixels crus em RGB; o H.264 entrega isso em poucos Mbps justamente porque cada frame depende dos vizinhos para ser reconstruído. A conta é paga na leitura: decodificar é reconstruir pixel por pixel, frame por frame, o tempo todo.

Com uma câmera, isso é rodapé. Com vinte, a proporção inverte: decode e pré-processamento passam a consumir mais ciclo do que a inferência, e a capacidade do sistema passa a ser definida por uma escolha que quase nunca é discutida em projeto: qual biblioteca lê o vídeo, e onde o frame nasce.

2. Como um vídeo vira frames

De arquivo a tensor: demux, decode, GOP, conversão de cor e o custo de cada etapa.

Quatro conceitos explicam praticamente todo comportamento estranho de performance em decode. Eles valem igual para um arquivo .mp4 e para o stream RTSP de uma câmera ao vivo: muda o envelope, não o trabalho.

Container não é codec. O .mp4, o .mkv ou a sessão RTSP são o envelope; dentro viajam pacotes comprimidos de um codec, como H.264, HEVC ou AV1. Separar os pacotes (demux) é barato. Reconstruir pixels a partir deles é o trabalho. E o RTSP tem uma diferença operacional que arquivo não tem: ele não espera. Se o consumidor atrasa, frame acumula ou cai, e é por isso que gargalo de decode em câmera ao vivo aparece como frame perdido, não como lentidão.

Frames dependem uns dos outros. O codec agrupa frames em GOPs (groups of pictures): um frame I (keyframe) é uma imagem completa; os frames P e B guardam só diferenças em relação a vizinhos. É isso que torna a compressão eficiente, e é isso que torna o seek caro: pedir o frame 500 exige voltar ao keyframe anterior e decodificar tudo desde ele. Seek "exato" paga esse custo; seek "aproximado" devolve um vizinho barato e resolve amostragem de treino.

O decoder não entrega RGB. O resultado sai em YUV (tipicamente NV12), o espaço de cor em que codecs trabalham. A conversão para RGB é uma conta por pixel: em 4K a 30 fps, 250 milhões de pixels por segundo, por stream. Medi até um quarto do throughput indo embora só nessa conversão.

Cópia de memória é etapa, não detalhe. Se o decode acontece na CPU e o modelo roda na GPU, cada frame cruza o barramento já descomprimido, em RGB cheio, 30 vezes por segundo por stream.

3. O código, nos dois caminhos

Decode na CPU com OpenCV, o ponto de partida de quase todo projeto:

import cv2

cap = cv2.VideoCapture("camera.mp4")   # ou "rtsp://usuario:senha@10.0.0.15/stream"
while True:
    ok, frame = cap.read()          # NumPy, BGR, na CPU
    if not ok:
        break
    rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)  # não esquecer

Decode na GPU com torchcodec, o frame nascendo no device do modelo:

from torchcodec.decoders import VideoDecoder

decoder = VideoDecoder("camera.mp4", device="cuda")   # NVDEC
frames = decoder.get_frames_at(indices=[0, 30, 60, 90])
# frames.data: tensor uint8 na GPU, pronto para o modelo

4. Os números

Mesmo vídeo em tudo (60 s, 1080p a 30 fps, H.264 a 8 Mbps, o perfil de uma câmera IP), GPUs de datacenter, inferência sempre na GPU, melhor de três execuções. São duas medições, cada uma respondendo uma pergunta: para onde vai o tempo de um stream (numa A100) e quantas câmeras a máquina sustenta (numa H100 com 8 núcleos de CPU, o tamanho de uma instância GPU típica).

Um stream: para onde vai o tempo

Coloquei um YOLO11n, o detector leve típico de pipeline de câmera, no fim dos dois caminhos de decode, com inferência em batch 32.

Sozinho, o modelo processa os 1.800 frames do vídeo em 0,76 segundo: 2.376 fps. Essa parte do pipeline está pronta para trabalhar.

O modelo precisa de menos de um segundo para o vídeo inteiro; o caminho do frame transforma isso em 5 a 6 segundos.

No caminho OpenCV, o mesmo vídeo leva 5,9 segundos, e 4,5 deles são caminho do frame: a GPU passa três de cada quatro segundos esperando. Trocar só o decode para o NVDEC corta a espera sem tocar no modelo. Foque no decode, que o modelo dá conta. Testei inclusive um modelo 8x mais pesado no mesmo pipeline e o relógio mal se mexeu, porque quem manda nele é o caminho do frame.

Repare que, com uma câmera só, a troca de decode parece render pouco: 302 para 364 fps. Guarde o motivo: com um stream, sobra máquina dos dois lados, e a GPU ociosa esconde a diferença. O jogo muda quando as câmeras somam, que é a segunda medição.

Trinta e duas câmeras: quem sustenta o tempo real

A pergunta de projeto nunca é o fps de um arquivo; é quantas câmeras a máquina segura a 30 fps cada. O teste tem o formato de um DVR: a mesma máquina (H100, 8 núcleos de CPU), o mesmo YOLO11n no fim, e o número de câmeras subindo de 8 para 32.

Câmeras Decode em software Decode no NVDEC
8 627 fps · 7,4 núcleos 1.192 fps · 2,1 núcleos
16 582 fps · 6,9 1.261 fps · 2,2
24 543 fps · 6,5 1.136 fps · 2,1
32 521 fps · 6,3 1.175 fps · 2,2

O DVR crescendo patamar a patamar: a meta de cada quantidade de câmeras, o que cada caminho entrega, e o ponto em que o software quebra sem lançar erro.

Em software, o agregado cai conforme as câmeras entram, com os 8 núcleos saturados o tempo todo: em 16 câmeras ainda sobra margem (36 fps por câmera), em 24 já não (23), em 32 vira 16. O sistema quebra o tempo real perto de 20 câmeras, e cada câmera nova piora as que já existiam. No NVDEC, o agregado fica em torno de 1.200 fps com 2 núcleos ocupados: as 32 câmeras rodam com folga, e sobram 6 núcleos para o resto da aplicação.

A mesma máquina com 8 câmeras: decodificando em software, os núcleos ficam presos em vídeo e a aplicação disputa o que sobra; no NVDEC, o decode sai da CPU e a máquina fica livre.

5. Decode na CPU ou na GPU

Decode na CPU paga conversão e cópia por frame; no NVDEC, o frame nasce na VRAM e o bloco de decode não disputa com o modelo.

O detalhe de hardware que explica os números: o NVDEC é um bloco dedicado da GPU, separado dos SMs (streaming multiprocessors, os núcleos de computação) que rodam o modelo. Decodificar nele não rouba capacidade da inferência, e o que sobe pelo barramento é o vídeo comprimido, não RGB cru. A NVIDIA publica o throughput por engine na nota de aplicação do NVDEC: 903 fps de H.264 1080p em Ada (nós medimos 905), 2.172 em Blackwell, com HEVC mais rápido que H.264 porque o silício é otimizado para os codecs novos. Placas de servidor carregam múltiplos engines por chip; as GeForce, um ou dois.

Três ressalvas antes de mover tudo para a GPU:

  • O ganho morre se o frame voltar. Decode em NVDEC seguido de .cpu() para redimensionar com NumPy recoloca a cópia que o desenho eliminou. Conversão, resize e augmentation precisam continuar em CUDA, que é o que torchcodec, DALI e o par PyNvVideoCodec + CV-CUDA fazem.
  • Suporte depende de codec e de placa. H.264 e HEVC decodificam em qualquer GPU NVIDIA recente; AV1 exige gerações novas. A matriz do Video Codec SDK responde por modelo.
  • Um vídeo só não mostra o ganho. Ficou medido: em throughput de um arquivo, o software ganhou. O bloco dedicado se paga em streams agregados e CPU liberada.

Fora da NVIDIA

Praticamente todo processador moderno carrega um bloco de decode; muda o nome, a API e o quanto o Python enxerga.

Fabricante Bloco Acesso prático em Python
NVIDIA NVDEC torchcodec, PyNvVideoCodec, DALI
Intel Quick Sync (VVC/H.266 no Lunar Lake, primeiro do mercado) PyAV/FFmpeg com hwaccel qsv ou vaapi
AMD VCN PyAV/FFmpeg com hwaccel vaapi
Apple Media engine (AV1 a partir do M3) PyAV/FFmpeg com hwaccel videotoolbox
ARM (SoCs) Varia por fornecedor; Pi 5 só decodifica HEVC FFmpeg/GStreamer do SoC

Fora da NVIDIA, o caminho em Python é o hwaccel do FFmpeg via PyAV, e o frame volta para a memória do sistema, sem o zero-copy que o mundo CUDA oferece. O padrão de custo se repete em qualquer bloco: no meu M2 Max, o VideoToolbox decodificou as mesmas 8 câmeras em menos de um núcleo, onde o software gastava onze. O Quick Sync é o mais subestimado: qualquer CPU Intel com iGPU decodifica dezenas de streams 1080p de graça, no hardware que já existe (o Xeon típico de rack não tem iGPU; o ganho vale para Xeon E, desktop reaproveitado e edge). E a AMD não publica números oficiais de throughput do VCN; para dimensionar, meça no hardware alvo com ffmpeg -hwaccel vaapi. Essa assimetria de documentação é parte do motivo de a NVIDIA dominar vídeo em servidor: não é só o engine, é saber quanto ele entrega antes de comprar.

6. O decode define o teto

O modelo é a parte visível do pipeline de visão, mas é o decode que define quantas câmeras cabem em cada máquina, e o que a fatura cobra por mês. Duas decisões resolvem a maior parte: biblioteca mantida e alinhada ao ecossistema do modelo, e frame nascendo no device onde vai ser consumido.

Pipeline de visão costuma nascer como protótipo, virar produção sem revisão e ficar anos rodando OpenCV single-thread numa máquina superdimensionada. O sintoma é sempre o mesmo: CPU alta com GPU ociosa. A correção raramente exige trocar o modelo. Exige trocar a leitura do vídeo.

Como ajudamos times a adotar IA

Trabalhamos com times de engenharia que buscam incorporar IA ao dia a dia de desenvolvimento, não como experimento, mas como parte real do fluxo de trabalho.

Isso passa por escolher as ferramentas certas para o contexto do time, configurá-las de forma que façam sentido para a política de dados da empresa e garantir que os desenvolvedores saibam usá-las de um jeito que realmente aumente a produtividade, em vez de gerar mais fricção.

Se você tem um time de desenvolvimento e está tentando colocar IA para trabalhar de forma séria, fale com a gente.