Asosiy qismga o'tish

Obyekt aniqlash: box, IoU va birinchi YOLO

1-bob 15 kod bloki 4692 so'z

Bu bobning kodi: kod/01_obyekt_aniqlash.py — kitob papkasidan ishga tushiring: python kod/01_obyekt_aniqlash.py

Kameradan bitta savol

Kichik do'kon. Egasi kassada o'tiradi, burchakda kamera osilgan. Bir kuni so'raydi: "Bugun do'konga nechta odam kirdi? Qaysi soatda eng ko'p? Kim necha minut turdi?"

Kamera buni bilmaydi. Kamera sekundiga 25 marta rasm oladi, xolos. Har bir rasm esa shunchaki raqamlar jadvali. "Odam", "kirdi", "necha minut" degan narsalarni shu raqamlardan chiqarib olish kerak. Bu kitob aynan shuni o'rgatadi, to'rt qismda:

Qism Savol Vosita
I. YOLO ichidan Har bir rasmda nima bor, qayerda, nechta? YOLO11, YOLO26, MediaPipe
II. Tracking Bu kadrdagi odam oldingi kadrdagi o'sha odammi? Qayerdan chiqib ketdi? Kalman, BoT-SORT, ReID, ArcFace
III. Loyiha Nechta kamera, qanday server, qanday dashboard? RTSP, Docker, PostgreSQL, Telegram
IV. Biznes Kimga, qancha narxda, qaysi litsenziya bilan sotiladi? Hisob-kitob, qonun, shartnoma

Birinchi qadam — bitta rasmda obyektni topish. Buni obyekt aniqlash (object detection) deyiladi.

Birinchi YOLO: 6 qator kod

Nazariyadan oldin natijani ko'raylik. Avval o'rnatish:

Terminal
bash3 qator
python -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install ultralytics          # torch, torchvision, opencv, numpy ham birga keladi

GPU bo'lsa, ultralytics dan oldin torch ni pytorch.org saytidagi CUDA buyrug'i bilan o'rnating, aks holda pip CPU versiyasini qo'yadi. RTX 50xx seriyasi uchun CUDA 12.8 li torch kerak.

Kitobdagi kod bilan qanday ishlash

Har bir kod bloki ustida uning fayli yozilgan, masalan kod/01_obyekt_aniqlash.py:

  • yangi fayl — shu blokdan yangi fayl boshlanadi;
  • davomi — o'sha faylning davomi: oldingi blokning ostiga qo'shib yoziladi;
  • Terminal — Python fayl emas, terminalda bajariladigan buyruq.

Har bir bobning kodi bitta faylda: 1-bob — kod/01_obyekt_aniqlash.py, 9-bob — kod/09_tracking_nima.py va hokazo. Bu fayllarni qo'lda yig'ish shart emas: ular kitob bilan birga kod/ papkasida tayyor turibdi va aynan kitobdagi bloklardan yig'ilgan. Datasetlar va modellarni yuklab olish bir joyda — kod/yuklab_olish.py.

Ikki qoida:

  1. Fayllarni kitob papkasidan ishga tushiring: python kod/01_obyekt_aniqlash.py. Kodda weights/, datasets/, out/ yo'llari shu papkaga nisbatan yozilgan.
  2. Boblarni tartib bilan bajaring: keyingi boblar oldingilarining natijasidan foydalanadi. Masalan, 9-bobda saqlangan box lar 10–14-boblarda, 7-bobda o'qitilgan model 8- va 14-boblarda ishlatiladi.

Kitobda natijalar sahifaning o'zida ko'rinadi: rasm uchun fig("nom.png"), jadval uchun show(jadval). Sizning kompyuteringizda bu ikki funksiya kichik yordamchi fayldan keladi — rasmni out/rasmlar/ ga saqlaydi, jadvalni ekranga chiqaradi. Ularni ishlatadigan har bir faylning birinchi qatori from kitob_yordam import fig, show — bu fayl yorlig'ida ham yozilgan:

kod/kitob_yordam.pyyangi fayl
python21 qator
import sys
from pathlib import Path

import matplotlib.pyplot as plt

RASMLAR = Path("out/rasmlar")


