Harakatni aniqlash va obyektni kuzatish
Ikki xil savol
Videoda ikki xil vazifa bor va ularni chalkashtirmaslik kerak:
- Harakat aniqlash — "kadrda nima qimirladi?" Javob: piksellar maskasi. Obyekt nima ekani noma'lum.
- Kuzatish (tracking) — "men ko'rsatgan obyekt keyingi kadrda qayerda?" Javob: quti. Obyekt bitta.
Birinchisi kuzatuv kamerasi uchun, ikkinchisi esa aniq bir narsani ta'qib qilish uchun.
Bu bobda haqiqat bizda bor: harakat.mp4 — o'zimiz yasagan video, unda ikki obyekt aniq ma'lum
yo'l bo'ylab yuradi, va har kadrdagi koordinatalari harakat_haqiqat.csv da yozilgan.
VIDEO = DATA / "harakat.mp4" haqiqat = pd.read_csv(DATA / "harakat_haqiqat.csv") print(f"kadrlar: {len(haqiqat)}") show(haqiqat.head(4)) cap = cv2.VideoCapture(str(VIDEO)) kadrlar = [] for i in range(150): ok, k = cap.read() if not ok: break if i in (0, 60, 120): kadrlar.append(k) cap.release() ko_rsat(*kadrlar, nom="harakat_kadrlar.png", kenglik=15, sarlavhalar=["kadr 0", "kadr 60", "kadr 120"], izoh="Fon qimirlamaydi, ikki obyekt harakatlanadi: qizil to'rtburchak o'ngga, yashil doira chapga.")
kadrlar: 150
| kadr | x1 | y1 | x2 | y2 |
|---|---|---|---|---|
| 0 | 40 | 250 | 580 | 120 |
| 1 | 43 | 250 | 577 | 123 |
| 2 | 46 | 250 | 575 | 126 |
| 3 | 49 | 250 | 573 | 129 |

Fon qimirlamaydi, ikki obyekt harakatlanadi: qizil to'rtburchak o'ngga, yashil doira chapga.
Eng sodda usul: ikki kadr farqi
cap = cv2.VideoCapture(str(VIDEO)) _, a = cap.read() for _ in range(4): ok, b = cap.read() cap.release() ga = cv2.cvtColor(a, cv2.COLOR_BGR2GRAY) gb = cv2.cvtColor(b, cv2.COLOR_BGR2GRAY) farq = cv2.absdiff(ga, gb) _, maska = cv2.threshold(farq, 25, 255, cv2.THRESH_BINARY) print(f"farq: o'rtacha {farq.mean():.2f}, maksimum {farq.max()} | " f"maskadagi oq piksellar: {int((maska > 0).sum())} ({100 * (maska > 0).mean():.2f}%)") ko_rsat(farq, maska, nom="kadr_farqi.png", sarlavhalar=["absdiff", "threshold 25"], izoh="Harakatlangan obyekt ikki joyda ko'rinadi: eski o'rnida va yangisida.")
farq: o'rtacha 0.97, maksimum 82 | maskadagi oq piksellar: 2829 (1.23%)

