7.7. Normalizacja¶
ml.Model.predict() przyjmuje listę wejść, ponieważ niektóre sieci mają więcej niż jeden tensor wejściowy, lecz lista nie ma sposobu, by w sposób wbudowany przenosić argumenty dla poszczególnych wejść – nie ma slotu kwarg na „skadruj to wejście do (x, y, w, h), ale pozostaw pozostałe wejścia bez zmian”. ml.preprocessing.Normalization to opakowanie wypełniające tę lukę. Instancja Normalization przechowuje parametry dla jednego wejścia; skrypt przekazuje opakowane wejście na liście predict, ilekroć potrzebuje czegokolwiek innego niż wartości domyślne.
Najczęstszym powodem, by po nią sięgnąć, jest skadrowanie określonego obszaru przechwyconej ramki do sieci zamiast całego obrazu.
7.7.1. Parametry¶
Normalization(scale=(0.0, 1.0),
mean=(0.0, 0.0, 0.0),
stdev=(1.0, 1.0, 1.0),
roi=None)
roi– prostokąt(x, y, w, h)w ramce źródłowej do skadrowania przed skalowaniem. Domyślnie cała ramka. Większość zastosowańNormalizationustawia tylko ten parametr.scale– zakres(min, max), którego tensory wejściowe zmiennoprzecinkowe oczekują po normalizacji. Zakres pikseli0..255jest liniowo odwzorowywany na ten zakres. Powszechnymi wartościami są(0.0, 1.0)dla sieci trenowanych z ReLU oraz(-1.0, 1.0)dla sieci znormalizowanych symetrycznie.mean– średnia na każdy kanał(R, G, B)odejmowana od obrazu po skalowaniu. Odpowiada statystykom kanałów, na których trenowano sieć – kanonicznym przykładem jest(0.485, 0.456, 0.406)dla sieci wywiedzionych z ImageNet. Sieci w skali szarości redukują średnią do wartości luminancji przy użyciu standardowego0.299*R + 0.587*G + 0.114*B.stdev– odchylenie standardowe na każdy kanał(R, G, B), przez które dzielony jest obraz po odjęciu średniej, ponownie odpowiadające statystykom treningowym sieci. Redukowane do luminancji w ten sam sposób dla sieci w skali szarości.
7.7.2. Kiedy parametry mają znaczenie¶
scale, mean i stdev są ignorowane, gdy input_dtype sieci to int8 lub uint8. Dla sieci o wejściu całkowitoliczbowym bajty skadrowanego obrazu są zapisywane wprost do tensora, a konwersję int-na-rzeczywiste obsługują własne input_scale i input_zero_point sieci. Te trzy parametry mają znaczenie tylko wtedy, gdy sieć oczekuje wejścia zmiennoprzecinkowego.
roi jest odczytywany w każdym przypadku – kontroluje, która część ramki źródłowej trafia do sieci, niezależnie od dtype wejścia.
7.7.3. ROI i skalowanie¶
ROI jest dwuliniowo skalowany ze swoich wymiarów źródłowych do wymiarów wejściowych sieci. Obraz jest wyśrodkowany w miejscu docelowym, a skalowanie wypełnia miejsce docelowe – nie zachowuje proporcji. Nie-kwadratowy ROI podany na kwadratowe wejście sieci wychodzi rozciągnięty poziomo lub pionowo.
To, czy rozciągnięcie ma znaczenie, zależy od sieci. Modele wykrywania twarzy i punktów charakterystycznych, takie jak rodzina MediaPipe (BlazeFace, FaceLandmarks, HandLandmarks, MoveNet), były trenowane na kwadratowych wycinkach i szybko tracą jakość, gdy proporcje wejścia są nieprawidłowe; w ich przypadku aplikacja musi podać im kwadratowy ROI – albo przechwytując w kwadratowym rozmiarze ramki poprzez window(), albo kadrując parametrem roi=. Detektory obiektów z rodziny YOLO są zwykle trenowane z augmentacją obejmującą losowe rozciągnięcia i akceptują nie-kwadratowe ROI bez większej utraty dokładności; przekazanie całej przechwyconej ramki wprost jest zwykle w porządku.
Gdy wymiary wejściowe sieci dokładnie odpowiadają ROI, skalowanie sprowadza się do kopiowania, co jest najtańszym przypadkiem.
7.7.4. Nadpisywanie wartości domyślnej¶
predict() automatycznie opakowuje każde wejście image.Image w Normalization() – z powyższymi parametrami domyślnymi. Większość modeli dostarczanych z kamerą była trenowana na zakresach pikseli, które wartości domyślne już pokrywają, więc powszechnym przypadkiem jest przekazanie obrazu bezpośrednio:
result = model.predict([img])
Aby użyć własnego ROI – najczęstszego nadpisania – zbuduj Normalization z ustawionym ROI i zwiąż z nim obraz:
from ml.preprocessing import Normalization
norm = Normalization(roi=(80, 60, 160, 120))
result = model.predict([norm(img)])
Aby dopasować się do statystyk kanałów z czasu treningu sieci, ustaw parametry zmiennoprzecinkowe:
norm = Normalization(scale=(0.0, 1.0),
mean=(0.485, 0.456, 0.406),
stdev=(0.229, 0.224, 0.225))
result = model.predict([norm(img)])
Wywołanie instancji Normalization na obrazie zwraca nową związaną instancję, z której silnik wypełnia tensor. Związana instancja jest tym, co predict przyjmuje zamiast surowego obrazu, a ponieważ jest to obiekt na każde wejście, sieć wielowejściowa może mieszać obrazy o różnych ROI na tej samej liście predict.
Dla sieci oczekujących wejść, które aplikacja wytworzyła już w postaci tensora – bufor z urządzenia peryferyjnego, ndarray obliczony przez inny potok, nieobrazowe dane liczbowe – pomiń Normalization całkowicie i przekaż ndarray lub obiekt wywoływalny, który go produkuje. predict() przepuszcza je do silnika bez opakowywania.