def fig(nom, izoh=""):
    """Joriy matplotlib rasmini out/rasmlar/ ga saqlaydi. Kitobda xuddi shu rasm sahifaga qo'yilgan."""
    RASMLAR.mkdir(parents=True, exist_ok=True)
    yol = RASMLAR / f"{Path(sys.argv[0]).stem[:2]}_{nom}"          # bob raqami bilan: 09_mot09_yollar.png
    plt.tight_layout()
    plt.savefig(yol, bbox_inches="tight")
    plt.close("all")
    print(f"rasm saqlandi: {yol}" + (f" — {izoh}" if izoh else ""))


def show(jadval, n=15):
    """Jadvalning birinchi n qatorini chiqaradi. Kitobda xuddi shu jadval sahifada ko'rinadi."""
    print(jadval.head(n).to_string(), "\n")

Endi kod:

kod/01_obyekt_aniqlash.pyyangi fayl · 1-qator: from kitob_yordam import fig, show
python11 qator
from ultralytics import YOLO
from ultralytics.utils import ASSETS          # ultralytics bilan birga keladigan namuna rasmlar papkasi

model = YOLO("weights/yolo11n.pt")            # fayl yo'q bo'lsa GitHub dan o'zi yuklab oladi (5.6 MB)
natija = model.predict(ASSETS / "bus.jpg", verbose=False)[0]   # bitta rasm berdik -> ro'yxatda bitta natija

for box in natija.boxes:                      # har bir topilgan obyekt uchun bitta box
    nom = natija.names[int(box.cls)]          # sinf raqami -> nom (0 -> "person", 5 -> "bus")
    ishonch = float(box.conf)                 # model qanchalik ishonadi, 0..1
    x1, y1, x2, y2 = box.xyxy[0].tolist()     # chap-yuqori va o'ng-past burchak, pikselda
    print(f"{nom:7s} {ishonch:.2f}   ({x1:4.0f}, {y1:4.0f}) -> ({x2:4.0f}, {y2:4.0f})")
Chiqish
bus     0.94   (   4,  229) -> ( 796,  728)
person  0.89   ( 671,  395) -> ( 810,  879)
person  0.88   (  47,  400) -> ( 239,  904)
person  0.86   ( 223,  409) -> ( 344,  860)
person  0.62   (   0,  556) -> (  69,  872)
kod/01_obyekt_aniqlash.pydavomi
python4 qator
import cv2

rasm = natija.plot()                          # box va yozuvlar chizilgan rasm (numpy massiv, BGR tartibda)
cv2.imwrite("out/01_bus_yolo11n.jpg", rasm)   # diskka saqlaymiz

YOLO11n natijasi: 1 avtobus va 4 odam

YOLO11n natijasi: 1 avtobus va 4 odam

Model 5 ta obyekt topdi: 1 ta avtobus va 4 ta odam. Chap chetdagi to'rtinchi odamning ishonchi pastroq (0.62), chunki u kadrga yarim kirgan. Bu hodisani kitob davomida ko'p ko'ramiz: obyektning qancha qismi ko'rinsa, model shuncha ishonadi.

Endi qatorma-qator nima bo'ldi:

  • YOLO("weights/yolo11n.pt") — model faylini yuklaydi. Fayl bo'lmasa, ultralytics uni github.com/ultralytics/assets reliz sahifasidan yuklab oladi. Bu fayl — o'qitilgan og'irliklar (weights), taxminan 2.6 million raqam. Ular COCO datasetida o'qitilgan: 80 ta sinf (odam, mashina, it, stul...), 118 mingga yaqin rasm. Nomidagi n — nano, eng kichik versiya. s, m, l, x kattaroq va aniqroq, lekin sekinroq. Qaysi birini qachon tanlash kerakligini 8-bobda o'lchab ko'ramiz.
  • predict(...) — rasmni tarmoqdan o'tkazadi. Ichida uch bosqich bor: rasmni tayyorlash (o'lchamini 640 ga keltirish), tarmoqning o'zi va natijani tozalash. Har birini 4–5-boblarda ochamiz.
  • [0] — predict har doim ro'yxat qaytaradi, chunki unga bir nechta rasm yoki butun video berish mumkin. Biz bitta rasm berdik.
  • box.cls, box.conf, box.xyxy — har bir topilgan obyekt uchta narsa: sinf raqami, ishonch va 4 ta koordinata.