Harakatlangan obyekt ikki joyda ko'rinadi: eski o'rnida va yangisida.
Bu usulning kamchiligi darhol ko'rinadi: obyekt ikki marta belgilanadi — eski joyida va yangisida. Chunki ikkala joyda ham piksellar o'zgargan. Bundan tashqari, obyekt to'xtasa u butunlay yo'qoladi.
Fon modeli: MOG2 va KNN
To'g'ri yechim — fon modelini saqlash. OpenCV buni ikki algoritm bilan qiladi: MOG2 (har piksel
uchun Gauss aralashmasi) va KNN. Ikkalasi ham fonni asta-sekin o'rganadi va undan farq qilgan
pikselni "oldingi plan" deb belgilaydi.
def fon_ayirish(yaratuvchi, yol=VIDEO, n=150): """Har kadrda maska yasaydi va eng katta ikki bo'lakning markazini qaytaradi.""" fon = yaratuvchi() cap = cv2.VideoCapture(str(yol)) yadro = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5, 5)) natijalar, maskalar = [], {} for t in range(n): ok, kadr = cap.read() if not ok: break m = fon.apply(kadr) m[m == 127] = 0 # soya piksellari (MOG2) — olib tashlaymiz m = cv2.morphologyEx(m, cv2.MORPH_OPEN, yadro) if t in (5, 75, 145): maskalar[t] = m.copy() kont, _ = cv2.findContours(m, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) katt = sorted([c for c in kont if cv2.contourArea(c) > 150], key=cv2.contourArea, reverse=True)[:2] markazlar = [] for c in katt: M = cv2.moments(c) markazlar.append((M["m10"] / M["m00"], M["m01"] / M["m00"])) natijalar.append({"kadr": t, "topilgan": len(katt), "markazlar": markazlar, "oq %": round(100 * (m > 0).mean(), 2)}) cap.release() return pd.DataFrame(natijalar), maskalar mog_df, mog_maskalar = fon_ayirish(lambda: cv2.createBackgroundSubtractorMOG2(history=200, varThreshold=25, detectShadows=True)) knn_df, knn_maskalar = fon_ayirish(lambda: cv2.createBackgroundSubtractorKNN(history=200, dist2Threshold=400)) show(pd.DataFrame([ {"usul": "MOG2", "ikki obyekt topilgan kadrlar": int((mog_df["topilgan"] == 2).sum()), "hech narsa topilmagan": int((mog_df["topilgan"] == 0).sum()), "o'rtacha oq %": round(mog_df["oq %"].mean(), 2)}, {"usul": "KNN", "ikki obyekt topilgan kadrlar": int((knn_df["topilgan"] == 2).sum()), "hech narsa topilmagan": int((knn_df["topilgan"] == 0).sum()), "o'rtacha oq %": round(knn_df["oq %"].mean(), 2)}, ])) ko_rsat(*mog_maskalar.values(), nom="mog2_maskalar.png", kenglik=15, sarlavhalar=[f"MOG2, kadr {t}" for t in mog_maskalar], izoh="Boshida fon hali o'rganilmagan, keyin maska tozalanadi va faqat ikki obyekt qoladi.")
| usul | ikki obyekt topilgan kadrlar | hech narsa topilmagan | o'rtacha oq % |
|---|---|---|---|
| MOG2 | 135 | 10 | 1.32 |
| KNN | 146 | 0 | 4.26 |

