Asosiy qismga o'tish

Detect boshi: 6 300 katakdan 5 ta box gacha

5-bob 13 kod bloki 4112 so'z

Bu bobning kodi: kod/05_detect_bosh.py — kitob papkasidan ishga tushiring: python kod/05_detect_bosh.py

Bu bobda nima qilamiz

model.predict() bitta qator. Uning ichida esa beshta bosqich bor, va bu bobda hammasini qo'lda qaytadan yozamiz:

  1. Letterbox — rasmni tarmoqqa mos o'lchamga keltirish.
  2. DFL — har katak chegaragacha masofani taqsimot sifatida bashorat qiladi.
  3. Dekodlash — masofalardan piksel koordinatalarga.
  4. NMS — bitta obyektga chizilgan ko'p box dan bittasini qoldirish.
  5. Asl rasmga qaytarish — letterbox koordinatalaridan 810 × 1080 ga.

Oxirida natijani predict() bilan solishtiramiz: bir xil chiqishi kerak. Keyin YOLO26 NMS siz qanday ishlashini ko'ramiz.

Letterbox va xom chiqish

Tarmoq kirishi 32 ga karrali bo'lishi kerak (beshta qadam-2 li qatlam, 3-bob). predict() rasmni cho'zmaydi: nisbatni saqlab uzun tomonini 640 ga keltiradi, qisqa tomonini esa eng yaqin 32 ga karrali songacha kulrang (114) bilan to'ldiradi:

kod/05_detect_bosh.pyyangi fayl · 1-qator: from kitob_yordam import fig, show
python27 qator
import cv2
import numpy as np
import pandas as pd
import torch
import torchvision
import matplotlib.pyplot as plt
from ultralytics import YOLO
from ultralytics.utils import ASSETS, LOGGER
from ultralytics.data.augment import LetterBox

LOGGER.setLevel("ERROR")

asl = cv2.imread(str(ASSETS / "bus.jpg"))                        # (1080, 810, 3), BGR
h0, w0 = asl.shape[:2]
lb = LetterBox((640, 640), auto=True, stride=32)(image=asl)      # predict() ishlatadigan aynan shu klass
gain = min(640 / h0, 640 / w0)
print("asl:", (h0, w0), "| letterbox:", lb.shape[:2], "| kichraytirish koeffitsiyenti:", round(gain, 4))
print("810 x gain =", w0 * gain, "-> 32 ga karrali, to'ldirish kerak emas")

x = torch.from_numpy(lb[:, :, ::-1].copy()).permute(2, 0, 1)[None].float() / 255   # BGR -> RGB, NCHW, 0..1
tarmoq = YOLO("weights/yolo11n.pt").model.eval()                 # fuse qilinmagan nusxa: boshning ichi ko'rinadi
with torch.no_grad():
    y, ichki = tarmoq(x)                                         # y: tayyor chiqish, ichki: boshning xom qismlari
print("\ny (dekodlangan):", tuple(y.shape))
print("boxes (xom):    ", tuple(ichki["boxes"].shape))
print("scores (xom):   ", tuple(ichki["scores"].shape))
print("xaritalar:      ", [tuple(f.shape) for f in ichki["feats"]])
Chiqish
asl: (1080, 810) | letterbox: (640, 480) | kichraytirish koeffitsiyenti: 0.5926
810 x gain = 480.0 -> 32 ga karrali, to'ldirish kerak emas

y (dekodlangan): (1, 84, 6300)
boxes (xom):     (1, 64, 6300)
scores (xom):    (1, 80, 6300)
xaritalar:       [(1, 64, 80, 60), (1, 128, 40, 30), (1, 256, 20, 15)]

Bu rasm uchun 640 × 640 emas, 640 × 480 kirish yetarli bo'ldi. Kataklar ham kamaydi: 80 × 60 + 40 × 30 + 20 × 15 = 6 300 (2-bobdagi 8 400 kvadrat kirish uchun edi). Kamroq katak — tezroq ishlash.

