Logo NascimentoIgor Nascimento / Blog
Visão computacional

Stratosphere: por que separar "onde" de "quem" funciona, e o que apodrece quando o banco é mal cuidado

Detecção de marcas em escala, com marcas novas entrando o tempo todo e sem ciclo de retreino. Uma conversa sobre as decisões que fazem a pipeline acertar, os números que forçaram cada uma delas, e a parte que ninguém gosta de ouvir: o banco de referências é o componente que mais determina o resultado e o que mais se deteriora em silêncio.

Visão computacionalIA aplicada22 min de leituraIgor Nascimento

O requisito que descarta a resposta óbvia

O problema parece simples: receber fotos de eventos, redes sociais e transmissões, e dizer quais marcas aparecem em cada uma. A resposta óbvia é treinar um classificador, ou um detector, com uma classe por marca. Ela morre no primeiro requisito de negócio: marca nova entra no portfólio toda semana, e não pode exigir um ciclo de retreino. Com classificador por marca, cada cliente novo é uma rodada de anotação, treino e validação, e o custo de inferência cresce com o número de classes.

O Stratosphere parte de outra decomposição, que cabe numa frase: separar onde tem logo de qual logo é. Um detector agnóstico de marca acha as regiões candidatas. Uma busca vetorial num banco de referências diz de quem são. Marca nova vira uma pasta de imagens a mais. É a mesma lógica de detector de rosto, que acha qualquer rosto, somado a reconhecimento facial, que compara com um cadastro.

Classificador por marca

  • Marca nova: um ciclo de treino
  • Inferência cresce com o número de marcas
  • Quem sabe o que é "Nike": o modelo

Retrieval (Stratosphere)

  • Marca nova: copiar imagens numa pasta
  • Inferência constante em relação às marcas
  • Quem sabe o que é "Nike": o banco

Essa escolha tem uma consequência estrutural que vale destacar: o detector nunca é informado de nome de marca. Os prompts são de conceito ("logo", "emblem", "wordmark", "printed label on packaging"), e a entidade Detection nem tem campo brand. A ausência é proposital. É o que impede o acoplamento de voltar por descuido numa refatoração qualquer.

A pipeline: cada camada só vê o que a anterior deixou passar

Um detector de vocabulário aberto devolve cerca de 55 regiões candidatas por imagem. Se cada uma passasse por todas as camadas, a pipeline custaria segundos. A regra de projeto é que cada camada é mais cara que a anterior e só vê o que a anterior não resolveu. Na ordem:

  1. Pré-filtro: densidade de bordas. Imagem sem estrutura não merece uma passada de detector. Permissivo de propósito, porque o que se descarta aqui nunca mais volta.
  2. Detector agnóstico (OWLv2) com confiança mínima de 0,05. Baixo de propósito: o detector só precisa acertar onde. Limiar alto troca recall, que é irrecuperável, por precisão, que é recuperável adiante.
  3. Codificador (SigLIP2) transforma cada recorte em um vetor, e a busca vetorial devolve os 25 vizinhos mais próximos no banco.
  4. Roteador, uma primeira vez, sem geometria. O que ele decide com folga (aceite ou rejeição) está decidido.
  5. Verificação geométrica (DISK + LightGlue) só nas regiões em dúvida, e o roteador decide de novo.
  6. Colapso de caixas aninhadas, corroboração dentro da imagem e, opcionalmente, um juiz visual (Qwen3.5-4B) sobre o que ainda ficou em revisão.

O ganho dessa ordem é medido: a pipeline roda em 0,7 segundo por imagem numa RTX 3060. Verificando geometria em tudo, como a primeira implementação fazia, são 3,0 segundos para o mesmo resultado. Numa imagem típica eram 55 regiões vezes 4 referências, 220 comparações a cerca de 87 ms cada, a maioria em regiões que o consenso já tinha decidido.

Trade-off

