Normalne powierzchni
Estymacja normalnych powierzchni przewiduje kierunek zwrócenia każdej widocznej powierzchni. LibreYOLO udostępnia ją jako zadanie normal, które zwraca gęste pole wektorów jednostkowych na obszarze roboczym oryginalnego obrazu.
Definicja
Zadanie normal przewiduje z jednego obrazu RGB trójskładnikowy wektor
jednostkowy dla każdego piksela, czyli kierunek zwrócenia powierzchni w tym
punkcie. W przeciwieństwie do głębi dane wyjściowe nie mają dowolnej skali,
dlatego można bezpośrednio porównywać dwie predykcje bez wyrównywania.
Predykcja wypełnia result.normal_map, czyli strukturę NormalMap zawierającą
tablicę float32 (H, W, 3) na obszarze roboczym oryginalnego obrazu, dostępną
również jako result.normals. Wektory używają układu kamery OpenCV przyjętego w
LibreYOLO, gdzie +x wskazuje w prawo, +y w dół, a +z w głąb sceny. Są
skierowane do kamery, więc powierzchnia równoległa do płaszczyzny obrazu ma
wartość (0, 0, -1). Metoda .assert_normalized() sprawdza, czy każdy piksel
jest skończony i ma długość jednostkową w granicach tolerancji. Pole
result.boxes pozostaje puste, więc conf, iou i max_det nie mają wpływu,
a Results.plot() obsługuje to zadanie.
Modele
Zadanie normal obsługują dwie rodziny.
MoGe-2 jest rodziną wyspecjalizowaną. To jednoetapowy model geometrii monokularnej w trzech rozmiarach enkodera. LibreYOLO nie kopiuje tych checkpointów do własnej organizacji. Wczytanie pobiera odpowiedni rozmiar z oficjalnych repozytoriów w przypiętej rewizji i weryfikuje go względem zarejestrowanego skrótu SHA-256.
LibreMODUS generuje normalne jako jeden z celów modelu
przetwarzającego dowolne dane wejściowe na dowolne dane wyjściowe i może przyjąć
mapę głębi zamiast obrazu RGB. Wymaga dodatku modus oraz własnego
uwierzytelnionego konta Hugging Face. Nie oferuje ani val(), ani export(),
dlatego nie uczestniczy w poniższych sekcjach walidacji i eksportu.
Predykcja
Przy pierwszym użyciu wagi MoGe-2 są pobierane i zapisywane lokalnie w pamięci podręcznej.
from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreMoGe2s-normal.pt")result = model(SAMPLE_IMAGE, save=True) normals = result.normal_mapprint(normals.data.shape) # wektory jednostkowe float32 (H, W, 3)normals.assert_normalized() # zgłasza błąd, jeśli piksel nie ma długości jednostkowejfrom libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreMoGe2s-normal.pt")result = model(SAMPLE_IMAGE) # Układ kamery OpenCV: +x w prawo, +y w dół, +z w głąb sceny. Powierzchnia# zwrócona do kamery ma wartość zbliżoną do (0, 0, -1).field = result.normals.datah, w = field.shape[:2]print(field[h // 2, w // 2])from libreyolo import LibreYOLO, SAMPLE_IMAGE model = LibreYOLO("LibreMoGe2s-normal.pt")result = model(SAMPLE_IMAGE) # plot() renderuje pole; jest zdefiniowane dla wyników normalnych i krawędzi.result.plot().save("normals.png")imgsz musi być podzielne przez rozmiar patcha enkodera ViT, co LibreYOLO
sprawdza przed rozpoczęciem przebiegu. Predykcja listy obrazów wykonuje po jednym
przebiegu na obraz. To zadanie nie ma szybkiej ścieżki połączonego batcha.
Informacje o źródłach, streamingu i obsłudze wyników znajdują się w sekcji
predykcja.
Format zbioru danych
Walidacja normalnych paruje każdy obraz z trójkanałowym, 16-bitowym plikiem PNG o tej samej nazwie bazowej i rozdzielczości oraz z opcjonalną maską poprawności.
dataset/
data.yaml
images/
val/room.jpg
normals/
val/room.png
masks/
val/room.pngpath: dataset
train: images/train
val: images/val
normals_dir: normals
masks_dir: masks
nc: 1
names: {0: normal}Docelowy plik PNG ma dokładnie trzy kanały typu uint16, zapisane jako RGB.
Dekodowanie ma postać n = png / 65535 * 2 - 1, po czym każdy wektor jest
ponownie normalizowany. Zdekodowane wektory używają tego samego układu kamery
OpenCV co predykcje. Piksel maski jest uznawany za poprawny, gdy ma wartość
niezerową. Bez pliku maski każdy skończony, niezerowy zdekodowany wektor jest
poprawny. Niepoprawne i dopełnione piksele docelowe są wewnętrznie przechowywane
jako (0, 0, 0) i nigdy nie wpływają na metrykę. Pełny kontrakt opisano w sekcji
formaty zbiorów danych.
Trenowanie
Żadna rodzina normalnych nie ma implementacji trenowania. train() zgłasza
NotImplementedError w obu przypadkach. Strona MoGe-2 wskazuje przypięte
oficjalne checkpointy do predykcji, walidacji i eksportu.
Walidacja
val() mierzy kąt między każdym przewidzianym wektorem a jego wektorem danych
referencyjnych (ground truth) dla pikseli oznaczonych przez zbiór danych jako
poprawne.
from libreyolo import LibreYOLO model = LibreYOLO("LibreMoGe2s-normal.pt")metrics = model.val(data="my-dataset.yaml", imgsz=518) print(metrics["metrics/mean_angular_error"]) # stopnieprint(metrics["metrics/median_angular_error"]) # stopnieprint(metrics["metrics/within_11_25"]) # procent pikseliprint(metrics["metrics/within_22_5"], metrics["metrics/within_30"])metrics/mean_angular_error i metrics/median_angular_error określają ten kąt w
stopniach, a niższa wartość jest lepsza. metrics/within_11_25,
metrics/within_22_5 i metrics/within_30 to odsetek poprawnych pikseli, których
błąd kątowy mieści się odpowiednio w 11.25, 22.5 i 30 stopniach, więc wyższa
wartość jest lepsza. Należy zwrócić uwagę na jednostkę: te trzy wartości są
procentami, a nie ułamkami. fitness to metrics/within_11_25 podzielone przez
100, co umieszcza wybór najlepszego checkpointu na tej samej skali [0, 1] co
w każdym innym zadaniu.
Eksport
Wyeksportowany model normalnych jest ponownie wczytywany przez LibreYOLO() na
podstawie sufiksu pliku, więc plik .onnx zachowuje się jak checkpoint i zwraca
ten sam obiekt Results.
from libreyolo import LibreYOLO model = LibreYOLO("LibreMoGe2s-normal.pt")model.export(format="onnx", imgsz=518)from libreyolo import LibreYOLO, SAMPLE_IMAGE # Fabryka wybiera ścieżkę na podstawie sufiksu pliku, więc wyeksportowany artefakt# wczytuje się jak dowolny checkpoint i zwraca ten sam obiekt Results.model = LibreYOLO("LibreMoGe2s-normal.onnx")result = model(SAMPLE_IMAGE) print(result.normal_map.data.shape)Eksport normalnych używa kontraktu środowiska uruchomieniowego o stałej
rozdzielczości i batchu 1. dynamic oraz batch inny niż 1 są odrzucane, a
imgsz musi być podzielne przez rozmiar patcha enkodera. Zakres poszczególnych
formatów znajduje się na stronie MoGe-2 i w
pełnej macierzy eksportu.
Eksport wymienia argumenty akceptowane przez każdy format.