YOLO nomi "You Only Look Once" dan olingan: rasmga bir marta qaraydi. Undan oldingi usullar rasmdagi minglab bo'laklarni alohida-alohida tekshirardi. Bu tarix 2-bobda.

Bu kitobdagi natijalar haqida

Kitobdagi har bir "Chiqish" bloki kod haqiqatan ishga tushirilib olingan. Hech bir raqam qo'lda yozilmagan. Muhit:

kod/01_obyekt_aniqlash.pydavomi
python6 qator
import sys, torch, ultralytics

print("Python     ", sys.version.split()[0])
print("ultralytics", ultralytics.__version__)
print("torch      ", torch.__version__)
print("GPU        ", torch.cuda.get_device_name(0) if torch.cuda.is_available() else "yo'q (CPU)")
Chiqish
Python      3.10.12
ultralytics 8.4.17
torch       2.9.1+cu128
GPU         NVIDIA GeForce RTX 5070 Ti

Sizning kompyuteringizda ishonch qiymatlari oxirgi xonada farq qilishi mumkin (GPU va CPU hisobi biroz farq qiladi), lekin topilgan obyektlar bir xil bo'ladi.

Uch xil savol, uch xil vazifa

"Rasmda nima bor" degan savol bir necha xil bo'ladi. Ultralytics har biri uchun alohida model beradi:

Vazifa Savol Chiqish Model
Classification Rasmda nima bor? bitta sinf va ehtimol yolo11n-cls.pt
Detection Nima, qayerda, nechta? har obyektga box, sinf, ishonch yolo11n.pt
Segmentation Aynan qaysi piksellar? har obyektga niqob (mask) yolo11n-seg.pt
Pose Gavda qanday turibdi? har odamga 17 ta nuqta (burun, yelka, tizza...) yolo11n-pose.pt
OBB Qiyshiq turgan obyekt qayerda? burilgan box (samolyotdan olingan rasmlar) yolo11n-obb.pt

Odamlarni sanash va kuzatish uchun detection yetadi: box bo'lsa, uni kadrdan kadrga ulash mumkin. Pose va segmentation keyinroq kerak bo'ladi (yiqilishni aniqlash, aniq maydon hisoblash). Yuz, qo'l va gavda nuqtalari uchun Google ning MediaPipe kutubxonasi ham bor. Uni YOLO bilan keyingi boblarda solishtiramiz.

Rasm — bu raqamlar jadvali

Kompyuter uchun rasm — uch o'lchamli massiv:

kod/01_obyekt_aniqlash.pydavomi
python7 qator
import cv2

img = cv2.imread(str(ASSETS / "bus.jpg"))    # diskdan o'qish; natija numpy massiv
print("shape:", img.shape)                   # (balandlik, kenglik, kanallar)
print("dtype:", img.dtype)                   # har qiymat 0..255 oralig'idagi butun son
print("chap-yuqori piksel [B G R]:", img[0, 0])
print("x=300, y=500 dagi piksel:  ", img[500, 300])   # DIQQAT: avval y (qator), keyin x (ustun)
Chiqish
shape: (1080, 810, 3)
dtype: uint8
chap-yuqori piksel [B G R]: [119 146 172]
x=300, y=500 dagi piksel:   [151 140 232]

Ikki narsa ko'p adashtiradi:

  1. Tartib. shape da avval balandlik (1080), keyin kenglik (810). Box koordinatalarida esa avval x, keyin y. Shuning uchun (x=300, y=500) nuqtadagi piksel img[500, 300] bo'ladi, img[300, 500] emas.
  2. Ranglar. OpenCV ranglarni BGR tartibida o'qiydi (ko'k, yashil, qizil), matplotlib esa RGB kutadi. Tartibni almashtirmasangiz, qizil ko'ylak ko'k bo'lib chiqadi.

Koordinata boshi (0, 0) — chap-yuqori burchak. x o'ngga, y pastga qarab o'sadi. Matematika darsidagi grafikka teskari:

kod/01_obyekt_aniqlash.pydavomi
python20 qator
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle

x1, y1, x2, y2 = natija.boxes.xyxy[3].tolist()  # uchinchi odam (ishonchi 0.86)
yozuv = dict(color="yellow", fontsize=10, weight="bold", bbox=dict(boxstyle="round", fc="black", alpha=0.7))
strelka = dict(arrowstyle="->", color="red", lw=2.5)
_, ax = plt.subplots(figsize=(5, 6.4))
ax.imshow(img[:, :, ::-1])                    # BGR -> RGB: kanallarni teskari tartibda olamiz
ax.add_patch(Rectangle((x1, y1), x2 - x1, y2 - y1, fill=False, color="yellow", lw=2))
ax.plot([x1, x2], [y1, y2], "o", color="yellow")
ax.annotate(f"(x1, y1) = ({x1:.0f}, {y1:.0f})", (x1, y1), (x1 + 40, y1 - 100),
            arrowprops=dict(arrowstyle="->", color="yellow"), **yozuv)
ax.annotate(f"(x2, y2) = ({x2:.0f}, {y2:.0f})", (x2, y2), (x2 - 60, y2 + 110),
            arrowprops=dict(arrowstyle="->", color="yellow"), **yozuv)
ax.annotate("", (260, 30), (30, 30), arrowprops=strelka)      # x o'qi: o'ngga
ax.annotate("", (30, 230), (30, 30), arrowprops=strelka)      # y o'qi: pastga
ax.text(275, 45, "x", color="red", fontsize=14, weight="bold")
ax.text(45, 270, "y", color="red", fontsize=14, weight="bold")
ax.set_title("(0, 0) chap-yuqorida, y pastga o'sadi")
fig("koordinata.png", "Box ikki nuqta bilan beriladi: chap-yuqori (x1, y1) va o'ng-past (x2, y2).")

Box ikki nuqta bilan beriladi: chap-yuqori (x1, y1) va o'ng-past (x2, y2).

Box ikki nuqta bilan beriladi: chap-yuqori (x1, y1) va o'ng-past (x2, y2).

Box: to'rtta raqam, to'rt xil yozuv

Box har doim 4 ta raqam, lekin ularni turlicha yozishadi. Kutubxonalar orasida formatni adashtirish — bu sohada eng ko'p uchraydigan xato:

Format 4 ta raqam Qayerda uchraydi
xyxy x1, y1, x2, y2 chizish, IoU, NMS
xywh markaz x, markaz y, kenglik, balandlik YOLO ichida, trackerlar ichida (II qism)
xywhn xywh ni rasm o'lchamiga bo'lingan, 0..1 YOLO label fayllari (.txt)
tlwh chap-yuqori x, chap-yuqori y, kenglik, balandlik MOT17 dataseti, tracker natija fayllari

Formulalar sodda. xyxy dan markazga:

Normallash: x bilan bog'liq raqamlar rasm kengligi W ga, y bilanlari balandlik H ga bo'linadi:

O'zimiz yozib, ultralytics bilan solishtiramiz:

kod/01_obyekt_aniqlash.pydavomi
python25 qator
import numpy as np

def xyxy_to_xywh(b):
    """[x1, y1, x2, y2] -> [markaz_x, markaz_y, kenglik, balandlik]. b shakli (N, 4)."""
    x1, y1, x2, y2 = b[:, 0], b[:, 1], b[:, 2], b[:, 3]
    return np.stack([(x1 + x2) / 2, (y1 + y2) / 2, x2 - x1, y2 - y1], axis=1)

def xywh_to_xyxy(b):
    """Teskari yo'l: markazdan yarim kenglik chapga va o'ngga, yarim balandlik tepaga va pastga."""
    xc, yc, w, h = b[:, 0], b[:, 1], b[:, 2], b[:, 3]
    return np.stack([xc - w / 2, yc - h / 2, xc + w / 2, yc + h / 2], axis=1)

def xyxy_to_tlwh(b):
    """MOT formati: chap-yuqori burchak + kenglik, balandlik."""
    return np.stack([b[:, 0], b[:, 1], b[:, 2] - b[:, 0], b[:, 3] - b[:, 1]], axis=1)

H, W = natija.orig_shape                      # asl rasm o'lchami: (balandlik, kenglik)
xyxy = natija.boxes.xyxy.cpu().numpy()        # GPU dagi tensor -> oddiy numpy massiv
xywh = xyxy_to_xywh(xyxy)
xywhn = xywh / np.array([W, H, W, H])         # har ustun o'z o'lchamiga bo'linadi