Har katak uchun Detect boshi ikki narsa chiqaradi: boxes da 64 ta raqam (4 tomon × 16) va scores da 80 ta raqam (har sinf uchun bittadan). Ular ikki alohida tarmoqdan (branch) keladi:

kod/05_detect_bosh.pydavomi
python4 qator
bosh = tarmoq.model[-1]
for nom, tarmoqcha in [("cv2 (box)", bosh.cv2[0]), ("cv3 (sinf)", bosh.cv3[0])]:
    print(f"{nom:11s}: {sum(p.numel() for p in tarmoqcha.parameters()):6,} parametr, oxirgi qatlam: {tarmoqcha[-1]}")
print("reg_max:", bosh.reg_max, "| qadamlar:", bosh.stride.tolist())
Chiqish
cv2 (box)  : 78,144 parametr, oxirgi qatlam: Conv2d(64, 64, kernel_size=(1, 1), stride=(1, 1))
cv3 (sinf) : 19,904 parametr, oxirgi qatlam: Conv2d(80, 80, kernel_size=(1, 1), stride=(1, 1))
reg_max: 16 | qadamlar: [8.0, 16.0, 32.0]

Anchor nuqtalar: har katakning markazi

Har bir katakning "o'z nuqtasi" bor — katak markazi. U katak birliklarida (x + 0.5, y + 0.5), pikselda esa qadamga ko'paytiriladi:

kod/05_detect_bosh.pydavomi
python11 qator
anchorlar, qadamlar = [], []
for f, s in zip(ichki["feats"], bosh.stride.tolist()):
    H, W = f.shape[2:]
    yy, xx = torch.meshgrid(torch.arange(H) + 0.5, torch.arange(W) + 0.5, indexing="ij")
    anchorlar.append(torch.stack([xx.flatten(), yy.flatten()]))    # (2, H*W), katak birliklarida
    qadamlar.append(torch.full((H * W,), s))
A = torch.cat(anchorlar, 1)                                      # (2, 6300)
S = torch.cat(qadamlar)                                          # (6300,)
print("anchorlar:", tuple(A.shape), "| birinchi uchtasi (katak):", A[:, :3].T.tolist())
print("pikselda:", (A[:, :3] * S[:3]).T.tolist())
print("P3 / P4 / P5 oralig'i:", "0–4799 / 4800–5999 / 6000–6299")
Chiqish
anchorlar: (2, 6300) | birinchi uchtasi (katak): [[0.5, 0.5], [1.5, 0.5], [2.5, 0.5]]
pikselda: [[4.0, 4.0], [12.0, 4.0], [20.0, 4.0]]
P3 / P4 / P5 oralig'i: 0–4799 / 4800–5999 / 6000–6299

DFL: chegara — bu taqsimot