Rodar geometria só na dúvida tem um custo dito claramente no código: uma região rejeitada pela pontuação não ganha segunda chance. Isso ameaçava a fila mais valiosa do sistema, o órfão geométrico, que depende de geometria forte com similaridade baixa. A solução foi um guarda: região rejeitada mas com similaridade acima do portão da geometria continua sendo verificada. É uma exceção à regra de economia, e ela existe porque a regra, sozinha, apagaria o mecanismo de melhoria do banco.

Por que funciona: cinco decisões, cada uma com o número que a forçou

1. O codificador certo, e o teste que não precisava de rótulo

A primeira versão rodava com DINOv2, e o resultado era inexplicável: amazon e azul com similaridade 0,903, itau lido como sadia, 238 pares de marcas diferentes parecidos demais no banco. A causa só apareceu com um teste que o próprio banco oferecia de graça. Várias referências vinham da mesma foto de backdrop, recortadas em marcas diferentes, e o nome do arquivo guardava o hash da origem. Isso dá dois conjuntos sem rotular nada: positivos (mesma marca, fotos diferentes) e negativos (mesma foto, marcas diferentes).

Medido com AUC, que avalia a ordem e não o valor absoluto: DINOv2 separava com 0,515, que é moeda. SigLIP2, com 0,812. O DINOv2 é auto-supervisionado e aprendeu que o que torna duas imagens parecidas é a superfície: o painel, a luz, a moldura. Dois logos diferentes do mesmo painel pontuavam 0,676, acima de duas fotos da mesma marca, 0,671. O SigLIP2 foi treinado casando imagem com legenda, e legenda fala de marca, não de "retângulo branco com moldura escura".

Há um detalhe que confirma o mecanismo: apertar o recorte ajuda o DINOv2 (0,515 para 0,655, porque tira o painel do quadro) e atrapalha o SigLIP2 (0,812 para 0,778). Um precisava que o contexto fosse escondido; o outro usa o contexto porque entende o que está olhando.

A lição que vale fora deste projeto

A tabela de nove codificadores medidos em imagem crua apontava o SigLIP2 giant como vencedor, com 0,920. Medindo os vetores como ficam no índice, depois do recorte quadrado com letterbox que a pipeline aplica, a ordem inverteu: o base com cabeça de pooling foi o único que subiu (0,801 para 0,862) e empatou com um modelo três vezes maior. A causa é o letterbox: a agregação por quarto central funciona em imagem cheia e falha em imagem que já tem borda vazia por construção. Só conta a medição feita na população em que o sistema roda. O mesmo erro quase fez o SIFT parecer melhor que o LightGlue.

2. Consenso vale mais que similaridade absoluta

A intuição diz que a decisão é "qual a similaridade do vizinho mais próximo". Os dados dizem outra coisa. Medido em imagem real: um swoosh de verdade teve similaridade 0,712 com 21 de 25 vizinhos da mesma marca; um texto sem marca nenhuma teve similaridade maior, 0,837, com apenas 3 de 25, e o resto espalhado entre quatro marcas sem relação. Pela similaridade, o ruído ganha do logo. Pelo consenso do top-k, não.

Por isso o consenso tem peso 0,20 na pontuação e é também uma regra de aceite por conta própria: em 84 imagens rotuladas, consenso acima de 0,80 não errou uma vez em 15 regiões. Mas ele tem um teto: é normalizado pelo mínimo entre k e o número de referências da marca, senão uma marca com 5 referências jamais alcançaria o consenso de uma com 40, e a cobertura do banco viraria critério de decisão sem ninguém ter escolhido isso. E tem um piso: consenso não resgata região abaixo do limiar de rejeição. Ele promove de revisão para aceite, nunca de descarte para aceite.

3. A geometria só soma

A verificação geométrica pergunta "é literalmente o mesmo desenho?" casando pontos entre o recorte e a referência. A trava mais importante do roteador é que "não confirmei" significa "não sei", nunca "não é". Logo chapado, pequeno ou vetorial não tem pontos para casar, e contagem baixa não é prova contra.