Boshida fon hali o'rganilmagan, keyin maska tozalanadi va faqat ikki obyekt qoladi.
Aniqlikni o'lchash
Endi topilgan markazlarni haqiqiy koordinatalar bilan solishtiramiz:
def xatoni_olch(df, nom, chegara=40): """Har kadrdagi ikki nishon uchun eng yaqin topilgan markazgacha masofani o'lchaydi.""" masofalar = [] for _, q in df.iterrows(): h = haqiqat.iloc[int(q["kadr"])] for nx, ny in [(h["x1"], h["y1"]), (h["x2"], h["y2"])]: if not q["markazlar"]: masofalar.append(np.inf) else: masofalar.append(min(((mx - nx) ** 2 + (my - ny) ** 2) ** 0.5 for mx, my in q["markazlar"])) topilgan = [d for d in masofalar if d < chegara] return {"usul": nom, "tekshirilgan nishonlar": len(masofalar), f"{chegara} px ichida topilgan": len(topilgan), "topish darajasi %": round(100 * len(topilgan) / len(masofalar), 1), "o'rtacha xato, px": round(float(np.mean(topilgan)), 2) if topilgan else None} show(pd.DataFrame([xatoni_olch(mog_df, "MOG2"), xatoni_olch(knn_df, "KNN")]))
| usul | tekshirilgan nishonlar | 40 px ichida topilgan | topish darajasi % | o'rtacha xato, px |
|---|---|---|---|---|
| MOG2 | 300 | 271 | 90.3 | 3.57 |
| KNN | 300 | 283 | 94.3 | 1.95 |
Markaz xatosi bir necha piksel — bu obyektni sanash va joyini bildirish uchun yetarli aniqlik.
Fon o'rganish tezligi
history parametri fon modelining "xotirasi". Uni o'zgartirsak nima bo'ladi?
qatorlar = [] for tarix in (20, 100, 500): df, _ = fon_ayirish(lambda t=tarix: cv2.createBackgroundSubtractorMOG2(history=t, varThreshold=25, detectShadows=False)) n = xatoni_olch(df, f"history={tarix}") n["o'rtacha oq %"] = round(df["oq %"].mean(), 2) qatorlar.append(n) show(pd.DataFrame(qatorlar))
| usul | tekshirilgan nishonlar | 40 px ichida topilgan | topish darajasi % | o'rtacha xato, px | o'rtacha oq % |
|---|---|---|---|---|---|
| history=20 | 300 | 172 | 57.3 | 16.78 | 0.87 |
| history=100 | 300 | 276 | 92 | 6.09 | 1.8 |
| history=500 | 300 | 276 | 92 | 3.83 | 2.01 |
Kichik history — fon tez yangilanadi, sekin harakatlanuvchi obyekt fonga "singib ketadi". Katta
history — fon sekin yangilanadi, haqiqiy o'zgarishlar (yorug'lik, ko'chirilgan buyum) uzoq vaqt
"harakat" bo'lib qoladi.
Eng muhim shart: kamera qimirlamasligi kerak
Bu usul fonni qotgan deb hisoblaydi. Agar kamera qimirlasa, butun kadr "harakat" bo'lib chiqadi. Buni haqiqiy videoda o'lchaymiz:
QURILISH = DATA.parent.parent / "YOLO_Tracking_Kitob" / "datasets" / "pexels" / "13921040_2560x1440.mp4" def siljish_va_maska(yol, n=60, olcham=(640, 360)): cap = cv2.VideoCapture(str(yol)) fon = cv2.createBackgroundSubtractorMOG2(history=200, varThreshold=25, detectShadows=False) oldingi, siljishlar, oq = None, [], [] namuna = None for t in range(n): ok, kadr = cap.read() if not ok: break k = cv2.resize(kadr, olcham) g = cv2.cvtColor(k, cv2.COLOR_BGR2GRAY).astype(np.float64) if oldingi is not None: (dx, dy), _ = cv2.phaseCorrelate(oldingi, g) siljishlar.append((dx ** 2 + dy ** 2) ** 0.5) oldingi = g m = fon.apply(k) oq.append(100 * (m > 0).mean()) if t == n - 1: namuna = m cap.release() return float(np.mean(siljishlar)), float(np.mean(oq[10:])), namuna s1, o1, m1 = siljish_va_maska(VIDEO) s2, o2, m2 = siljish_va_maska(QURILISH) show(pd.DataFrame([ {"video": "harakat.mp4 (qotgan kamera)", "kadrlar orasi siljish, px": round(s1, 2), "maskadagi oq %": round(o1, 2)}, {"video": "qurilish (kamera suriladi)", "kadrlar orasi siljish, px": round(s2, 2), "maskadagi oq %": round(o2, 2)}, ])) ko_rsat(m1, m2, nom="kamera_qimirlashi.png", sarlavhalar=["qotgan kamera — faqat obyektlar", "suriladigan kamera — butun kadr"], izoh="Kamera surilsa fon ayirish ma'nosini yo'qotadi: hamma narsa 'harakat' bo'lib chiqadi.")
| video | kadrlar orasi siljish, px | maskadagi oq % |
|---|---|---|
| harakat.mp4 (qotgan kamera) | 2.98 | 1.02 |
| qurilish (kamera suriladi) | 6.28 | 13.86 |