Oddiy regressiya "chap chegaragacha 6.4 katak" deb bitta son chiqarardi. YOLO11 boshqacha ishlaydi: har tomon uchun 16 ta ehtimol chiqaradi — "masofa 0 katak", "1 katak", ..., "15 katak". Keyin softmax va kutilgan qiymat (o'rtacha):

Bu DFL (Distribution Focal Loss) g'oyasi: model faqat javob emas, o'z ishonchsizligini ham ko'rsatadi. Keng taqsimot — "chegara aniq qayerda, bilmayman". Ultralytics da kutilgan qiymat qanday hisoblanishiga qarang: vaznlari 0, 1, ..., 15 bo'lgan va o'qitilmaydigan 1 × 1 konvolyutsiya.

kod/05_detect_bosh.pydavomi
python7 qator
print("DFL konvolyutsiyasi vaznlari:", bosh.dfl.conv.weight.flatten().tolist())
print("o'qitiladimi:", bosh.dfl.conv.weight.requires_grad)

z = ichki["boxes"][0].view(4, 16, -1)                            # (4 tomon, 16 bin, 6300 katak)
p = z.softmax(1)                                                 # har tomon bo'yicha ehtimollar
d = (p * torch.arange(16).view(1, 16, 1)).sum(1)                 # kutilgan qiymat: (4, 6300), katak birliklarida
print("\nqo'lda d vs bosh.dfl, eng katta farq:", float((d - bosh.dfl(ichki["boxes"])[0]).abs().max()))
Chiqish
DFL konvolyutsiyasi vaznlari: [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0]
o'qitiladimi: False

qo'lda d vs bosh.dfl, eng katta farq: 1.9073486328125e-06

Muhim cheklov: masofa 0 dan 15 gacha. Demak katak markazidan chegaragacha eng ko'pi bilan 15 katak, pikselda esa har to'rda har xil:

kod/05_detect_bosh.pydavomi
python2 qator
for nom, s in zip(["P3", "P4", "P5"], bosh.stride.tolist()):
    print(f"{nom}: qadam {s:4.0f} px -> markazdan chegaragacha ko'pi bilan 15 x {s:.0f} = {15 * s:5.0f} px, box ko'pi bilan ~{30 * s:.0f} px")
Chiqish
P3: qadam    8 px -> markazdan chegaragacha ko'pi bilan 15 x 8 =   120 px, box ko'pi bilan ~240 px
P4: qadam   16 px -> markazdan chegaragacha ko'pi bilan 15 x 16 =   240 px, box ko'pi bilan ~480 px
P5: qadam   32 px -> markazdan chegaragacha ko'pi bilan 15 x 32 =   480 px, box ko'pi bilan ~960 px

P3 katagi 240 pikseldan katta box ni umuman chiza olmaydi. Katta obyekt faqat P5 dan chiqishi mumkin. Arxitektura (3-4-boblar) va bosh bir-biriga shunday bog'langan.

Masofadan box ga

Katak markazi (a_x, a_y) va to'rtta masofa (chap l, yuqori t, o'ng r, past b) ma'lum bo'lsa, box:

Sinf ehtimoli — sigmoid. Softmax emas: har sinf mustaqil, "odam" ehtimoli "velosiped" ehtimoliga bog'liq emas.

Hammasini yig'ib, tarmoqning o'z chiqishi y bilan solishtiramiz:

kod/05_detect_bosh.pydavomi
python7 qator
x1, y1 = (A[0] - d[0]) * S, (A[1] - d[1]) * S
x2, y2 = (A[0] + d[2]) * S, (A[1] + d[3]) * S
xyxy = torch.stack([x1, y1, x2, y2], 1)                          # (6300, 4), letterbox pikselida
ehtimol = ichki["scores"][0].sigmoid()                           # (80, 6300)
bizniki = torch.cat([torch.stack([(x1 + x2) / 2, (y1 + y2) / 2, x2 - x1, y2 - y1]), ehtimol])
print("bizniki:", tuple(bizniki.shape), "| tarmoqniki:", tuple(y[0].shape))
print("eng katta farq:", float((bizniki - y[0]).abs().max()))
Chiqish
bizniki: (84, 6300) | tarmoqniki: (84, 6300)
eng katta farq: 6.103515625e-05

6 300 dan 47 gacha: ishonch chegarasi

Har katak uchun eng ehtimolli sinf va uning ehtimoli. 0.25 dan pastlari tashlanadi:

kod/05_detect_bosh.pydavomi
python5 qator
ishonch, sinf = ehtimol.max(0)                                   # har katak: eng yuqori ehtimol va uning sinfi
nomzod = torch.nonzero(ishonch > 0.25).flatten()                 # katak raqamlari
print("0.25 dan yuqori kataklar:", len(nomzod), "ta, 6 300 dan")
print("to'rlar bo'yicha: P3", int((nomzod < 4800).sum()), "| P4", int(((nomzod >= 4800) & (nomzod < 6000)).sum()),
      "| P5", int((nomzod >= 6000).sum()))
Chiqish
0.25 dan yuqori kataklar: 47 ta, 6 300 dan
to'rlar bo'yicha: P3 0 | P4 4 | P5 43