print("xywh  ultralytics bilan farq:", np.abs(xywh - natija.boxes.xywh.cpu().numpy()).max())
print("xywhn ultralytics bilan farq:", np.abs(xywhn - natija.boxes.xywhn.cpu().numpy()).max())
print("xyxy -> xywh -> xyxy farq:   ", np.abs(xywh_to_xyxy(xywh) - xyxy).max())
print("tlwh (1-odam):", xyxy_to_tlwh(xyxy)[1].round(1))
Chiqish
xywh  ultralytics bilan farq: 0.0
xywhn ultralytics bilan farq: 6.387263173923685e-08
xyxy -> xywh -> xyxy farq:    6.1035156e-05
tlwh (1-odam): [        671       394.8       138.8       483.9]

Farqlar nolga juda yaqin: bu float32 sonlarining yaxlitlash qoldig'i, formulalar bir xil.

Normallashgan format nega kerak? Chunki u rasm o'lchamiga bog'liq emas. O'qitish paytida rasm 640 ga kichraytiriladi, lekin 0..1 dagi raqamlar o'zgarmaydi. YOLO label fayli aynan shunday ko'rinadi, har qatorda bitta obyekt:

kod/01_obyekt_aniqlash.pydavomi
python3 qator
# YOLO label formati: "sinf markaz_x markaz_y kenglik balandlik", hammasi 0..1 oralig'ida
for sinf, (xc, yc, w, h) in zip(natija.boxes.cls.int().tolist(), xywhn):
    print(f"{sinf} {xc:.4f} {yc:.4f} {w:.4f} {h:.4f}")
Chiqish
5 0.4938 0.4434 0.9782 0.4621
0 0.9141 0.5896 0.1713 0.4480
0 0.1770 0.6036 0.2369 0.4672
0 0.3503 0.5876 0.1499 0.4183
0 0.0425 0.6613 0.0850 0.2929

7-bobda o'z datasetimizni belgilaganimizda aynan shunday fayllar yozamiz.

IoU: ikki box qanchalik ustma-ust tushadi

IoU (Intersection over Union) — bu kitobdagi eng ko'p ishlatiladigan formula. U uch joyda kerak bo'ladi:

  1. Baholash. Model topgan box to'g'rimi? Haqiqiy box bilan IoU si 0.5 dan katta bo'lsa, to'g'ri deb hisoblanadi (7-bob).
  2. NMS. Model bitta odamga ikkita box chizsa, ular IoU bo'yicha aniqlanib, bittasi o'chiriladi (5-bob).
  3. Tracking. Bu kadrdagi box oldingi kadrdagi qaysi box ga eng yaqin? (II qism).

Ta'rif: kesishma yuzi bo'lingan birlashma yuziga:

Maxrajda kesishma bir marta ayiriladi, chunki S_A + S_B da u ikki marta sanalgan. Kesishma ham box, uning burchaklari:


Kodda:

kod/01_obyekt_aniqlash.pydavomi
python17 qator
def iou(a, b):
    """Ikki box orasidagi IoU. a, b: [x1, y1, x2, y2]."""
    ix1, iy1 = max(a[0], b[0]), max(a[1], b[1])          # kesishmaning chap-yuqori burchagi
    ix2, iy2 = min(a[2], b[2]), min(a[3], b[3])          # kesishmaning o'ng-past burchagi
    kesishma = max(0, ix2 - ix1) * max(0, iy2 - iy1)     # ustma-ust tushmasa 0
    sa = (a[2] - a[0]) * (a[3] - a[1])
    sb = (b[2] - b[0]) * (b[3] - b[1])
    return kesishma / (sa + sb - kesishma)

print("yarmi ustma-ust:", iou([0, 0, 100, 100], [50, 0, 150, 100]))
print("aynan bir xil:  ", iou([0, 0, 100, 100], [0, 0, 100, 100]))
print("tegmaydi:       ", iou([0, 0, 100, 100], [200, 200, 300, 300]))

# max(0, ...) ni olib tashlasak nima bo'ladi?
a, b = [0, 0, 100, 100], [200, 200, 300, 300]
soxta = (min(a[2], b[2]) - max(a[0], b[0])) * (min(a[3], b[3]) - max(a[1], b[1]))
print("max(0) siz 'kesishma':", soxta)
Chiqish
yarmi ustma-ust: 0.3333333333333333
aynan bir xil:   1.0
tegmaydi:        0.0
max(0) siz 'kesishma': 10000