Isso foi aprendido da forma difícil. Ao trocar SIFT por LightGlue, que é muito melhor, o resultado em 20 imagens de rede social piorou: 10 aceites contra 18. O motivo é contraintuitivo. O SIFT ficava mudo em metade dos pares certos, e o silêncio dele redistribuía o peso e subia a nota. O LightGlue opina em quase tudo, então uma região com 25 inliers passou a receber 0,28 de geometria onde antes recebia redistribuição, e caiu abaixo do limiar por ter ganhado um sinal. A solução tem duas partes: contagem dentro da faixa de ruído conta como silêncio (peso redistribuído), e, se mesmo assim a geometria baixaria a nota, ela é silenciada.

Há um caso real registrado no código que mostra o que estava em jogo: um swoosh com consenso 0,92 e 23 das 25 respostas em nike caiu de aceite para descarte porque 12 inliers derrubaram a nota de 0,42 para 0,377, cruzando o piso de rejeição por 23 milésimos.

4. O roteador é uma política ordenada, e a ordem é o produto

O núcleo do sistema é um serviço de domínio puro, sem I/O e sem GPU, que aplica regras em ordem fixa: sem candidatos, marca negativa, grupo de confusão, parecer do juiz, órfão geométrico, consenso, aceite por geometria, limiares. A primeira que casa decide, e as regras de negócio vêm antes das de evidência. Invertido, um empate gatorade contra powerade com geometria forte viraria órfão e escaparia do desempate obrigatório.

Duas filas nessa lista merecem explicação porque não são óbvias. Negativa é marca cadastrada no banco que não é cliente. Sem ela, o sistema é obrigado a escolher entre as marcas do portfólio e sempre escolhe alguma; com ela, "isto é logo de quem não interessa" vira um acerto, não uma rejeição. Órfão é a mais valiosa: a geometria confirma a marca e o banco não reconhece bem a variação. Não é erro. É o sistema dizendo "tenho esta marca e não tenho esta aplicação dela". Promover essas regiões de volta ao banco é o que faz o sistema melhorar com o uso.

Cada decisão carrega reasons, as regras na ordem em que decidiram, e contributions, quanto cada sinal somou. Parece enfeite até a primeira vez que alguém pergunta "por que esta região caiu nesta fila?" e a resposta sai do JSON sem reexecutar nada.

5. O que a imagem inteira sabe e a região não

Todo o resto decide região por região. Mas ninguém fotografa um logo isolado: fotografa um backdrop com o patrocinador repetido oito vezes. Numa imagem real, volkswagen aparecia três vezes, uma aceita com 110 inliers e duas em revisão. A corroboração aceita a região em revisão quando a mesma foto já confirmou a marca em outra caixa, por conta própria. Isso não é baixar o limiar: baixar o limiar compra as mesmas promoções e paga com falso aceite em toda região do sistema. Aqui a promoção exige uma evidência que o limiar não tem como exprimir, e duas travas a mantêm honesta: a testemunha tem que ser um aceite (duas dúvidas não viram certeza), e a região promovida precisa de similaridade própria alta, porque região errada tem mediana 0,539 e p90 0,755.

Os trade-offs que o projeto assume de olhos abertos

O que apodrece quando o banco é mal cuidado

Esta é a parte que eu considero a mais importante do projeto, porque é a que não aparece em nenhuma demonstração. O banco de referências determina o resultado mais que o detector e mais que os limiares. Marca que o banco cobre mal não é recuperável por regra nenhuma. E ele se deteriora de formas que não disparam alarme. Cada item abaixo veio de um caso medido.

Cobertura de variante vale mais que quantidade

Numa coletiva de imprensa, com a marca correta em primeiro lugar na busca em todos os casos, a nike foi rejeitada, e ela tinha 35 referências no banco. Só que 27 eram de uniforme e 2 de backdrop. Um swoosh gigante num painel procurava vizinho entre fotos de camisa e não achava nenhuma parecida: o consenso desabava para 2 de 25 e a região morria. Não era dúvida sobre a marca; era aritmética do banco. Quatro referências boas de tipos diferentes (backdrop, uniforme, digital, logo oficial) valem mais que trinta do mesmo tipo.

Enquadrar na placa ensina a placa