Rasmda odamlar katta (letterbox da ~270 px bo'yli), shuning uchun deyarli hamma nomzod P5 dan. 47 ta nomzod bor, obyekt esa 5 ta: har bir obyektni bir nechta qo'shni katak birdaniga "topgan".

NMS: takrorlarni o'chirish

Non-Maximum Suppression algoritmi oddiy:

  1. Box larni ishonch bo'yicha kamayish tartibida saralash.
  2. Eng ishonchlisini olib qoldirish.
  3. Qolganlar ichidan u bilan IoU si chegaradan (standart 0.7) katta bo'lganlarini o'chirish — ular o'sha obyektning takrori.
  4. Ro'yxat bo'shaguncha 2-qadamga qaytish.

Bitta nozik joy: turli sinfdagi box lar bir-birini o'chirmasligi kerak (odam avtobus oldida turibdi, ularning box lari ustma-ust). Ultralytics buni hiyla bilan qiladi: har box ni sinf × 7680 pikselga siljitadi. Turli sinflar hech qachon ustma-ust tushmaydi, bitta sinf ichida esa hech narsa o'zgarmaydi.

kod/05_detect_bosh.pydavomi
python25 qator
def iou_bitta(box, boxlar):
    """Bitta box va boxlar ro'yxati orasidagi IoU (1-bobdagi formula, torch da)."""
    ix1 = torch.maximum(box[0], boxlar[:, 0]); iy1 = torch.maximum(box[1], boxlar[:, 1])
    ix2 = torch.minimum(box[2], boxlar[:, 2]); iy2 = torch.minimum(box[3], boxlar[:, 3])
    kesishma = (ix2 - ix1).clamp(0) * (iy2 - iy1).clamp(0)
    yuza = lambda b: (b[..., 2] - b[..., 0]) * (b[..., 3] - b[..., 1])
    return kesishma / (yuza(box) + yuza(boxlar) - kesishma)

def nms(boxlar, ballar, chegara):
    """Ochko'z NMS: qoldirilgan box larning indekslari, ishonch bo'yicha tartibda."""
    tartib = ballar.argsort(descending=True)
    qoldi = []
    while tartib.numel() > 0:
        eng_yaxshi = tartib[0]
        qoldi.append(int(eng_yaxshi))
        qolgan = tartib[1:]
        tartib = qolgan[iou_bitta(boxlar[eng_yaxshi], boxlar[qolgan]) <= chegara]   # takrorlarni o'chiramiz
    return torch.tensor(qoldi)

nb, ns, nc = xyxy[nomzod], ishonch[nomzod], sinf[nomzod]
siljigan = nb + nc[:, None].float() * 7680                       # sinflarni bir-biridan uzoqlashtirish
bizniki = nms(siljigan, ns, 0.7)
torchniki = torchvision.ops.nms(siljigan, ns, 0.7)
print("bizning NMS:     ", bizniki.tolist())
print("torchvision NMS: ", torchniki.tolist())
Chiqish
bizning NMS:      [11, 46, 38, 32, 43]
torchvision NMS:  [11, 46, 38, 32, 43]

IoU chegarasini o'zgartirib ko'ramiz:

kod/05_detect_bosh.pydavomi
python2 qator
qatorlar = [{"IoU chegarasi": t, "qolgan box lar": len(nms(siljigan, ns, t))} for t in (0.3, 0.5, 0.7, 0.9, 1.0)]
show(pd.DataFrame(qatorlar))
IoU chegarasiqolgan box lar
0.35
0.55
0.75
0.96
147

Past chegara takrorlarni ishonchli o'chiradi, lekin olomonda xavfli: ikki odam bir-birini yarim to'sib turgan bo'lsa, ularning box lari IoU si katta bo'ladi va haqiqiy ikkinchi odam "takror" deb o'chiriladi. Baland chegara olomonga yaxshi, lekin takrorlar qoladi (0.9 da allaqachon bitta takror paydo bo'ldi). 1.0 da NMS hech narsani o'chirmaydi — 47 ta nomzodning hammasi qoladi.

Asl rasmga qaytarish va predict() bilan solishtirish

Letterbox koordinatalaridan asl rasmga: to'ldirishni ayirib, koeffitsiyentga bo'lamiz:

kod/05_detect_bosh.pydavomi
python11 qator
px, py = (lb.shape[1] - w0 * gain) / 2, (lb.shape[0] - h0 * gain) / 2   # chap va yuqoridagi to'ldirish
yakuniy = (nb[bizniki] - torch.tensor([px, py, px, py])) / gain
yakuniy[:, [0, 2]] = yakuniy[:, [0, 2]].clamp(0, w0)             # rasm chegarasidan chiqmasin
yakuniy[:, [1, 3]] = yakuniy[:, [1, 3]].clamp(0, h0)

natija = YOLO("weights/yolo11n.pt").predict(ASSETS / "bus.jpg", device="cpu", verbose=False)[0]
print("bizniki:  ", [f"{natija.names[int(c)]} {s:.3f}" for c, s in zip(nc[bizniki], ns[bizniki])])
print("predict():", [f"{natija.names[int(c)]} {s:.3f}" for c, s in zip(natija.boxes.cls, natija.boxes.conf)])
print("box lar orasidagi eng katta farq, px:", float((yakuniy - natija.boxes.xyxy).abs().max()))
toplar = ["P3" if i < 4800 else "P4" if i < 6000 else "P5" for i in nomzod[bizniki].tolist()]
print("har yakuniy box qaysi to'rdan:", toplar)
Chiqish
bizniki:   ['bus 0.940', 'person 0.888', 'person 0.878', 'person 0.856', 'person 0.622']
predict(): ['bus 0.940', 'person 0.888', 'person 0.878', 'person 0.856', 'person 0.622']
box lar orasidagi eng katta farq, px: 0.0001220703125
har yakuniy box qaysi to'rdan: ['P5', 'P5', 'P5', 'P5', 'P5']

Beshta bosqichni qo'lda yozdik va predict() natijasini piksel darajasida takrorladik. Endi rasmda: NMS dan oldin va keyin.

kod/05_detect_bosh.pydavomi
python11 qator
def asl_rasmga(b):
    return ((b - torch.tensor([px, py, px, py])) / gain).tolist()

_, axes = plt.subplots(1, 2, figsize=(10, 6.8))
for ax, (sarlavha, boxlar) in zip(axes, [(f"NMS dan oldin: {len(nomzod)} ta", nb), (f"NMS dan keyin: {len(bizniki)} ta", nb[bizniki])]):
    ax.imshow(asl[:, :, ::-1])
    for bx1, by1, bx2, by2 in asl_rasmga(boxlar):
        ax.add_patch(plt.Rectangle((bx1, by1), bx2 - bx1, by2 - by1, fill=False, color="yellow", lw=1.2))
    ax.set_title(sarlavha)
    ax.axis("off")
fig("nms_oldin_keyin.png", "Chapda 0.25 dan yuqori hamma nomzod: har obyekt atrofida bir nechta box. O'ngda NMS qoldirgan 5 tasi.")

Chapda 0.25 dan yuqori hamma nomzod: har obyekt atrofida bir nechta box. O'ngda NMS qoldirgan 5 tasi.

Chapda 0.25 dan yuqori hamma nomzod: har obyekt atrofida bir nechta box. O'ngda NMS qoldirgan 5 tasi.

DFL taqsimotlari: model qanchalik ishonadi

Yakuniy ikki box ning to'rt tomoni uchun 16 lik taqsimotlarni chizamiz: chap chetdagi yarim ko'ringan odam (ishonchi 0.62) va eng aniq odam (0.89):

kod/05_detect_bosh.pydavomi
python17 qator
tanlangan = {"aniq odam (0.89)": int(nomzod[bizniki][1]), "yarim ko'ringan odam (0.62)": int(nomzod[bizniki][4])}
tomonlar = ["chap (l)", "yuqori (t)", "o'ng (r)", "past (b)"]
_, axes = plt.subplots(2, 4, figsize=(11, 4.6), sharey=True)
qatorlar = []
for qator, (nom, k) in zip(axes, tanlangan.items()):
    for j, ax in enumerate(qator):
        pj = p[j, :, k].numpy()
        ax.bar(range(16), pj, color="#2563eb")
        ax.set_title(f"{tomonlar[j]}", fontsize=9)
        ax.set_xticks([0, 5, 10, 15])
        orta = float((pj * np.arange(16)).sum())
        qatorlar.append({"box": nom, "tomon": tomonlar[j], "kutilgan d, katak": round(orta, 2),
                         "standart og'ish": round(float(np.sqrt((pj * (np.arange(16) - orta) ** 2).sum())), 2),
                         "eng katta p": round(float(pj.max()), 2)})
    qator[0].set_ylabel(nom, fontsize=9)