Ikki narsaga e'tibor bering:

  • "Yarmi ustma-ust" IoU = 0.5 emas, 0.33. Kesishma 5 000, birlashma esa 15 000. IoU ko'zga ko'ringanidan qattiqroq baho beradi.
  • max(0, ...) shart. Box lar tegmasa, ikkala ayirma ham manfiy chiqadi: (−100) × (−100) = 10 000. Manfiy ko'paytiriladi manfiyga — musbat, va umuman tegmaydigan ikki box ga soxta kesishma paydo bo'ladi. Internetdagi IoU kodlarida bu xato tez-tez uchraydi.

IoU qiymatlari ko'zga qanday ko'rinadi:

kod/01_obyekt_aniqlash.pydavomi
python11 qator
_, axes = plt.subplots(1, 3, figsize=(10, 3.4))
s = 100
for ax, maqsad in zip(axes, [0.3, 0.5, 0.8]):
    d = s * (1 - maqsad) / (1 + maqsad)       # shu siljish aynan kerakli IoU beradi (formulasi pastda)
    A, B = [0, 0, s, s], [d, 0, d + s, s]
    ax.add_patch(Rectangle((0, 0), s, s, fill=False, color="#2563eb", lw=2.5))
    ax.add_patch(Rectangle((d, 0), s, s, fill=False, color="#dc2626", lw=2.5, ls="--"))
    ax.add_patch(Rectangle((d, 0), s - d, s, color="#16a34a", alpha=0.25))
    ax.set_xlim(-10, 2 * s); ax.set_ylim(-10, s + 10); ax.set_aspect("equal"); ax.axis("off")
    ax.set_title(f"IoU = {iou(A, B):.2f}  (siljish {d:.0f} px)")
fig("iou_misollar.png", "Ko'k va qizil box bir xil o'lchamda, faqat gorizontal siljigan. Yashil — kesishma.")

Ko'k va qizil box bir xil o'lchamda, faqat gorizontal siljigan. Yashil — kesishma.

Ko'k va qizil box bir xil o'lchamda, faqat gorizontal siljigan. Yashil — kesishma.

IoU = 0.5 da box lar allaqachon ancha siljigan. Shuning uchun "IoU 0.5" chegarasi yumshoq emas.

Butun matritsa birdaniga

Tracking da bitta juftlik emas, hamma juftliklar kerak: har bir yangi box ni har bir eski box bilan solishtirish. N × M matritsa. Sikl bilan sekin, shuning uchun numpy broadcasting ishlatamiz:

kod/01_obyekt_aniqlash.pydavomi
python17 qator
import pandas as pd

def iou_matrix(A, B):
    """A: (N, 4), B: (M, 4) xyxy formatda -> (N, M) matritsa, [i, j] = IoU(A[i], B[j])."""
    A, B = A[:, None, :], B[None, :, :]       # (N,1,4) va (1,M,4): numpy ularni (N,M) juftlikka yoyadi
    ix1 = np.maximum(A[..., 0], B[..., 0])
    iy1 = np.maximum(A[..., 1], B[..., 1])
    ix2 = np.minimum(A[..., 2], B[..., 2])
    iy2 = np.minimum(A[..., 3], B[..., 3])
    kesishma = np.clip(ix2 - ix1, 0, None) * np.clip(iy2 - iy1, 0, None)   # clip = max(0, ...)
    sa = (A[..., 2] - A[..., 0]) * (A[..., 3] - A[..., 1])
    sb = (B[..., 2] - B[..., 0]) * (B[..., 3] - B[..., 1])
    return kesishma / (sa + sb - kesishma)

M = iou_matrix(xyxy, xyxy)
nomlar = [f"{natija.names[int(c)]}_{i}" for i, c in enumerate(natija.boxes.cls)]
show(pd.DataFrame(M.round(3), index=nomlar, columns=nomlar))
indexbus_0person_1person_2person_3person_4
bus_010.0990.1470.0940.028
person_10.0991000
person_20.147010.0510.061
person_30.09400.05110
person_40.02800.06101
kod/01_obyekt_aniqlash.pydavomi
python4 qator
import torch, torchvision

