7.14. YOLOv8 adım adım kılavuzu

YOLOv8 son işlemcisi, giriş tensöründen döndürülen listeye kadar adım adım gidilecek kadar küçüktür. Onu bir kez okumak, katalogdaki diğer her son işlemcinin ne yaptığını gösterir: kuantize edilmiş skorları ucuza eşikle, hayatta kalanı al, kuantizasyondan çıkar, geometriyi kod çöz, NMS’ye ilet, sınıf başına listeleri döndür.

7.14.1. Başlangıç tensörü

Bir YOLOv8 modeli, şekli (1, C, A) olan tek bir çıkış tensörü yayar – bir çerçeve, C kanal, A bağlantı tahmini. İlk dört kanal kutu geometrisidir – cx, cy, w, h – ağın giriş boyutlarının [0, 1] aralığına normalleştirilmiş. Kalan C - 4 kanal sınıf başına skorlardır, eğitilmiş bir model için zaten [0, 1] aralığındadır. Her bağlantı, kanallar boyunca aşağı inen bir sütundur.

Yan yatırılmış bir ızgara: satırlar cx, cy, w, h, score_0, score_1, ..., score_(N-1) olarak etiketlenmiş; sütunlar bağlantı 0'dan bağlantı A-1'e kadar etiketlenmiş. Tek bir bağlantının tahmininin kanallar boyunca aşağı inen tek bir sütun olduğunu göstermek için bir sütun çerçeve içine alınmış.

Bir YOLOv8 bağlantısı, kanallar boyunca aşağı inen tek bir sütundur: dört kutu sayısı ve N sınıf skoru.

Sevk edilen yolov8n_192.tflite tek sınıflı bir kişi tespitçisidir, dolayısıyla C = 5 ve A binlercedir; tam 80 sınıflı COCO kümesi üzerinde eğitilmiş özel bir modelde aynı A karşısında C = 84 olur. Aşağıdaki kod çözme herhangi bir sınıf sayısı için geçerlidir.

7.14.2. Kuantizasyondan çıkarmadan önce eşikleme

Önce ucuz adım. Çıkış tensörü modelin kuantize edilmiş tamsayı dtype’ındadır ve her değeri kuantizasyondan çıkarmak, çoğu skor eşiğinin altında olan ve sonunda atılan binlerce girdiye sahip bir tensörün her öğesine dokunur. Bunun yerine son işlemci eşiği bir kez kuantize eder ve ham kuantize uzayda karşılaştırır:

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 ()

column_outputs, kanallar satır ve bağlantılar sütun olacak şekilde (C, A) boyutuna yeniden şekillendirilmiş tensördür. score_block, sınıf skoru alt tensörüdür – 4 satırından aşağı her şey. ml.utils.threshold() bu bloğu eksen 0 boyunca (find_max=True, find_max_axis=0) bağlantı başına maksimum skora indirger, ardından maksimumu kuantize edilmiş eşiği geçen bağlantıların indekslerini döndürür. Tensörün tamamı hiçbir zaman kuantizasyondan çıkarılmadı; yalnızca kuantize edilmiş tamsayı uzayda sütun başına bir maksimum indirgemesi yapıldı.

Hiçbir bağlantı geçemezse, son işlemci predict() öğesinin tespit yok olarak yorumladığı boş demeti döndürür.

Bu koddaki iki karar, çalıştırılabilir bir son işlemci ile kullanılamayacak kadar yavaş olanı arasındaki farkı yaratır. İlki, aritmetiği numpy aracılığıyla yürütmektir: çıkış tensörünün binlerce öğesi vardır ve onu ham Python’da yinelemek çıkarım başına saniyeler alırken, aynı aritmetik numpy aracılığıyla vektörleştirildiğinde milisaniyeler içinde çalışır. İkincisi, kuantizasyondan çıkarmayı eşik filtresinden önce değil sonra yapmaktır. Önce kuantizasyondan çıkarmak, kuantize edilmiş olanın dört katı boyutunda bir kayan noktalı tensör ayırır ve neredeyse hepsini atmadan önce her öğeyi dolaşır; yalnızca hayatta kalan sütunları kuantizasyondan çıkarmak en fazla bir avuç değere dokunur ve tam dönüşümün tüketeceği hem zamanı hem de RAM’i kazandırır.

7.14.3. Hayatta kalanları kuantizasyondan çıkarma

Yalnızca hayatta kalan bağlantıların geometrilerinin kod çözülmesi gerekir. numpy.take() bu sütunları çeker ve tek bir ml.utils.dequantize() çağrısı onları kayan noktalı sayılara dönüştürür:

bb = dequantize(model,
                np.take(column_outputs, score_indices, axis=1))

bb artık (C, K) boyutundadır; burada K hayatta kalan bağlantıların sayısıdır – A binlerce iken bile genellikle bir avuç.

7.14.4. Geometriyi okuma

Dört kutu kanalı ve sınıf başına skorlar doğrudan çekilir:

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

bb_scores, hayatta kalan bağlantı başına en iyi sınıf skorudur; bb_classes, o skoru getiren sınıf indeksidir. Kutu geometrisi hâlâ ağ girişi boyutlarının normalleştirilmiş [0, 1] aralığındadır, dolayısıyla sonraki adım onu piksellere ölçekler:

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

Bunun ardından kutular ağ girişi piksel uzayındadır – NMS öğesinin girişte beklediği koordinat uzayı.

7.14.5. Maksimum olmayanların bastırılması

Hayatta kalanlar NMS’den geçer ve sınıf başına listeler olarak döndürülür:

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)

NMS, inputs[0].roi değerini okur, böylece döndürülen kutular ağınkinde değil orijinal görüntünün koordinat uzayındadır – uygulama onları daha fazla yeniden eşleme yapmadan doğrudan yakalanan çerçeve üzerine çizer.

7.14.6. Betiğin geri aldığı şey

Döndürülen değer, sınıfa göre indekslenmiş, sınıf başına listelerden oluşan bir listedir. Üç sınıflı bir örnek şöyle görünebilir:

[
    [((23, 41, 95, 142), 0.92), ((180, 60, 88, 130), 0.71)],
    [],
    [((310, 95, 55, 70), 0.85)],
]

Her girdi bir ((x, y, w, h), score) demetidir: (x, y), sınırlayıcı kutunun orijinal görüntünün piksel koordinatlarındaki sol üst köşesidir, w ve h piksel cinsinden genişliği ve yüksekliğidir ve score, ağın tespite atadığı güvendir. Dolayısıyla ((180, 60, 88, 130), 0.71), sol üst köşesi (180, 60) pikselinde duran, sağa doğru 88 piksel ve aşağıya doğru 130 piksel uzanan ve 0.71 güveniyle bildirilen bir kutu olarak okunur.

Dış liste, 0 sınıfı için hayatta kalan iki kutuyu, 1 sınıfı için hiçbir şey, 2 sınıfı için bir tane gösterir. 1 sınıfı için boş liste, dış indeksin her zaman sınıf indeksiyle eşleşmesi için yerinde tutulur. Sevk edilen kişi tespitçisi için dış listede, iç listesi hayatta kalan kişi kutularını içeren tek bir öğe bulunur. 80 sınıflı bir model için 80 iç liste vardır, herhangi bir çerçevede çoğu boştur, boş olmayan girdiler tetiklenen sınıfların kutularını tutar. Uygulama, sınıf indekslerini kutu listelerinin yanında dolaşmak için sonucu enumerate(boxes) ile okur – katalog genelinde tespit son işlemcilerinin hedeflediği aynı yapı.