fig("dfl.png", "Har tomon uchun 16 ta ehtimol. Tor cho'qqi — model chegara joyiga ishonadi; keng — ishonmaydi.")
show(pd.DataFrame(qatorlar), 8)
boxtomonkutilgan d, katakstandart og'isheng katta p
aniq odam (0.89)chap (l)2.070.290.91
aniq odam (0.89)yuqori (t)7.190.550.74
aniq odam (0.89)o'ng (r)0.50.50.5
aniq odam (0.89)past (b)1.770.470.76
yarim ko'ringan odam (0.62)chap (l)0.50.510.5
yarim ko'ringan odam (0.62)yuqori (t)4.20.720.71
yarim ko'ringan odam (0.62)o'ng (r)0.780.480.75
yarim ko'ringan odam (0.62)past (b)1.650.620.6

Har tomon uchun 16 ta ehtimol. Tor cho'qqi — model chegara joyiga ishonadi; keng — ishonmaydi.

Har tomon uchun 16 ta ehtimol. Tor cho'qqi — model chegara joyiga ishonadi; keng — ishonmaydi.

Ehtimol ko'pincha ikkita qo'shni bin ga bo'linadi. Aniq odamning o'ng tomonida 0 va 1 ga teng yarimdan — kutilgan qiymat aynan 0.5 katak. DFL butun sonlar orasidagi masofani shunday ifodalaydi: 16 ta bin bilan 0 dan 15 gacha istalgan kasr son. Yarim ko'ringan odamda yuqori va past tomonlarning standart og'ishi aniq odamnikidan kattaroq (0.72 va 0.62, 0.55 va 0.47 ga qarshi): model uning bo'yi qayerda boshlanib, qayerda tugashiga kamroq ishonadi.

YOLO26: NMS siz bosh

