Obyekt aniqlash: box, IoU va birinchi YOLO
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:
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:
- Fayllarni kitob papkasidan ishga tushiring:
python kod/01_obyekt_aniqlash.py. Koddaweights/,datasets/,out/yo'llari shu papkaga nisbatan yozilgan. - 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:
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:
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})")
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)
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
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 unigithub.com/ultralytics/assetsreliz 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. Nomidagin— nano, eng kichik versiya.s,m,l,xkattaroq 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]—predicthar 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:
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)")
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:
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)
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:
- Tartib.
shapeda avval balandlik (1080), keyin kenglik (810). Box koordinatalarida esa avval x, keyin y. Shuning uchun(x=300, y=500)nuqtadagi pikselimg[500, 300]bo'ladi,img[300, 500]emas. - 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:
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: 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:
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))
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:
# 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}")
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:
- 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).
- NMS. Model bitta odamga ikkita box chizsa, ular IoU bo'yicha aniqlanib, bittasi o'chiriladi (5-bob).
- 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:
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)
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:
_, 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.
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:
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))
| index | bus_0 | person_1 | person_2 | person_3 | person_4 |
|---|---|---|---|---|---|
| bus_0 | 1 | 0.099 | 0.147 | 0.094 | 0.028 |
| person_1 | 0.099 | 1 | 0 | 0 | 0 |
| person_2 | 0.147 | 0 | 1 | 0.051 | 0.061 |
| person_3 | 0.094 | 0 | 0.051 | 1 | 0 |
| person_4 | 0.028 | 0 | 0.061 | 0 | 1 |
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())
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:
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, px | formula | iou() |
|---|---|---|
| 10 | 0 | 0 |
| 20 | 0.333 | 0.333 |
| 30 | 0.5 | 0.5 |
| 50 | 0.667 | 0.667 |
| 100 | 0.818 | 0.818 |
| 200 | 0.905 | 0.905 |
| 400 | 0.951 | 0.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:
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())
| conf | soni | topilganlar |
|---|---|---|
| 0.05 | 11 | bus 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.1 | 6 | bus 0.94, person 0.89, person 0.88, person 0.86, person 0.62, skateboard 0.12 |
| 0.25 | 5 | bus 0.94, person 0.89, person 0.88, person 0.86, person 0.62 |
| 0.5 | 5 | bus 0.94, person 0.89, person 0.88, person 0.86, person 0.62 |
| 0.75 | 4 | bus 0.94, person 0.89, person 0.88, person 0.86 |

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
ASSETS / "zidane.jpg"rasmida modelni ishga tushiring. Nechta obyekt topildi?conf=0.1da nima qo'shiladi va u to'g'rimi? Rasmni ochib, o'zingiz tekshiring.iou_matrixgaxyxyo'rnigaxywhformatdagi box lar bersangiz nima bo'ladi? Avval taxmin qiling, keyin ishga tushirib tekshiring.- 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.