tv = torchvision.ops.box_iou(torch.tensor(xyxy), torch.tensor(xyxy)).numpy()   # tayyor funksiya
print("torchvision bilan farq:", np.abs(tv - M).max())
Chiqish
torchvision bilan farq: 0.0

Jadvalni o'qing. Diagonal 1 ga teng: har box o'zi bilan to'liq ustma-ust. Odamlar avtobus oldida turibdi, shuning uchun avtobus bilan odamlar orasida IoU noldan katta. Demak IoU > 0 degani "bir xil obyekt" degani emas. Tracker bu matritsani har kadrda quradi va eng yaxshi juftliklarni tanlaydi. Qanday tanlashini II qismda ko'ramiz.

Kichik obyekt muammosi

Bir xil 10 piksel xato katta va kichik obyektga har xil ta'sir qiladi. Tomoni s bo'lgan kvadrat d piksel gorizontal siljisa (d < s), kesishma (s − d) · s, birlashma esa s(s + d) bo'ladi:

Formulani iou() funksiyamiz bilan tekshiramiz:

kod/01_obyekt_aniqlash.pydavomi
python5 qator
d = 10
qatorlar = []
for s in [10, 20, 30, 50, 100, 200, 400]:
    qatorlar.append({"box tomoni, px": s, "formula": (s - d) / (s + d), "iou()": iou([0, 0, s, s], [d, 0, d + s, s])})
show(pd.DataFrame(qatorlar).round(3))
box tomoni, pxformulaiou()
1000
200.3330.333
300.50.5
500.6670.667
1000.8180.818
2000.9050.905
4000.9510.951