YOLO26 ning Detect boshida ikki juft tarmoqcha bor: one2many (YOLO11 dagi kabi, bitta obyektga ko'p katak javob beradi) va one2one (bitta obyektga bitta katak). O'qitishda ikkalasi ham o'rganadi, ishlatishda esa faqat one2one ishlaydi. reg_max = 1, ya'ni DFL yo'q: har tomon uchun 16 ta ehtimol emas, bitta son.

kod/05_detect_bosh.pydavomi
python18 qator
tarmoq26 = YOLO("weights/yolo26n.pt").model.eval()
with torch.no_grad():
    y26, ichki26 = tarmoq26(x)                                   # o'sha letterbox rasm
print("tayyor chiqish:", tuple(y26.shape), "-> 300 ta qator: x1, y1, x2, y2, ishonch, sinf")
qatorlar = []
for nom in ("one2many", "one2one"):
    eh = ichki26[nom]["scores"][0].sigmoid().max(0).values
    qatorlar.append({"bosh": nom, "box kanallari": ichki26[nom]["boxes"].shape[1],
                     "ishonch > 0.25": int((eh > 0.25).sum()), "> 0.1": int((eh > 0.1).sum()), "> 0.05": int((eh > 0.05).sum())})
show(pd.DataFrame(qatorlar))

top = y26[0][y26[0][:, 4] > 0.05]
print("one2one chiqishi, ishonch > 0.05:")
for r in top.tolist():
    print(f"  {tarmoq26.names[int(r[5])]:7s} {r[4]:.2f}   ({r[0]:6.1f}, {r[1]:6.1f}) -> ({r[2]:6.1f}, {r[3]:6.1f})")
kuchli = top[top[:, 4] > 0.25]
print("0.25 dan yuqorilarga NMS qo'llasak, nechtasi qoladi:",
      len(torchvision.ops.nms(kuchli[:, :4] + kuchli[:, 5:6] * 7680, kuchli[:, 4], 0.7)), "/", len(kuchli))
Chiqish
tayyor chiqish: (1, 300, 6) -> 300 ta qator: x1, y1, x2, y2, ishonch, sinf
one2one chiqishi, ishonch > 0.05:
  bus     0.92   (  -1.4,  135.9) -> ( 477.7,  448.6)
  person  0.91   ( 131.8,  240.0) -> ( 204.7,  510.6)
  person  0.91   (  28.3,  236.6) -> ( 141.8,  535.1)
  person  0.87   ( 397.4,  232.0) -> ( 479.9,  520.9)
  person  0.53   (   0.1,  329.9) -> (  35.6,  515.6)
  person  0.15   (   0.2,  328.3) -> (  38.2,  516.8)
  stop sign 0.12   (  -0.2,  150.2) -> (  20.4,  192.8)
  stop sign 0.07   (   0.0,  149.2) -> (  21.3,  193.0)
  handbag 0.06   (  25.7,  435.0) -> (  46.2,  480.9)
  handbag 0.06   ( 134.2,  377.5) -> ( 173.2,  417.1)
0.25 dan yuqorilarga NMS qo'llasak, nechtasi qoladi: 5 / 5
boshbox kanallariishonch > 0.25> 0.1> 0.05
one2many4515771
one2one45710

one2many boshda 0.25 dan yuqori kataklar o'nlab, one2one boshda esa obyektlar soniga teng. NMS qo'llasak ham hech narsa o'chirilmaydi — takrorlar yo'q. Lekin diqqat bilan qarang: one2one ham chap chetdagi yarim ko'ringan odamga ikkinchi box chiqargan, faqat past ishonch bilan. Chegara 0.25 bo'lsa, u o'tmaydi. Bosh takrorni butunlay yo'qotishni emas, unga past ball berishni o'rgangan.

Bu qanday o'rgatiladi? O'qitishda har bir haqiqiy obyekt one2many boshga bir nechta katak bilan, one2one boshga esa aynan bitta katak bilan bog'lanadi. Birinchisi boy gradient beradi, ikkinchisi "faqat bir marta javob ber" ni o'rgatadi (YOLOv10 dagi "consistent dual assignments"). Bog'lash qoidalari va loss funksiyalari — 6-bobda. Narxini 2-bobda o'lchagan edik: NMS siz rejim COCO da 0.7–0.8 mAP past, lekin postprocess deyarli uch barobar qisqa.

Asosiy fikr

  • Detect boshi har katak uchun 4 × 16 masofa taqsimoti (DFL) va 80 ta sigmoid ehtimol chiqaradi. Kutilgan qiymat d = Σ i · pᵢ, keyin (a ∓ d) · s — piksel koordinata.
  • DFL da masofa 15 katakdan oshmaydi: P3 240 pikseldan katta box chiza olmaydi. Katta obyektlar P5 dan keladi.
  • NMS takrorlarni IoU bo'yicha o'chiradi. Chegara past — olomonda haqiqiy odamlar yo'qoladi, baland — takrorlar qoladi. YOLO26 ning one2one boshi takrorlarga o'zi past ball beradi va NMS ga ehtiyoj qolmaydi.

Mashqlar

  1. zidane.jpg uchun letterbox o'lchamini oldindan qo'lda hisoblang (rasm 1280 × 720), keyin kod bilan tekshiring. Qancha to'ldirish qo'shildi va kataklar soni qancha?
  2. nms() funksiyasini sinflarni siljitmasdan (7680 hiylasisiz) ishga tushiring. bus.jpg da natija o'zgaradimi? Qanday rasmda albatta o'zgaradi?
  3. Kataklarning 0.25 dan yuqori nomzodlarini to'rlar bo'yicha zidane.jpg uchun sanang. Odamlar kattami yoki kichikmi — P3, P4, P5 taqsimoti bunga mos keladimi?

Bu bobni bepul o'qidingiz.

Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.

Telegramda yozish Narxlar