Asosiy qismga o'tish

Harakatni aniqlash va obyektni kuzatish

15-bob 12 kod bloki 4504 so'z

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.

python17 qator
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.")
Chiqish
kadrlar: 150
kadrx1y1x2y2
040250580120
143250577123
246250575126
349250573129

Fon qimirlamaydi, ikki obyekt harakatlanadi: qizil to'rtburchak o'ngga, yashil doira chapga.

Fon qimirlamaydi, ikki obyekt harakatlanadi: qizil to'rtburchak o'ngga, yashil doira chapga.

Eng sodda usul: ikki kadr farqi

python13 qator
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.")
Chiqish
farq: o'rtacha 0.97, maksimum 82 | maskadagi oq piksellar: 2829 (1.23%)

Harakatlangan obyekt ikki joyda ko'rinadi: eski o'rnida va yangisida.

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.

python37 qator
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.")
usulikki obyekt topilgan kadrlarhech narsa topilmagano'rtacha oq %
MOG2135101.32
KNN14604.26

Boshida fon hali o'rganilmagan, keyin maska tozalanadi va faqat ikki obyekt qoladi.

Boshida fon hali o'rganilmagan, keyin maska tozalanadi va faqat ikki obyekt qoladi.

Aniqlikni o'lchash

Endi topilgan markazlarni haqiqiy koordinatalar bilan solishtiramiz:

python17 qator
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")]))
usultekshirilgan nishonlar40 px ichida topilgantopish darajasi %o'rtacha xato, px
MOG230027190.33.57
KNN30028394.31.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?

python7 qator
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))
usultekshirilgan nishonlar40 px ichida topilgantopish darajasi %o'rtacha xato, pxo'rtacha oq %
history=2030017257.316.780.87
history=100300276926.091.8
history=500300276923.832.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:

python33 qator
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.")
videokadrlar orasi siljish, pxmaskadagi oq %
harakat.mp4 (qotgan kamera)2.981.02
qurilish (kamera suriladi)6.2813.86

Kamera surilsa fon ayirish ma'nosini yo'qotadi: hamma narsa 'harakat' bo'lib chiqadi.

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

python20 qator
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.")
Chiqish
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.

Rang harakat yo'nalishini, yorqinlik esa tezligini bildiradi. Fon qora — u qimirlamaydi.

Oqim kuchini haqiqat bilan solishtiramiz — obyektlar ustidagi o'rtacha siljish qancha?

python15 qator
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))
obyekto'lchangan siljish, pxhaqiqiy siljish, pxxato, px
qizil to'rtburchak0.5732.43
yashil doira2.222.20.02
fon (qimirlamaydi)0.0200.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:

python3 qator
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__)
Chiqish
opencv-python versiyasi: 4.10.0
trackermavjudmi
TrackerMILTrue
TrackerCSRTFalse
TrackerKCFFalse
TrackerGOTURNTrue

:::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.

python55 qator
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:

python9 qator
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}")
Chiqish
haqiqiy ballar taqsimoti — eng kichigi: 0.200, o'rtacha: 0.252, eng kattasi: 0.766
min_ballkuzatilganyo'qotilgano'rtacha xato, px
0.511480
0.421470
0.331460
0.2148138.65
0.1149038.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:

python10 qator
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))
usulkuzatilgan kadrlaryo'qotilgano'rtacha xato, pxeng katta xato, pxkadriga, ms
TrackerMIL149014.3627.322.69
o'zimiz yozgan (shablon)148138.6539.40.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

  1. varThreshold ni 10, 25, 50, 100 qilib MOG2 ni qayta o'lchang. Kichik qiymatda nima ko'payadi, kattasida nima yo'qoladi?
  2. Kadr farqi usulini uchta kadr bilan qiling (t-1, t, t+1 ning ikki farqini bitwise_and). "Ikki marta belgilash" muammosi yo'qoldimi?
  3. Trackerni yashil doiradan boshlang (u sinus bo'yicha yuradi). Xato to'rtburchaknikidan kattami yoki kichikmi, va nega? Doiraning chekkasi shablon uchun nima beradi?
  4. 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.

Telegramda yozish Narxlar