Medido no par amazon contra azul, recortados da mesma foto: com placa, moldura e fundo, 0,903 de similaridade entre marcas diferentes. Tirando o fundo, 0,801. Só o wordmark, 0,630. Cor quase não entra nessa conta: a mesma imagem em tons de cinza fica a 0,929 do original. O que separa marca de marca é a forma do que sobra no quadro.

Mas há um piso. Cortando a 40%, a similaridade sobe de novo para 0,699: o corte comeu letras, e fragmento mutilado volta a parecer com outro fragmento mutilado. Por isso a ferramenta de reenquadramento usa a caixa do próprio detector da pipeline, com folga pequena, e não uma fração fixa. E usa o mesmo detector de produção porque assimetria de preparo entre referência e consulta é fonte silenciosa de similaridade baixa em par que deveria casar.

PNG com transparência descartada vira retângulo preto

Quatro referências do banco original eram exatamente isso, e uma delas colocava cimed e nike a 0,951 de similaridade, o pior par do banco inteiro, entre duas marcas sem nenhuma semelhança. Um bug de carregamento de imagem virou um ímã permanente de falso positivo, e ninguém veria isso olhando a pasta.

Marca super-representada vaza para onde não deve

A cbf tinha 65 referências, mais que qualquer outra. Resultado: quase todo top-25 era cbf, e a bandeira do Brasil era aceita como cbf com pontuação 0,44. O escudo tem um círculo azul com estrelas sobre verde e amarelo, e o consenso chegava a 1,00 em cima de similaridade 0,75. Isso forçou duas mudanças: consenso sem similaridade mínima não vale, e consenso não resgata região abaixo do limiar de rejeição. Mas a lição operacional é anterior: desconfie de marca com muitas referências.

Quase-cópias deslocam o vizinho útil

Trinta fotos do mesmo ângulo são um vetor. Elas não acrescentam cobertura, ocupam o topo da busca com cópias e deslocam do top-k o vizinho que alimentaria o consenso. A construção do banco deduplica dentro de cada marca, e nunca entre marcas, porque duas marcas parecidas acima do limiar são exatamente o que a auditoria precisa enxergar.

Trocar o codificador invalida tudo, e metade disso falha em silêncio

O índice guarda a assinatura do codificador e recusa a combinação errada na carga. Mas os limiares de similaridade não têm essa proteção: min_similarity, redundancy_similarity, alert_similarity, entry_similarity, orphan_max_similarity vivem todos na escala do codificador. Quando o projeto trocou DINOv2 por SigLIP2, o limiar de deduplicação ficou em 0,95 e cortou o banco de 753 para 524 referências antes de alguém notar. O que era "quase cópia" numa escala é "mesma marca, outra foto" na outra. A amazon caiu de 26 para 3 referências.

Promover a partir da imagem de avaliação fabrica métrica

O loop de melhoria é promover órfãos para o banco. Mas se a imagem usada para medir entra no banco que será medido, o sistema passa a reconhecer um recorte de si mesmo, e todo número produzido depois é ficção. Por isso a ferramenta de garimpagem tem --excluir e registra no log o que foi excluído. E nada entra sozinho: medido, das 20 candidatas com similaridade acima de 0,82, só 11 eram a marca certa. itau lido como sadia, um político lido como vivo, um apresentador lido como uber.

O que eu faria desde o primeiro dia

As ferramentas que já existem para enxergar isso, e o que ainda falta

Tudo o que descrevi acima foi descoberto na mão, caso a caso. A pergunta que vale fazer depois é quanto disso o repositório já consegue detectar sozinho hoje. A resposta é: boa parte, desde que alguém rode. A pasta tools/ e a CLI têm uma ferramenta para quase cada modo de deterioração, e cada uma nasceu de um dos casos medidos. O que elas têm em comum é uma regra que o projeto não abre mão: nenhuma escreve no banco. Todas medem, relatam e sugerem; a decisão de reconstruir o índice continua sendo humana.

