Ver como markdown

Normais de superfície

A estimativa de normais de superfície prevê a direção para a qual cada superfície visível aponta. O LibreYOLO expõe isso como a tarefa normal, que devolve um campo denso de vetores unitários no canvas da imagem original.

Definição

A tarefa normal prevê um vetor unitário de três componentes por pixel a partir de uma única imagem RGB: a direção para a qual a superfície naquele pixel aponta. Diferente da profundidade, a saída não tem escala livre, então duas predições são diretamente comparáveis sem alinhamento.

Uma predição preenche result.normal_map, um payload NormalMap que guarda um array (H, W, 3) float32 no canvas da imagem original, também acessível como result.normals. Os vetores usam o referencial de câmera OpenCV do LibreYOLO, com +x para a direita, +y para baixo e +z para dentro da cena, e apontam para a câmera, então uma superfície fronto-paralela é (0, 0, -1). .assert_normalized() verifica se todo pixel é finito e tem comprimento unitário dentro de uma tolerância. result.boxes fica vazio, então conf, iou e max_det não têm efeito, e Results.plot() cobre esta tarefa.

Modelos

Duas famílias atendem normal.

MoGe-2 é a família dedicada: um modelo de geometria monocular de uma única passagem em três tamanhos de encoder. O LibreYOLO não copia esses checkpoints para a sua própria organização; carregar um baixa o tamanho correspondente dos repositórios oficiais em uma revisão fixada e o confere contra um SHA-256 registrado.

LibreMODUS produz normais como um dos alvos de um modelo any-to-any, e pode receber um mapa de profundidade em vez de uma imagem RGB como entrada. Precisa do extra modus e da sua própria conta autenticada no Hugging Face, e não oferece nem val() nem export(), então não participa das seções de validação e exportação abaixo.

Predição

Os pesos do MoGe-2 são baixados no primeiro uso e ficam em cache localmente.

Prever um campo de normais
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreMoGe2s-normal.pt")result = model(SAMPLE_IMAGE, save=True) normals = result.normal_mapprint(normals.data.shape)      # vetores unitários (H, W, 3) float32normals.assert_normalized()    # levanta erro se algum pixel não for unitário
Ler um pixel
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreMoGe2s-normal.pt")result = model(SAMPLE_IMAGE) # Referencial de câmera do OpenCV: +x à direita, +y para baixo, +z para# dentro da cena. Uma superfície voltada para a câmera fica perto de (0, 0, -1).field = result.normals.datah, w = field.shape[:2]print(field[h // 2, w // 2])
Salvar a visualização
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreMoGe2s-normal.pt")result = model(SAMPLE_IMAGE) # plot() renderiza o campo; está definido para resultados de normais e bordas.result.plot().save("normals.png")

imgsz precisa ser divisível pelo tamanho de patch do encoder ViT, o que o LibreYOLO confere antes de a execução começar. Predizer uma lista de imagens roda um forward pass por imagem; esta tarefa não tem caminho rápido de batch empilhado. Veja predição para fontes, streaming e tratamento de resultados.

Formato do dataset

A validação de normais emparelha cada imagem com um PNG de 16 bits e três canais, de mesmo nome-base e mesma resolução, mais uma máscara de validade opcional.

dataset/
  data.yaml
  images/
    val/room.jpg
  normals/
    val/room.png
  masks/
    val/room.png
yaml
path: dataset
train: images/train
val: images/val
normals_dir: normals
masks_dir: masks
nc: 1
names: {0: normal}

O PNG alvo é exatamente uint16 de três canais, com os canais armazenados como RGB. A decodificação é n = png / 65535 * 2 - 1 seguida da renormalização de cada vetor, e os vetores decodificados usam o mesmo referencial de câmera do OpenCV das predições. Um pixel de máscara conta como válido quando é diferente de zero; sem arquivo de máscara, todo vetor decodificado finito e diferente de zero é válido. Pixels de alvo inválidos e de padding são mantidos internamente como (0, 0, 0) e nunca contribuem para uma métrica. Veja formatos de dataset para o contrato completo.

Treinamento

Nenhuma das duas famílias de normais tem implementação de treinamento: train() levanta NotImplementedError nas duas. A página do MoGe-2 indica seus checkpoints oficiais fixados para predição, validação e exportação.

Validação

val() mede o ângulo entre cada vetor predito e o seu vetor de ground truth, nos pixels que o dataset marca como válidos.

Validar e ler as chaves das métricas
from libreyolo import LibreYOLO model = LibreYOLO("LibreMoGe2s-normal.pt")metrics = model.val(data="my-dataset.yaml", imgsz=518) print(metrics["metrics/mean_angular_error"])     # grausprint(metrics["metrics/median_angular_error"])   # grausprint(metrics["metrics/within_11_25"])           # porcentagem de pixelsprint(metrics["metrics/within_22_5"], metrics["metrics/within_30"])

metrics/mean_angular_error e metrics/median_angular_error são esse ângulo em graus, e quanto menor, melhor. metrics/within_11_25, metrics/within_22_5 e metrics/within_30 são a porcentagem de pixels válidos cujo erro angular fica dentro de 11.25, 22.5 e 30 graus, então quanto maior, melhor. Repare na unidade: esses três são porcentagens, não frações. fitness é metrics/within_11_25 dividido por 100, o que coloca a seleção do melhor checkpoint na mesma escala [0, 1] de todas as outras tarefas.

Exportação

Um modelo de normais exportado é carregado de volta por LibreYOLO() pelo sufixo do arquivo, então um arquivo .onnx se comporta como um checkpoint e devolve o mesmo Results.

Exportar
from libreyolo import LibreYOLO model = LibreYOLO("LibreMoGe2s-normal.pt")model.export(format="onnx", imgsz=518)
Rodar o arquivo exportado
from libreyolo import LibreYOLO, SAMPLE_IMAGE # A factory decide pelo sufixo do arquivo, então um artefato exportado# carrega como qualquer checkpoint e devolve o mesmo objeto Results.model = LibreYOLO("LibreMoGe2s-normal.onnx")result = model(SAMPLE_IMAGE) print(result.normal_map.data.shape)

A exportação de normais usa um contrato de runtime de resolução fixa e batch 1: dynamic e um batch diferente de 1 são rejeitados, e imgsz precisa ser divisível pelo tamanho de patch do encoder. A cobertura por formato está na página do MoGe-2 e na matriz completa de exportação. Exportação lista os argumentos que cada formato aceita.

Verificado com o LibreYOLO v1.5.0.