400 pikselli box uchun 10 px xato sezilmaydi (IoU 0.95). 30 pikselli box uchun o'sha xato IoU ni 0.5 ga tushiradi, 20 piksellida esa 0.33: bu endi "noto'g'ri topilgan" hisoblanadi. Kameradan uzoqdagi odam aynan shunday kichik box. Xulosa ikkita:

  • Kichik obyektni to'g'ri aniqlash ham, uni kadrdan kadrga IoU bilan ulash ham qiyinroq. II qismda bu muammoga qarshi appearance (tashqi ko'rinish) belgilarini qo'shamiz.
  • Kamerani qayerga va qanday balandlikka qo'yish muhim: odam kadrda necha piksel bo'lishini oldindan hisoblash mumkin. Bu III qismdagi "piksel/metr" hisobi.

Ishonch (confidence) va chegara

Model har bir box ga 0..1 oralig'ida ishonch beradi. Chegaradan past bo'lganlar tashlab yuboriladi. Ultralytics da standart chegara 0.25 (engine/predictor.py: self.args.conf = 0.25). Chegarani o'zgartirib ko'ramiz:

kod/01_obyekt_aniqlash.pydavomi
python9 qator
qatorlar = []
for chegara in [0.05, 0.1, 0.25, 0.5, 0.75]:
    r = model.predict(ASSETS / "bus.jpg", conf=chegara, verbose=False)[0]
    topilgan = [f"{r.names[int(c)]} {p:.2f}" for c, p in zip(r.boxes.cls, r.boxes.conf)]
    qatorlar.append({"conf": chegara, "soni": len(r.boxes), "topilganlar": ", ".join(topilgan)})
show(pd.DataFrame(qatorlar))

r = model.predict(ASSETS / "bus.jpg", conf=0.05, verbose=False)[0]
cv2.imwrite("out/01_bus_conf005.jpg", r.plot())
confsonitopilganlar
0.0511bus 0.94, person 0.89, person 0.88, person 0.86, person 0.62, skateboard 0.12, fire hydrant 0.09, person 0.09, fire hydrant 0.07, handbag 0.06, stop sign 0.06
0.16bus 0.94, person 0.89, person 0.88, person 0.86, person 0.62, skateboard 0.12
0.255bus 0.94, person 0.89, person 0.88, person 0.86, person 0.62
0.55bus 0.94, person 0.89, person 0.88, person 0.86, person 0.62
0.754bus 0.94, person 0.89, person 0.88, person 0.86

conf = 0.05: qo'shimcha 6 ta box hammasi xato

conf = 0.05: qo'shimcha 6 ta box hammasi xato

0.05 da qo'shilgan 6 ta box ni rasmda bittalab tekshirdik, hammasi xato:

  • 2 ta "fire hydrant" — trotuardagi qora ustunchalar;
  • "skateboard" — o'ngdagi odamning oyoq kiyimi;
  • "handbag" — chapdagi odamning paltosi ustida, sumka yo'q;
  • "stop sign" — chap chetdagi dumaloq belgi, lekin u "to'xtash taqiqlangan" belgisi, STOP emas;
  • ikkinchi "person" 0.09 — chap chetdagi odamga ikkinchi marta chizilgan box.

Teskari tomonda, 0.75 da chap chetdagi yarim ko'ringan odam yo'qoldi. Chegara har doim murosaga keladi:

Chegara Yutuq Yo'qotish
Past kam narsa o'tkazib yuboriladi soxta box lar ko'payadi
Baland box lar toza qiyin obyektlar (yarim ko'ringan, uzoqdagi) yo'qoladi

Qaysi biri yomonroq — vazifaga bog'liq. Do'konga kirganlarni sanashda odamni o'tkazib yuborish yomon. Qo'riqlash signalizatsiyasida esa soxta signal yomon: tungi soat 3 da qo'riqchini bekorga uyg'otgan tizimni tez orada o'chirib qo'yishadi. Buni o'lchaydigan metrikalar (precision, recall) 7-bobda.

Yana bir qiziq narsa: past ishonchli box larni tashlab yuborish shart emas. ByteTrack degan tracker ularni ikkinchi bosqichda ishlatadi: "0.15 lik box, lekin aynan oldingi kadrdagi odam turgan joyda — demak bu o'sha odam, faqat yarim to'silgan". Ultralytics tracker sozlamasida buning uchun alohida chegara bor (track_low_thresh: 0.1). II qismda ko'ramiz.

Detector nimani bilmaydi

Detector har bir rasmga alohida qaraydi. Unga ketma-ket ikki kadr bersangiz, ikkalasida "4 ta odam" deydi, lekin qaysi odam qaysi ekanini bilmaydi. Bir-biriga bog'lamaydi.

Do'kon misolida bu nimani anglatadi: kamera 25 kadr/sekund. Kassa oldida 1 daqiqa turgan bitta xaridor 25 × 60 = 1 500 kadrda topiladi. Box larni shunchaki sanasangiz, 1 500 ta "odam" chiqadi. "Nechta odam kirdi" degan savolga javob berish uchun box larni vaqt bo'yicha ulash kerak: "bu box — 17-raqamli odam, u 14:03 da kirdi, 14:04 da kassaga keldi". Bu tracking, II qismning mavzusi.

Undan oldin detector ning o'zini ichidan tushunib olamiz: qayerdan kelgan (2-bob), qanday qatlamlardan qurilgan (3–4-boblar), box larni qanday chiqaradi (5-bob), qanday o'qitiladi (6–7-boblar) va qanday qilib tez ishlatiladi (8-bob).

Asosiy fikr

  • Detection — har bir rasmda box, sinf va ishonch. YOLO buni tarmoqdan bir marta o'tishda qiladi.
  • Box doim 4 ta raqam, lekin 4 xil yozuvda. Formatni adashtirish — eng ko'p uchraydigan xato. Numpy indeksida esa avval y, keyin x.
  • IoU baholash, NMS va tracking ning asosi. Kichik obyektda u juda sezgir: 30 pikselli box uchun 10 px xato IoU ni 0.5 ga tushiradi.

Mashqlar

  1. ASSETS / "zidane.jpg" rasmida modelni ishga tushiring. Nechta obyekt topildi? conf=0.1 da nima qo'shiladi va u to'g'rimi? Rasmni ochib, o'zingiz tekshiring.
  2. iou_matrix ga xyxy o'rniga xywh formatdagi box lar bersangiz nima bo'ladi? Avval taxmin qiling, keyin ishga tushirib tekshiring.
  3. Kameradan uzoqdagi odam 30 px kenglikda ko'rinadi. IoU 0.7 dan tushmasligi uchun box ko'pi bilan necha piksel siljishi mumkin? Ko'rsatma: (s − d) / (s + d) = 0.7 ni d ga nisbatan yeching.

Bu bobni bepul o'qidingiz.

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

Telegramda yozish Narxlar