7.14. YOLOv8 áttekintés¶
A YOLOv8 utófeldolgozó elég kicsi ahhoz, hogy a bemeneti tenzortól a visszaadott listáig végiglépkedjünk rajta. Egyszeri elolvasása megmutatja, mit csinál a katalógus összes többi utófeldolgozója: olcsón küszöböli a kvantált pontszámokat, veszi a túlélőket, dekvantál, dekódolja a geometriát, az NMS-be tolja, és osztályonkénti listákat ad vissza.
7.14.1. A kiinduló tenzor¶
Egy YOLOv8 modell egyetlen kimeneti tenzort bocsát ki, amelynek alakja (1, C, A) – egy képkocka, C csatorna, A horgony-predikció. Az első négy csatorna a doboz geometriája – cx, cy, w, h –, a hálózat bemeneti dimenzióinak [0, 1] tartományára normalizálva. A maradék C - 4 csatorna osztályonkénti pontszám, egy betanított modellnél már [0, 1] tartományban. Minden horgony egy oszlop a csatornák mentén lefelé.
Egy YOLOv8 horgony egy oszlop a csatornák mentén lefelé: négy doboz-szám és N osztály-pontszám.¶
A szállított yolov8n_192.tflite egy egyosztályos személydetektor, így C = 5 és A ezres nagyságrendű; egy egyéni, a teljes 80-osztályos COCO halmazon betanított modellnél C = 84 ugyanazon A mellett. Az alábbi dekódolás bármely osztályszámra érvényes.
7.14.2. Küszöbölés a dekvantálás előtt¶
Az olcsó lépés először. A kimeneti tenzor a modell kvantált egész dtype-jában van, és minden érték dekvantálása egy több ezer bejegyzéssel rendelkező tenzor minden elemét érintené – amelyek többsége a pontszámküszöb alatt van, és végül eldobásra kerül. Az utófeldolgozó ehelyett egyszer kvantálja a küszöbértéket, és nyers kvantált térben hasonlít össze:
from ml.utils import quantize, threshold, dequantize, NMS
from ulab import numpy as np
oh, ow, oc = model.output_shape[0]
scale = model.output_scale[0]
t = quantize(model, self.threshold)
column_outputs = outputs[0].reshape((oh * ow, oc))
score_block = column_outputs[4:, :]
score_indices = threshold(score_block, t, scale,
find_max=True,
find_max_axis=0)
if not len(score_indices):
return ()
A column_outputs a tenzor (C, A) alakzatra átalakítva, így a csatornák a sorok, a horgonyok pedig az oszlopok. A score_block az osztály-pontszám altenzor – minden a 4 sortól lefelé. Az ml.utils.threshold() ezt a blokkot a 0 tengely mentén redukálja (find_max=True, find_max_axis=0) a horgonyonkénti maximális pontszámra, majd visszaadja azon horgonyok indexeit, amelyek maximuma átmegy a kvantált küszöbértéken. A teljes tenzort soha nem dekvantáltuk; csak egy oszloponkénti max-redukció történt kvantált egész térben.
Ha egyetlen horgony sem megy át, az utófeldolgozó visszaadja az üres rendezett ennest, amelyet a predict() nincs észlelésként értelmez.
Ebben a kódban két döntés teszi a különbséget egy futtatható utófeldolgozó és egy használhatatlanul lassú között. Az első az aritmetika numpy modulon keresztüli vezetése: a kimeneti tenzornak több ezer eleme van, és nyers Pythonban iterálva ez egész másodperceket vesz igénybe következtetésenként, míg ugyanez az aritmetika a numpy modulon keresztül vektorizálva ezredmásodpercek alatt fut. A második a küszöbszűrés után történő dekvantálás, nem előtte. Az előbb dekvantálás egy négyszer akkora lebegőpontos tenzort foglalna le, mint a kvantált, és minden elemet bejárna, mielőtt szinte mindet eldobja; csak a túlélő oszlopok dekvantálása legfeljebb egy maréknyi értéket érint, és megspórolja mind az időt, mind a RAM-ot, amelyet a teljes átalakítás felemésztett volna.
7.14.3. A túlélők dekvantálása¶
Csak a túlélő horgonyok geometriáját kell dekódolni. A numpy.take() kihúzza ezeket az oszlopokat, és egyetlen ml.utils.dequantize() hívás lebegőpontossá alakítja őket:
bb = dequantize(model,
np.take(column_outputs, score_indices, axis=1))
A bb most (C, K), ahol K a túlélő horgonyok száma – jellemzően egy maréknyi, még akkor is, ha A ezres nagyságrendű volt.
7.14.4. A geometria kiolvasása¶
A négy doboz-csatornát és az osztályonkénti pontszámokat közvetlenül kihúzzuk:
bb_scores = np.max(bb[4:, :], axis=0)
bb_classes = np.argmax(bb[4:, :], axis=0)
x_center = bb[0, :]
y_center = bb[1, :]
w_half = bb[2, :] * 0.5
h_half = bb[3, :] * 0.5
A bb_scores a túlélő horgonyonkénti legjobb osztály-pontszám; a bb_classes az az osztályindex, amely azt a pontszámot adta. A doboz geometriája még mindig a hálózati bemeneti dimenziók normalizált [0, 1] tartományában van, így a következő lépés képpontokra skálázza:
ib, ih, iw, ic = model.input_shape[0]
xmin = (x_center - w_half) * iw
ymin = (y_center - h_half) * ih
xmax = (x_center + w_half) * iw
ymax = (y_center + h_half) * ih
Ezután a dobozok a hálózati bemeneti képpont-térben vannak – abban a koordinátatérben, amelyet a NMS a bemeneten vár.
7.14.5. Non-max suppression¶
A túlélők átmennek az NMS-en, és osztályonkénti listákként térnek vissza:
nms = NMS(iw, ih, inputs[0].roi)
for i in range(bb.shape[1]):
nms.add_bounding_box(xmin[i], ymin[i],
xmax[i], ymax[i],
bb_scores[i], bb_classes[i])
return nms.get_bounding_boxes(threshold=self.nms_threshold,
sigma=self.nms_sigma)
A NMS az inputs[0].roi értéket olvassa, így a visszaadott dobozok az eredeti kép koordinátaterében vannak, nem a hálózatéban – az alkalmazás közvetlenül a rögzített képkockára rajzolja őket további visszaképezés nélkül.
7.14.6. Mit kap vissza a szkript¶
A visszatérési érték osztályonkénti listák listája, osztály szerint indexelve. Egy háromosztályos példa így nézhet ki:
[
[((23, 41, 95, 142), 0.92), ((180, 60, 88, 130), 0.71)],
[],
[((310, 95, 55, 70), 0.85)],
]
Minden bejegyzés egy ((x, y, w, h), score) rendezett ennes: az (x, y) a határoló doboz bal felső sarka az eredeti kép képpont-koordinátáiban, a w és a h a szélessége és magassága képpontban, a score pedig a megbízhatóság, amelyet a hálózat az észleléshez rendelt. Tehát a ((180, 60, 88, 130), 0.71) úgy olvasandó, mint egy doboz, amelynek bal felső sarka a (180, 60) képponton ül, 88 képpontot terjed jobbra és 130 képpontot lefelé, és 0.71 megbízhatósággal jelentették.
A külső lista két túlélő dobozt mutat a 0 osztályhoz, semmit az 1 osztályhoz, egyet a 2 osztályhoz. Az 1 osztály üres listája a helyén marad, hogy a külső index mindig megfeleljen az osztályindexnek. A szállított személydetektornál a külső listának egyetlen eleme van, amelynek belső listája a túlélő személy-dobozokat tartalmazza. Egy 80-osztályos modellnél 80 belső listája van, amelyek többsége bármely adott képkockán üres, a nem üres bejegyzések pedig az aktiválódott osztályok dobozait tartalmazzák. Az alkalmazás az enumerate(boxes) segítségével olvassa az eredményt, hogy az osztályindexeket a doboz-listákkal együtt járja be – ugyanaz az alakzat, amelyet az észlelő utófeldolgozók a katalógusban megcéloznak.