Modo de deterioraçãoFerramentaO que ela enxerga
Marcas parecidas demais entre sistratosphere auditarRoda o banco contra ele mesmo e lista pares de marcas diferentes acima do limiar de alerta, do pior para o melhor. Foi com ela que os 238 pares viraram 32. Também imprime a contagem por marca e, com --minimo, as marcas com cobertura fina demais, que tendem a produzir órfão em vez de acerto
Referência ruim entrandotools/prepare_reference.pyCompõe a transparência, reenquadra com o detector da pipeline, amplia o que for pequeno e só então compara com o banco. Devolve um de quatro veredictos antes de gravar: REDUNDANTE, PERIGOSA (parecida com outra marca), ROTULO SUSPEITO (o banco prefere outra marca à declarada) e pequena demais. Grava uma cópia preparada numa pasta; não toca no índice
Enquadramento na placatools/tighten_references.pyReenquadra o banco inteiro no logo usando o mesmo detector de produção, com folga pequena, e copia como está quem não tem caixa utilizável, porque perder referência é pior que mantê-la larga
Variante faltandotools/mine_references.pyGarimpa candidatas a referência nova em fotos não rotuladas, por marca, com --excluir para manter a imagem de avaliação fora e um log do que foi excluído. Entrega candidatas para conferência, nunca promove
Regra de admissão que corta coberturatools/sweep_admission.py e tools/ablate_admission.pyA primeira diz quantas referências legítimas cada limiar recusaria e de qual variante; foi ela que mostrou o corte de 48px recusando 227 de 753, quase tudo da variante digital. A segunda constrói um índice sem elas, roda a pipeline inteira nos dois e compara recall, carga humana, órfãos e marcas espúrias. É cara e por isso existe separada
Limiares de outra escalatools/calibrate_thresholds.pyCom imagens rotuladas por pasta, imprime a distribuição de similaridade, consenso e inliers dos acertos contra a dos erros e sugere os quatro limiares do roteador. É a resposta ao caso do DINOv2 para SigLIP2, mas só se alguém lembrar de rodá-la
Roteamento mal ajustadotools/dump_signals.py e tools/sweep_routing.pyA primeira roda as camadas caras uma vez e grava os sinais crus por região; a segunda explora configurações do roteador em segundos sobre esse arquivo, usando o QueueRouter de verdade. Mede as três forças: precisão do aceite, volume de revisão e perda por rejeição, que é a que some sem deixar rastro
Juiz visual descalibradotools/calibrate_judge.pyMede a probabilidade que o modelo devolve em acertos e em erros e sugere os dois cortes por percentil, porque um corte ingênuo em 0,5 aprova tudo

Lida assim, a tabela parece completa. Ela não é, e vale dizer onde estão os buracos e por que eles existem.

O que ainda falta

Por que esses buracos têm a forma que têm

Repare que todos os itens que faltam são da mesma família: estado entre execuções. Histórico do índice, acúmulo de órfãos, limiares versionados, lista de reserva. As ferramentas que existem são todas sem estado, rodam sobre o que está na pasta agora e terminam. Isso foi uma escolha, e uma boa escolha no começo, porque manteve cada ferramenta pequena, testável e independente. O custo aparece exatamente nos modos de deterioração que são lentos: os que não quebram nada hoje e custam um relatório errado daqui a três meses. O próximo passo do projeto não é mais uma ferramenta de medição. É um manifesto do banco, com versão, assinatura do codificador, limiares e lista de reserva, para que as ferramentas que já existem passem a comparar em vez de só fotografar.

Por que não um banco vetorial de verdade

Uma pergunta que sempre aparece. O banco é um npz com os vetores mais um json com os metadados, e a busca é uma multiplicação de matriz. Dezenas de marcas com algumas dezenas de referências cada dão milhares de vetores, e a consulta leva milissegundos. Banco vetorial passa a valer a partir de centenas de milhares de vetores; antes disso ele acrescenta um serviço para operar, um processo para subir e um estado a mais para dessincronizar. E o json é auditável com um editor de texto, o que importa muito quando a pergunta é "por que esta referência está puxando falso positivo?".

Resumo da conversa