Kamera surilsa fon ayirish ma'nosini yo'qotadi: hamma narsa 'harakat' bo'lib chiqadi.
Hal qiluvchi ustun — oxirgisi. Qotgan kamerada maskaning 1.02% i oq (ya'ni ikki obyekt), suriladigan kamerada esa 13.86% — o'n uch barobar ko'p. Rasmda ko'rinib turibdi: o'ngdagi maska deyarli butun kadrni "harakat" deb belgilagan.
(Birinchi ustunga e'tibor bering: qotgan kamerada ham siljish 2.98 px chiqdi. Bu kamera qimirlagani
emas — phaseCorrelate butun kadrga qaraydi va harakatlanuvchi obyektlarning o'zi natijaga ta'sir
qiladi. Shuning uchun qaror uchun maska foizi ishonchliroq o'lchov.)
Yechimlar: (1) kamerani mahkam o'rnatish — eng arzoni; (2) kadrlarni stabilizatsiya qilish (13-bobdagi homografiya bilan kadrni oldingisiga moslash); (3) fon ayirish o'rniga obyekt detektorini ishlatish (YOLO kabi).
Optik oqim: har piksel qayoqqa ketdi
Optik oqim har piksel uchun siljish vektorini beradi. calcOpticalFlowFarneback — zich variant
(hamma piksel uchun):
cap = cv2.VideoCapture(str(VIDEO)) cap.set(cv2.CAP_PROP_POS_FRAMES, 60) _, f1 = cap.read() _, f2 = cap.read() cap.release() g1 = cv2.cvtColor(f1, cv2.COLOR_BGR2GRAY) g2 = cv2.cvtColor(f2, cv2.COLOR_BGR2GRAY) oqim = cv2.calcOpticalFlowFarneback(g1, g2, None, 0.5, 3, 15, 3, 5, 1.2, 0) kuch, burchak = cv2.cartToPolar(oqim[..., 0], oqim[..., 1]) print(f"oqim tensori: {oqim.shape} (har piksel uchun dx, dy)") print(f"eng katta siljish: {kuch.max():.2f} px | o'rtacha: {kuch.mean():.3f} px") print(f"haqiqat: qizil to'rtburchak kadriga 3.0 px, yashil doira 2.2 px siljiydi") hsv = np.zeros_like(f1) hsv[..., 0] = burchak * 180 / np.pi / 2 hsv[..., 1] = 255 hsv[..., 2] = cv2.normalize(kuch, None, 0, 255, cv2.NORM_MINMAX) ko_rsat(cv2.cvtColor(hsv, cv2.COLOR_HSV2BGR), (kuch > 1.0).astype(np.uint8) * 255, nom="optik_oqim.png", sarlavhalar=["oqim (rang — yo'nalish)", "kuch > 1 px"], izoh="Rang harakat yo'nalishini, yorqinlik esa tezligini bildiradi. Fon qora — u qimirlamaydi.")
oqim tensori: (360, 640, 2) (har piksel uchun dx, dy) eng katta siljish: 3.59 px | o'rtacha: 0.092 px haqiqat: qizil to'rtburchak kadriga 3.0 px, yashil doira 2.2 px siljiydi

Rang harakat yo'nalishini, yorqinlik esa tezligini bildiradi. Fon qora — u qimirlamaydi.
Oqim kuchini haqiqat bilan solishtiramiz — obyektlar ustidagi o'rtacha siljish qancha?
h60 = haqiqat.iloc[60] qatorlar = [] for nom, (cx, cy), r, kutilgan in [("qizil to'rtburchak", (h60["x1"], h60["y1"]), 18, 3.0), ("yashil doira", (h60["x2"], h60["y2"]), 20, 2.2)]: m = np.zeros(kuch.shape, np.uint8) cv2.circle(m, (int(cx), int(cy)), r, 255, -1) olchangan = float(kuch[m > 0].mean()) qatorlar.append({"obyekt": nom, "o'lchangan siljish, px": round(olchangan, 2), "haqiqiy siljish, px": kutilgan, "xato, px": round(abs(olchangan - kutilgan), 2)}) fon_m = np.ones(kuch.shape, np.uint8) * 255 for cx, cy, r in [(h60["x1"], h60["y1"], 40), (h60["x2"], h60["y2"], 40)]: cv2.circle(fon_m, (int(cx), int(cy)), r, 0, -1) qatorlar.append({"obyekt": "fon (qimirlamaydi)", "o'lchangan siljish, px": round(float(kuch[fon_m > 0].mean()), 2), "haqiqiy siljish, px": 0.0, "xato, px": round(float(kuch[fon_m > 0].mean()), 2)}) show(pd.DataFrame(qatorlar))
| obyekt | o'lchangan siljish, px | haqiqiy siljish, px | xato, px |
|---|---|---|---|
| qizil to'rtburchak | 0.57 | 3 | 2.43 |
| yashil doira | 2.22 | 2.2 | 0.02 |
| fon (qimirlamaydi) | 0.02 | 0 | 0.02 |
Fon uchun natija mukammal: o'lchangan siljish 0.02 px, haqiqat 0. Yashil doira uchun ham deyarli aniq: 2.22 px, haqiqat 2.2.
Qizil to'rtburchak esa muammo berdi: o'lchangan 0.57 px, haqiqiy 3.0. Besh barobar kam.
Bu optik oqimning mashhur cheklovi — apertura muammosi. Optik oqim pikselning siljishini uning atrofidagi yorqinlik o'zgarishiga qarab topadi. To'rtburchakning ichida esa hamma piksel bir xil rangda: u 3 piksel siljiganda ham ichkaridagi piksel atrofida hech narsa o'zgarmaydi. Algoritm "bu yer qimirlamadi" deb xulosa qiladi.
Doira bilan nega bunday bo'lmadi? U kichikroq (radius 22), shuning uchun biz o'lchagan soha uning chekkalarini ham qamrab oldi — chekkada esa yorqinlik o'zgaradi va oqim to'g'ri hisoblanadi.
Amaliy qoida: optik oqimga faqat teksturali joyda ishoning. Shuning uchun siyrak (sparse) variant —
calcOpticalFlowPyrLK — oqimni hamma pikselda emas, faqat 13-bobdagi kabi kalit nuqtalarda hisoblaydi.
U tabiatan teksturali joylarni tanlaydi va aynan shu muammodan qochadi.
Obyektni kuzatish: trackerlar
Endi ikkinchi vazifa. Biz obyektni bir marta ko'rsatamiz, tracker uni keyingi kadrlarda o'zi topadi.
Avval qaysi trackerlar mavjudligini tekshiramiz — bu o'rnatilgan paketga bog'liq:
nomzodlar = ["TrackerMIL_create", "TrackerCSRT_create", "TrackerKCF_create", "TrackerGOTURN_create"] show(pd.DataFrame([{"tracker": n.replace("_create", ""), "mavjudmi": hasattr(cv2, n)} for n in nomzodlar])) print("opencv-python versiyasi:", cv2.__version__)
opencv-python versiyasi: 4.10.0
| tracker | mavjudmi |
|---|---|
| TrackerMIL | True |
| TrackerCSRT | False |
| TrackerKCF | False |
| TrackerGOTURN | True |
:::gotcha CSRT va KCF asosiy paketda yo'q
Ko'p darsliklarda cv2.TrackerCSRT_create() ishlatiladi, lekin u opencv-contrib-python paketida.
Oddiy opencv-python da faqat TrackerMIL bor (va model fayli talab qiladigan GOTURN, Nano, Vit).
Kerak bo'lsa: pip uninstall opencv-python && pip install opencv-contrib-python. Ikkalasini birga
o'rnatmang — ular to'qnashadi.
:::
Bizda TrackerMIL bor. Uni o'zimiz yozgan eng sodda tracker bilan solishtiramiz: har kadrda
oldingi joy atrofidagi kichik sohada 12-bobdagi matchTemplate ni ishlatamiz.
import time def kuzat_mil(n=150): cap = cv2.VideoCapture(str(VIDEO)) ok, kadr = cap.read() h0 = haqiqat.iloc[0] tr = cv2.TrackerMIL_create() tr.init(kadr, (int(h0["x1"] - 18), int(h0["y1"] - 30), 36, 60)) xatolar, yoqolgan = [], 0 t0 = time.perf_counter() for t in range(1, n): ok, kadr = cap.read() if not ok: break topildi, q = tr.update(kadr) if not topildi: yoqolgan += 1 continue hq = haqiqat.iloc[t] xatolar.append(((q[0] + q[2] / 2 - hq["x1"]) ** 2 + (q[1] + q[3] / 2 - hq["y1"]) ** 2) ** 0.5) cap.release() return xatolar, yoqolgan, time.perf_counter() - t0 def kuzat_shablon(n=150, radius=30, min_ball=0.2): """Oldingi joy atrofidagi kichik sohada shablonni qidiradi.""" cap = cv2.VideoCapture(str(VIDEO)) ok, kadr = cap.read() h0 = haqiqat.iloc[0] cx, cy = float(h0["x1"]), float(h0["y1"]) shab = cv2.cvtColor(kadr, cv2.COLOR_BGR2GRAY)[int(cy) - 30:int(cy) + 30, int(cx) - 18:int(cx) + 18] sh_h, sh_w = shab.shape xatolar, yoqolgan, ballar = [], 0, [] t0 = time.perf_counter() for t in range(1, n): ok, kadr = cap.read() if not ok: break g = cv2.cvtColor(kadr, cv2.COLOR_BGR2GRAY) x0 = max(0, int(cx) - sh_w // 2 - radius) # qidiruv oynasi y0 = max(0, int(cy) - sh_h // 2 - radius) soha = g[y0:int(cy) + sh_h // 2 + radius, x0:int(cx) + sh_w // 2 + radius] if soha.shape[0] < sh_h or soha.shape[1] < sh_w: yoqolgan += 1 continue r = cv2.matchTemplate(soha, shab, cv2.TM_CCOEFF_NORMED) _, ball, _, joy = cv2.minMaxLoc(r) ballar.append(ball) if ball < min_ball: yoqolgan += 1 continue cx, cy = x0 + joy[0] + sh_w / 2, y0 + joy[1] + sh_h / 2 hq = haqiqat.iloc[t] xatolar.append(((cx - hq["x1"]) ** 2 + (cy - hq["y1"]) ** 2) ** 0.5) cap.release() return xatolar, yoqolgan, time.perf_counter() - t0, ballar
Bu yerda bitta parametr bor va uni taxmin qilib bo'lmaydi: min_ball — "moslik yetarlimi?" chegarasi.
12-bobda ball 1.0 ga yaqin edi, shuning uchun 0.5 mantiqiy ko'rinadi. Tekshiramiz:
qatorlar = [] for chegara in (0.5, 0.4, 0.3, 0.2, 0.1): xat, yoq, dt, ballar = kuzat_shablon(min_ball=chegara) qatorlar.append({"min_ball": chegara, "kuzatilgan": len(xat), "yo'qotilgan": yoq, "o'rtacha xato, px": round(float(np.mean(xat)), 2) if xat else None}) show(pd.DataFrame(qatorlar)) _, _, _, ballar = kuzat_shablon(min_ball=0.0) print(f"haqiqiy ballar taqsimoti — eng kichigi: {min(ballar):.3f}, " f"o'rtacha: {np.mean(ballar):.3f}, eng kattasi: {max(ballar):.3f}")
haqiqiy ballar taqsimoti — eng kichigi: 0.200, o'rtacha: 0.252, eng kattasi: 0.766
| min_ball | kuzatilgan | yo'qotilgan | o'rtacha xato, px |
|---|---|---|---|
| 0.5 | 1 | 148 | 0 |
| 0.4 | 2 | 147 | 0 |
| 0.3 | 3 | 146 | 0 |
| 0.2 | 148 | 1 | 38.65 |
| 0.1 | 149 | 0 | 38.41 |
Jadval ikkita narsani ko'rsatadi va ikkalasi ham men kutganimdan boshqacha.
0.5, 0.4, 0.3 chegaralarida tracker deyarli darhol to'xtaydi — mos ravishda 1, 2 va 3 kadrdan
keyin. Ballar taqsimoti sababini aytadi: eng kattasi 0.766 (birinchi kadr), o'rtachasi esa atigi
0.252. Ya'ni 12-bobdagi 1.0 ga yaqin ballar bu yerda umuman yo'q.
Lekin chegarani 0.2 ga tushirganda ham masala hal bo'lmadi: tracker 148 kadr "ishladi", ammo o'rtacha xatosi 38.65 piksel — obyektning o'zi 36x60 px ekanini hisobga olsak, bu butunlay boshqa joyni kuzatayotgani degani.
Sabab 12-bobdan tanish: bizning obyektimiz bir tekis rangli to'rtburchak, foni ham deyarli bir tekis. Bu o'sha "tekis shablon" tuzog'ining o'zi — mos kelish xaritasida bir-biriga yaqin ko'p zaif maksimum bor, va tracker bir necha kadrdan keyin ularning birortasiga yopishib qoladi.
Muhim xulosa: past ball — nosozlik emas, ogohlantirish. U "bu obyektni ko'rinishi bo'yicha kuzatib bo'lmaydi" deb aytayotgan edi. Chegarani pasaytirib uni "tuzatish" — ogohlantirishni o'chirish bilan bir xil.
Endi MIL bilan solishtiramiz:
qatorlar = [] xat, yoq, dt = kuzat_mil() qatorlar.append({"usul": "TrackerMIL", "kuzatilgan kadrlar": len(xat), "yo'qotilgan": yoq, "o'rtacha xato, px": round(float(np.mean(xat)), 2), "eng katta xato, px": round(float(np.max(xat)), 1), "kadriga, ms": round(dt / 149 * 1000, 2)}) xat, yoq, dt, _ = kuzat_shablon(min_ball=0.2) qatorlar.append({"usul": "o'zimiz yozgan (shablon)", "kuzatilgan kadrlar": len(xat), "yo'qotilgan": yoq, "o'rtacha xato, px": round(float(np.mean(xat)), 2), "eng katta xato, px": round(float(np.max(xat)), 1), "kadriga, ms": round(dt / 149 * 1000, 2)}) show(pd.DataFrame(qatorlar))
| usul | kuzatilgan kadrlar | yo'qotilgan | o'rtacha xato, px | eng katta xato, px | kadriga, ms |
|---|---|---|---|---|---|
| TrackerMIL | 149 | 0 | 14.36 | 27.3 | 22.69 |
| o'zimiz yozgan (shablon) | 148 | 1 | 38.65 | 39.4 | 0.27 |
MIL ancha yaxshi: 149 kadrning hammasini kuzatdi, o'rtacha xato 14.36 px. U bitta shablonni emas, obyektning ko'p variantidan o'rganilgan modelni saqlaydi, shuning uchun zaif tekstura bilan ham kurasha oladi. Narxi — 22.57 ms kadriga, ya'ni bizning o'zimiz yozganimizdan (0.27 ms) sakson barobar sekin.
| Tracker | Xususiyati | Qayerda |
|---|---|---|
| MIL | Sekin (22.6 ms), qisman berkitilgan obyektni ushlab turadi | Asosiy paketda |
| CSRT | Eng aniq; o'lcham o'zgarishiga moslashadi | opencv-contrib-python |
| KCF | Tez, aniqligi o'rtacha | opencv-contrib-python |
| Shablon + oyna | 0.27 ms, lekin tekis obyektda adashadi | O'zingiz yozasiz |
Contrib o'rnatilgan bo'lsa tanlov qoidasi: aniqlik kerak bo'lsa CSRT, tezlik kerak bo'lsa KCF.
Eng muhim xulosa: to'g'ri asbobni tanlang
Uchta usulning o'sha obyekt ustidagi natijasini yonma-yon qo'yamiz:
| Usul | O'rtacha xato | Kadriga |
|---|---|---|
| Fon ayirish (KNN) + kontur | 1.95 px | ~1 ms |
| TrackerMIL | 14.36 px | 22.57 ms |
| Shablon + qidiruv oynasi | 38.65 px | 0.27 ms |
Kuzatuv trackerlari yigirma barobar yomonroq natija berdi va yigirma barobar ko'p vaqt oldi.
Nega? Chunki bu vazifa uchun tracker noto'g'ri asbob. Kamera qotgan, fon ma'lum, obyektlar fondan ajralib turadi — bunday sharoitda har kadrda qaytadan topish kuzatishdan arzon ham, aniq ham.
Tracker qachon kerak bo'ladi: kamera qimirlaganda, fon o'zgaruvchan bo'lganda, yoki bir xil ko'rinishdagi ko'p obyekt ichidan aynan bittasini ajratib turish kerak bo'lganda. Ya'ni detektor "qaysi biri meniki?" degan savolga javob bera olmaganda.
:::gotcha Tracker adashganini o'zi aytmaydi
tr.update() True qaytarsa ham, quti butunlay boshqa narsada bo'lishi mumkin — tracker fonga
"yopishib" qolgan bo'lishi mumkin. Shuning uchun amaliy tizimlarda tracker har N kadrda detektor bilan
qayta tekshiriladi. Bu "detect + track" naqshi: detektor sekin, lekin ishonchli; tracker tez, lekin
vaqt o'tishi bilan adashadi.
:::
Xulosa
| Tushuncha | Asosiy fikr |
|---|---|
| Kadr farqi | Eng sodda; obyektni ikki marta belgilaydi, to'xtaganda yo'qotadi |
| MOG2 / KNN | Fon modelini o'rganadi; bu videoda KNN aniqroq (1.95 px vs 3.57 px) |
history |
Xotira uzunligi; 20 da sekin obyekt fonga singib ketdi (topish 57%) |
detectShadows |
Soyani 127 qiymat bilan belgilaydi; odatda uni 0 qilish kerak |
| Kamera | Qimirlamasligi shart: suriladigan kamerada maska 1.02% dan 13.86% ga chiqdi |
| Optik oqim | Teksturali joyda aniq (doira 2.22 vs 2.2), bir tekis ichkarida yo'q (0.57 vs 3.0) |
| Tracker | Bitta obyektni ta'qib qiladi; CSRT/KCF uchun opencv-contrib-python kerak |
| Asbob tanlash | Qotgan kamerada fon ayirish trackerdan aniqroq: 1.95 px vs 14.36 px |
| Past ball | Nosozlik emas, ogohlantirish — chegarani pasaytirib "tuzatmang" |
| Detect + track | Amaliy tizimlarda tracker davriy ravishda detektor bilan tuzatiladi |
Talaba ishi
varThresholdni 10, 25, 50, 100 qilib MOG2 ni qayta o'lchang. Kichik qiymatda nima ko'payadi, kattasida nima yo'qoladi?- Kadr farqi usulini uchta kadr bilan qiling (
t-1,t,t+1ning ikki farqinibitwise_and). "Ikki marta belgilash" muammosi yo'qoldimi? - Trackerni yashil doiradan boshlang (u sinus bo'yicha yuradi). Xato to'rtburchaknikidan kattami yoki kichikmi, va nega? Doiraning chekkasi shablon uchun nima beradi?
- Qurilish videosini 13-bobdagi homografiya bilan stabilizatsiya qiling (har kadrni birinchisiga moslang), keyin MOG2 ni qo'llang. Maskadagi oq foiz qanchaga tushdi?
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.