Kamera tanlash: piksel zichligi, ob'ektiv va masofa
Bu bobning kodi: kod/17_kamera_tanlash.py — kitob papkasidan ishga tushiring: python kod/17_kamera_tanlash.py
Mijozning birinchi savoli
Loyiha kod bilan emas, savol bilan boshlanadi: "Qanday kamera olay, qayerga ilay?" Noto'g'ri kamera keyin hech qanday model bilan tuzalmaydi: odam 20 piksel bo'lib chiqsa, YOLO uni topmaydi, yuz 15 piksel bo'lsa, tanib bo'lmaydi. Bu bobda shu savolga o'lchangan raqamlar bilan javob beramiz:
- Odam kadrda necha piksel bo'lishi kamera, ob'ektiv va masofaga qanday bog'liq?
- YOLO11n odamni ishonchli topishi uchun necha piksel kerak? Kaska uchun-chi?
- Kodek, bitreyt, tungi rejim natijaga qanchalik ta'sir qiladi?
Piksel zichligi va DORI
Kuzatuv sohasida kameraning "qanchalik yaxshi ko'rishi" piksel zichligi bilan o'lchanadi — nishon joylashgan masofada bir metrga nechta piksel to'g'ri keladi (px/m). Xalqaro standart IEC 62676-4:2014 buni to'rt vazifa bilan bog'lagan — DORI. Asosiy g'oya yuz kengligi: o'rtacha yuz 16 sm deb olinadi.
| Vazifa | Yuzga piksel | px/m | Ma'nosi |
|---|---|---|---|
| Detection (aniqlash) | 4 | 25 | kadrda odam borligini ko'rish |
| Observation (kuzatish) | 10 | 62.5 | nima qilayotgani, kiyimi |
| Recognition (tanish) | 20 | 125 | tanish odamni "bu o'sha" deb bilish |
| Identification (shaxsni aniqlash) | 40 | 250 | notanish odamning shaxsini aniqlash |
2025-yilgi yangi tahrir (IEC 62676-4:2025, 2025-yil 9-oktyabrdan kuchda) yetti pog'onali shkalaga o'tdi: 20, 40, 80, 125, 250, 500 va 1500 px/m. Eng muhim o'zgarish: ishonchli shaxs aniqlash uchun endi 500 px/m tavsiya qilinadi — 250 px/m kam yorug'likda va xira tasvirda yetmasligi amalda ko'rilgan. Bu bobning oxirida biz ham shunga o'xshash xulosaga kelamiz.
Formula: piksel = f · o'lcham / masofa
15-bobdagi teshikli kamera modelidan: masofasi Z bo'lgan S o'lchamli narsa rasmda f·S/Z piksel bo'ladi (f — piksellardagi fokus masofasi, K matritsadagi f_x). Demak piksel zichligi:
Bu yerda W — kadr kengligi pikselda (1920, 2560, 3840), HFOV — gorizontal ko'rish burchagi. Kamera hujjatida (datasheet) ob'ektivning fokus masofasi mm da va HFOV gradusda yoziladi: bizga HFOV kerak, u sensor o'lchamini ham hisobga olgan. Tekshiramiz: WILDTRACK da f ma'lum (15-bob), har bir GT box uchun "box balandligi × Z / f" — odamning metrdagi bo'yi chiqishi kerak.
import json import re import subprocess from pathlib import Path import cv2 import numpy as np import pandas as pd import torch import torchvision import matplotlib.pyplot as plt from scipy.optimize import linear_sum_assignment from ultralytics import YOLO from ultralytics.utils import LOGGER LOGGER.setLevel("ERROR") WT = Path("datasets/wildtrack/Wildtrack_dataset") qatorlar = [] for c, nom, kor in (("C1", "CVLab1", 0), ("C2", "CVLab2", 1), ("C6", "IDIAP2", 5)): fs = cv2.FileStorage(str(WT / "calibrations" / "intrinsic_zero" / f"intr_{nom}.xml"), cv2.FILE_STORAGE_READ) K = fs.getNode("camera_matrix").mat() matn = (WT / "calibrations" / "extrinsic" / f"extr_{nom}.xml").read_text() rvec, t = (np.array(re.search(f"<{teg}>(.*?)</{teg}>", matn, re.S).group(1).split(), float) for teg in ("rvec", "tvec")) R = cv2.Rodrigues(rvec)[0] boy = [] for k in range(0, 750, 5): for p in json.load(open(WT / "annotations_positions" / f"{k:08d}.json")): v = p["views"][kor] if v["xmin"] < 0 or v["ymin"] <= 0 or v["ymax"] >= 1079: # kadr chetida kesilgan box lar emas continue X = np.array([-300 + 2.5 * (p["positionID"] % 480), -900 + 2.5 * (p["positionID"] // 480), 0.0]) Z = (R @ X + t)[2] / 100 # kameragacha chuqurlik, metr boy.append((v["ymax"] - v["ymin"]) * Z / K[1, 1]) qatorlar.append({"kamera": c, "f_y, px": K[1, 1], "box lar": len(boy), "box × Z / f, m (median)": np.median(boy)}) show(pd.DataFrame(qatorlar).round(2))
| kamera | f_y, px | box lar | box × Z / f, m (median) |
|---|---|---|---|
| C1 | 1 735.16 | 2 841 | 1.83 |
| C2 | 1 719.04 | 1 691 | 1.85 |
| C6 | 1 746.01 | 2 990 | 1.81 |
Uchala kamerada ham 1.8 m atrofida — odamning bo'yi va box ning ozgina bo'sh joyi. Formula ishlaydi: kamerani, ob'ektivni va masofani bilsak, odam kadrda necha piksel bo'lishini oldindan hisoblay olamiz.
YOLO11n ga necha piksel kerak
Endi asosiy o'lchov. Oddiy yo'l — MOT17 da har bir odamning box balandligi bo'yicha recall ni hisoblash. Lekin bu aldaydi: kichik odamlar — uzoqdagi, olomondagi, xira odamlar, va biz "piksel kam" ni "sahna qiyin" dan ajrata olmaymiz. Toza tajriba: bitta kadrni bir necha marta kichraytiramiz (1, 1/2, 1/3, 1/4, 1/6) va har safar modelga o'z o'lchamida beramiz. Endi o'sha odamlar, o'sha sahna — faqat piksellar soni o'zgaradi.
7 ta videodan har 10-kadrni olamiz, ko'rinishi ≥ 0.8 va kadr ichida to'liq bo'lgan piyodalarni hisoblaymiz (to'silish ta'siri bo'lmasin). Box balandligi — modelga kiradigan rasmdagi piksellarda.
MOT = Path("datasets/MOT17/train") VIDEOLAR = ["MOT17-02", "MOT17-04", "MOT17-05", "MOT17-09", "MOT17-10", "MOT17-11", "MOT17-13"] MASSHTAB = [1, 1 / 2, 1 / 3, 1 / 4, 1 / 6] ORALIQLAR = [8, 12, 16, 24, 32, 48, 64, 96, 128, 10_000] def iou_matritsa(a, b): return torchvision.ops.box_iou(torch.tensor(np.asarray(a, float)), torch.tensor(np.asarray(b, float))).numpy() def topildi(G, D): """G ning qaysi box lari D dagi biror box bilan IoU >= 0.5 da moslandi (Venger).""" t = np.zeros(len(G), bool) if len(G) and len(D): M = iou_matritsa(G, D) r, c = linear_sum_assignment(-M) t[r[M[r, c] >= 0.5]] = True return t def kichraytirib_top(model, rasm, s, **kw): """Rasmni s marta kichraytiradi va modelga o'z o'lchamida beradi (imgsz — 32 ga karrali).""" kichik = cv2.resize(rasm, (max(1, round(rasm.shape[1] * s)), max(1, round(rasm.shape[0] * s))), interpolation=cv2.INTER_AREA) imgsz = int(np.ceil(max(kichik.shape[:2]) / 32) * 32) return model.predict(kichik, imgsz=imgsz, conf=0.25, device=0, verbose=False, **kw)[0].boxes model = YOLO("weights/yolo11n.pt") qatorlar = [] for video in VIDEOLAR: gt = np.loadtxt(MOT / f"{video}-FRCNN" / "gt" / "gt.txt", delimiter=",") gt = gt[gt[:, 7] == 1] kadr_yollari = sorted((MOT / f"{video}-FRCNN" / "img1").iterdir()) for k in range(1, len(kadr_yollari) + 1, 10): rasm = cv2.imread(str(kadr_yollari[k - 1])) H, W = rasm.shape[:2] G = gt[gt[:, 0] == k] B = np.c_[G[:, 2:4], G[:, 2:4] + G[:, 4:6]] yaxshi = (G[:, 8] >= 0.8) & (B[:, 0] >= 0) & (B[:, 1] >= 0) & (B[:, 2] <= W) & (B[:, 3] <= H) for s in MASSHTAB: t = topildi(B * s, kichraytirib_top(model, rasm, s, classes=[0]).xyxy.cpu().numpy()) qatorlar += [(video, s, h * s, x) for h, x in zip(G[yaxshi, 5], t[yaxshi])] odam = pd.DataFrame(qatorlar, columns=["video", "masshtab", "balandlik", "topildi"]) odam["oraliq, px"] = pd.cut(odam["balandlik"], ORALIQLAR, right=False) jadval = odam.groupby(["oraliq, px", "masshtab"], observed=True)["topildi"].mean().unstack() * 100 jadval.columns = [f"s = 1/{round(1 / s)}" if s < 1 else "asl" for s in jadval.columns] jadval["hammasi"] = odam.groupby("oraliq, px", observed=True)["topildi"].mean() * 100 jadval["odamlar"] = odam.groupby("oraliq, px", observed=True).size() show(jadval.round(1))
| oraliq, px | s = 1/6 | s = 1/4 | s = 1/3 | s = 1/2 | asl | hammasi | odamlar |
|---|---|---|---|---|---|---|---|
| [8, 12) | 0 | 0 | 0 | NaN | NaN | 0 | 536 |
| [12, 16) | 0 | 0.4 | 2.1 | 0 | NaN | 0.5 | 580 |
| [16, 24) | 11.2 | 9.1 | 8.1 | 7.4 | NaN | 9.5 | 1 405 |
| [24, 32) | 10.2 | 34.1 | 35.8 | 23.8 | 33.3 | 19 | 2 166 |
| [32, 48) | 63.8 | 40.5 | 61.7 | 55 | 45.4 | 52.5 | 3 454 |
| [48, 64) | 93.4 | 86.3 | 56.1 | 75.4 | 65.1 | 70.9 | 3 066 |
| [64, 96) | 99.3 | 97.3 | 96.5 | 85.1 | 72.5 | 89.8 | 3 409 |
| [96, 128) | 100 | 98.9 | 98.1 | 98.7 | 77.3 | 93.9 | 1 858 |
| [128, 10000) | 100 | 99.7 | 99.6 | 99.3 | 96.7 | 97.7 | 4 775 |
Jadvalni ikki yo'nalishda o'qiymiz.
Pastga (piksellar ko'payadi): 32 pikseldan past odamni YOLO11n deyarli ko'rmaydi, 48–64 px da ko'pini ko'radi, 64 px dan yuqorida — ishonchli. Chegara keskin: 24 px dan 96 px gacha recall 19% dan 94% gacha ko'tariladi.
Chapdan o'ngga (bir xil piksel, turli manba): kuchli kichraytirilgan rasmda (1/3, 1/4, 1/6) 64–96 px lik odam deyarli har doim topiladi, asl kadrda shu o'lchamdagi odam esa ancha kam. Sabab: asl kadrdagi kichik odam — haqiqatan uzoqdagi odam: u xira, JPEG siqish uni buzgan, olomon ichida. Kichraytirilgan yaqin odam esa tiniq. Demak "64 piksel yetadi" — ideal tasvir uchun; real uzoqdagi odam uchun zaxira qoldiring. 2025-yilgi standart identification ni ikki barobar oshirgani ham shu sabab bilan.
Kaska uchun necha piksel
"Kaskasiz odam" (7- va 14-boblar) uchun odamni topish yetmaydi — kaskaning o'zini ko'rish kerak. 7-bobda o'qitilgan Construction-PPE modeli bilan xuddi shu tajriba: test rasmlarini kichraytiramiz va kaska box ining balandligi bo'yicha recall ni o'lchaymiz. Kaska odam bo'yining qancha qismi ekanini ham o'sha belgilardan olamiz.
ppe = YOLO("out/train/ppe_coco/weights/best.pt") # 7-bob: 0 — helmet, 6 — Person qatorlar, nisbat = [], [] for yol in sorted(Path("datasets/construction-ppe/images/test").iterdir()): rasm = cv2.imread(str(yol)) H, W = rasm.shape[:2] L = np.loadtxt(Path(str(yol).replace("/images/", "/labels/")).with_suffix(".txt"), ndmin=2) B = np.c_[(L[:, 1] - L[:, 3] / 2) * W, (L[:, 2] - L[:, 4] / 2) * H, (L[:, 1] + L[:, 3] / 2) * W, (L[:, 2] + L[:, 4] / 2) * H] kaskalar, odamlar = B[L[:, 0] == 0], B[L[:, 0] == 6] for b in kaskalar: # kaska aynan bitta odamning boshida bo'lsa egasi = [o for o in odamlar if o[0] - 5 <= b[0] and b[2] <= o[2] + 5 and o[1] - 5 <= b[1] <= o[1] + 0.3 * (o[3] - o[1])] if len(egasi) == 1: nisbat.append((b[3] - b[1]) / (egasi[0][3] - egasi[0][1])) for s in MASSHTAB: r = kichraytirib_top(ppe, rasm, s) t = topildi(kaskalar * s, r.xyxy.cpu().numpy()[r.cls.cpu().numpy() == 0]) qatorlar += [(s, h * s, x) for h, x in zip(kaskalar[:, 3] - kaskalar[:, 1], t)] kaska = pd.DataFrame(qatorlar, columns=["masshtab", "balandlik", "topildi"]) kaska["oraliq, px"] = pd.cut(kaska["balandlik"], [4, 8, 12, 16, 24, 32, 48, 64, 10_000], right=False) show(kaska.groupby("oraliq, px", observed=True)["topildi"].agg(recall=lambda x: 100 * x.mean(), kaskalar="size").round(1)) print(f"kaska balandligi / odam balandligi: median {np.median(nisbat):.3f} ({len(nisbat)} juft)") _, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4.3), sharey=True) for s in MASSHTAB: q = odam[odam["masshtab"] == s].groupby("oraliq, px", observed=True)["topildi"].mean() * 100 ax1.plot([o.left for o in q.index], q.values, marker="o", ms=4, label=f"s = 1/{round(1 / s)}" if s < 1 else "asl kadr") q = kaska.groupby("oraliq, px", observed=True)["topildi"].mean() * 100 ax2.plot([o.left for o in q.index], q.values, marker="o", color="C3") for ax, nom, q in ((ax1, "odam (YOLO11n)", odam), (ax2, "kaska (7-bob modeli)", kaska)): ax.set_xscale("log", base=2) ax.minorticks_off() chegara = [o.left for o in q["oraliq, px"].cat.categories] ax.set_xticks(chegara, [str(c) for c in chegara]) ax.set_xlabel("box balandligi, modelga kiradigan rasmda, px (oraliq boshi)") ax.set_title(nom, fontsize=10) ax.axhline(90, color="gray", lw=0.7, ls="--") ax1.set_ylabel("recall, %") ax1.legend(fontsize=8) fig("piksel_recall.png", "Recall box balandligiga qarab. Chapda: bir xil kadrlar turli masshtabda — kichraytirilgan tiniq odam asl kadrdagi uzoq odamdan osonroq topiladi.")
kaska balandligi / odam balandligi: median 0.143 (141 juft)
| oraliq, px | recall | kaskalar |
|---|---|---|
| [4, 8) | 4.8 | 62 |
| [8, 12) | 29.9 | 127 |
| [12, 16) | 52.1 | 142 |
| [16, 24) | 76.2 | 193 |
| [24, 32) | 85.1 | 134 |
| [32, 48) | 88.9 | 126 |
| [48, 64) | 90.5 | 74 |
| [64, 10000) | 94 | 100 |

Recall box balandligiga qarab. Chapda: bir xil kadrlar turli masshtabda — kichraytirilgan tiniq odam asl kadrdagi uzoq odamdan osonroq topiladi.
Kaska kichik narsa: u odam bo'yining taxminan 14% i. Model kaskalarning 85% va undan ko'prog'ini 24 pikseldan boshlab topadi — bu esa odam bo'yi ~170 piksel degani. Odamni sanash uchun ~64 px yetsa, "kaskasiz" nazorati uchun odam 2.5 barobardan ko'proq katta bo'lishi kerak. Loyiha uchun bu hal qiluvchi: bitta keng burchakli kamera maydondagi odamlarni sanay oladi, lekin kaskani faqat yaqin masofada tekshiradi.
Modelning kirish o'lchami: kamera piksellari qayerda yo'qoladi
Muhim tafsilot: model kadrni imgsz ga kichraytiradi. 1920 px kenglikdagi kadr imgsz=640 da uch marta kichrayadi — kameraning har uch pikselidan bittasi qoladi. Shuning uchun detection uchun kerakli kamera piksel zichligi:
Ikkinchi formulada W qisqarib ketdi: detection masofasi kameraning ruxsatiga bog'liq emas, faqat imgsz va ko'rish burchagiga. 8 MP kamera imgsz=640 bilan 2 MP kamera imgsz=640 bilan bir xil masofada odam topadi. Katta ruxsat ikki holatda foyda beradi: imgsz ni oshirsangiz (GPU narxi — keyingi bob) yoki kadrdan to'liq ruxsatda qirqib olsangiz — masalan, odam box ini asl kadrdan kesib, yuz yoki kaska modeliga bersangiz (13-bobdagi yuzlar aynan shunday, asl kadrdan olingan).
Kalkulyator: turli ko'rish burchaklari uchun har bir vazifa qaysi masofagacha bajariladi. Odam uchun 64 px, kaska uchun 24 px (o'lchangan nisbat bilan odam bo'yiga aylantiramiz), yuz uchun 13-bobdagi 45 px kenglik (16 sm yuzda 281 px/m, asl kadrdan qirqib olinadi — shuning uchun kamera ruxsatiga bog'liq).
BOY = 1.7 # odam bo'yi, m ODAM_PX = 64 # yuqoridagi jadvaldan: ishonchli topish KASKA_PX = 24 # kaska jadvalidan KASKA_NISBAT = np.median(nisbat) YUZ_PX_M = 45 / 0.16 # 13-bob: 45 px yuz, yuz kengligi 16 sm def masofa(zichlik, W, hfov): """Kerakli piksel zichligi (px/m) shu masofagacha bajariladi, metr.""" return W / (2 * zichlik * np.tan(np.radians(hfov) / 2)) qatorlar = [] for hfov in (100, 80, 60, 40, 25): q = {"HFOV, °": hfov} for imgsz in (640, 1280): q[f"odam, imgsz {imgsz}"] = masofa(ODAM_PX / BOY, imgsz, hfov) q[f"kaska, imgsz {imgsz}"] = masofa(KASKA_PX / KASKA_NISBAT / BOY, imgsz, hfov) for nom, W in (("2 MP", 1920), ("4 MP", 2560), ("8 MP", 3840)): q[f"yuz, {nom}"] = masofa(YUZ_PX_M, W, hfov) qatorlar.append(q) show(pd.DataFrame(qatorlar).round(1))
| HFOV, ° | odam, imgsz 640 | kaska, imgsz 640 | odam, imgsz 1280 | kaska, imgsz 1280 | yuz, 2 MP | yuz, 4 MP | yuz, 8 MP |
|---|---|---|---|---|---|---|---|
| 100 | 7.1 | 2.7 | 14.3 | 5.4 | 2.9 | 3.8 | 5.7 |
| 80 | 10.1 | 3.9 | 20.3 | 7.7 | 4.1 | 5.4 | 8.1 |
| 60 | 14.7 | 5.6 | 29.4 | 11.2 | 5.9 | 7.9 | 11.8 |
| 40 | 23.4 | 8.9 | 46.7 | 17.8 | 9.4 | 12.5 | 18.8 |
| 25 | 38.3 | 14.6 | 76.7 | 29.2 | 15.4 | 20.5 | 30.8 |
Jadval — metrlarda. Undan kamera tanlash qoidalari o'z-o'zidan chiqadi:
- Keng burchak (100°) — kichik xona yoki kirish joyi: odamni 640 da ~7 metrgacha topadi, yuzni esa kamera ruxsatiga qarab 3–6 metrgacha. Omborxona yoki maydon uchun yetmaydi.
- Tor burchak (25°) — uzoqni ko'radi, lekin kam joyni: yo'lak, darvoza, uzun devor.
- Kaska nazorati odam sanashdan taxminan 2.6 barobar yaqin masofani talab qiladi.
imgsz=1280bu masofani ikki barobar uzaytiradi — narxi: modelga to'rt barobar ko'p piksel (GPU tezligini keyingi bobda o'lchaymiz). - Yuz — faqat asl ruxsatdagi kadrdan va yaqindan: kirish eshigi, turniket. 8 MP kamera bu yerda haqiqatan foyda beradi.
Tasvir sifati: tun, kodek, bitreyt
Kamera kadrni tarmoq orqali siqilgan holda yuboradi (H.264 yoki H.265) va bitreyt ni o'rnatuvchi tanlaydi. Past bitreyt — arzon disk va tarmoq, lekin detector qancha yo'qotadi? MOT17-09 (1080p, 30 fps) ni turli sozlamalar bilan qayta siqamiz va YOLO11n (1280) ni ishga tushiramiz. Tungi IQ (infraqizil) rejimining eng oddiy tomoni — rangning yo'qolishi — kulrang tasvir bilan tekshiriladi.
video = "MOT17-09" papka = MOT / f"{video}-FRCNN" gt = np.loadtxt(papka / "gt" / "gt.txt", delimiter=",") gt = gt[gt[:, 7] == 1] W, H = 1920, 1080 yaxshi = (gt[:, 8] >= 0.8) & (gt[:, 2] >= 0) & (gt[:, 3] >= 0) & (gt[:, 2] + gt[:, 4] <= W) & (gt[:, 3] + gt[:, 5] <= H) def baho(kadrlar): """kadrlar: (kadr raqami, rasm). Qaytaradi: recall, % va GT ga mos kelmagan box lar.""" tp = jami = ortiqcha = 0 for k, rasm in kadrlar: D = model.predict(rasm, classes=[0], conf=0.25, imgsz=1280, device=0, verbose=False)[0].boxes.xyxy.cpu().numpy() m = gt[:, 0] == k t = topildi(np.c_[gt[m, 2:4], gt[m, 2:4] + gt[m, 4:6]], D) tp, jami, ortiqcha = tp + t[yaxshi[m]].sum(), jami + yaxshi[m].sum(), ortiqcha + len(D) - t.sum() return 100 * tp / jami, ortiqcha def videodan(fayl, qadam): cap, k = cv2.VideoCapture(str(fayl)), 1 while True: ok, rasm = cap.read() if not ok: return yield k, rasm k += qadam yollar = sorted((papka / "img1").iterdir()) qatorlar = [] for nom, kadrlar in (("asl JPEG kadrlar", ((k, cv2.imread(str(p))) for k, p in enumerate(yollar, 1))), ("kulrang (tungi rejim kabi)", ((k, cv2.cvtColor(cv2.cvtColor(cv2.imread(str(p)), cv2.COLOR_BGR2GRAY), cv2.COLOR_GRAY2BGR)) for k, p in enumerate(yollar, 1)))): recall, ortiqcha = baho(kadrlar) qatorlar.append({"tasvir": nom, "recall, %": recall, "mos kelmagan box lar": ortiqcha}) Path("out/kamera").mkdir(parents=True, exist_ok=True) for kodek, fps, kbit in (("H.264", 30, 4000), ("H.264", 30, 2000), ("H.264", 30, 1000), ("H.264", 30, 500), ("H.264", 30, 250), ("H.264", 15, 1000), ("H.264", 15, 500), ("H.265", 30, 1000), ("H.265", 30, 500), ("H.265", 30, 250)): fayl = Path(f"out/kamera/{video}_{kodek}_{fps}fps_{kbit}k.mp4") if not fayl.exists(): kod = ["-c:v", "libx264"] if kodek == "H.264" else ["-c:v", "libx265", "-x265-params", "log-level=error"] filtr = ["-vf", r"select=not(mod(n\,2)),setpts=N/15/TB"] if fps == 15 else [] # 15 fps: har ikkinchi kadr subprocess.run(["ffmpeg", "-y", "-loglevel", "error", "-framerate", "30", "-i", str(papka / "img1" / "%06d.jpg"), *filtr, "-r", str(fps), *kod, "-preset", "medium", "-b:v", f"{kbit}k", "-maxrate", f"{kbit}k", "-bufsize", f"{2 * kbit}k", "-pix_fmt", "yuv420p", str(fayl)], check=True) recall, ortiqcha = baho(videodan(fayl, 30 // fps)) qatorlar.append({"tasvir": f"{kodek}, {fps} fps, {kbit} kbit/s", "kadrga kbit": kbit / fps, "fayl, KB": fayl.stat().st_size // 1024, "recall, %": recall, "mos kelmagan box lar": ortiqcha}) show(pd.DataFrame(qatorlar).round(1))
| tasvir | recall, % | mos kelmagan box lar | kadrga kbit | fayl, KB |
|---|---|---|---|---|
| asl JPEG kadrlar | 99 | 1 641 | NaN | NaN |
| kulrang (tungi rejim kabi) | 98.8 | 2 083 | NaN | NaN |
| H.264, 30 fps, 4000 kbit/s | 98.9 | 1 729 | 133.3 | 8 479 |
| H.264, 30 fps, 2000 kbit/s | 98.7 | 1 482 | 66.7 | 4 161 |
| H.264, 30 fps, 1000 kbit/s | 96.9 | 1 288 | 33.3 | 2 046 |
| H.264, 30 fps, 500 kbit/s | 89.1 | 1 069 | 16.7 | 1 005 |
| H.264, 30 fps, 250 kbit/s | 58.6 | 659 | 8.3 | 499 |
| H.264, 15 fps, 1000 kbit/s | 97.4 | 799 | 66.7 | 1 936 |
| H.264, 15 fps, 500 kbit/s | 95.2 | 633 | 33.3 | 954 |
| H.265, 30 fps, 1000 kbit/s | 98 | 1 378 | 33.3 | 2 156 |
| H.265, 30 fps, 500 kbit/s | 94.8 | 1 048 | 16.7 | 1 085 |
| H.265, 30 fps, 250 kbit/s | 74.3 | 733 | 8.3 | 540 |
Birinchi ikki qatorda kodek yo'q (asl kadrlar), shuning uchun "kadrga kbit" va "fayl" bo'sh (NaN).
- Bitreyt: 1080p da H.264 2 Mbit/s gacha yo'qotish deyarli yo'q, 1 Mbit/s da biroz, 500 kbit/s da sezilarli, 250 kbit/s da topilgan odamlar 59% ga tushadi. Muhimi — bitta kadrga to'g'ri keladigan bitlar: 15 fps va 500 kbit/s natijasi 30 fps va 1000 kbit/s ga yaqin (ikkalasida kadrga 33 kbit). Detection uchun 15 fps odatda yetadi (odam 1/15 soniyada ko'p siljimaydi), shuning uchun fps ni kamaytirib, bitreyt ni saqlash — yaxshi murosa.
- H.265 bir xil bitreyt da H.264 dan yaxshi: 500 kbit/s da farq ayniqsa katta. Yangi kameralar va NVR lar H.265 ni qo'llaydi — disk va tarmoqni ikki barobar tejaydi.
- Kulrang tasvir da recall deyarli o'zgarmadi, lekin mos kelmagan box lar ko'paydi. Real IQ rejim bundan qiyinroq: shovqin, IQ projektori yoritgan doira, uzoqdagi qorong'ilik. Tungi ish muhim bo'lsa, o'sha kamera bilan tunda yozilgan video ustida alohida tekshiring.
Eslatma: MOT17-09 — qo'zg'almas kamera, sahna sekin o'zgaradi, kodek unga qulay. Harakatlanuvchi kamera yoki shamolda tebranayotgan daraxtlar ko'p bitreyt talab qiladi.
Joyida tekshiruv ro'yxati
Kamera sotib olishdan oldin ob'ektga borib, quyidagilarni aniqlang:
- Vazifa va hudud. Har bir hudud uchun: sanash, kuzatish, kaska yoki yuz? Talab qilinadigan piksel zichligi shundan chiqadi (yuqoridagi jadval).
- Masofa. Kameradan hududning eng uzoq nuqtasigacha — ruletka yoki xarita bilan. Jadvaldan HFOV ni tanlang.
- O'rnatish balandligi va burchagi. 2.5–4 m: pastroq — odamlar bir-birini to'sadi va kamerani buzish oson; balandroq — odam tepadan ko'rinadi, YOLO esa asosan yon tomondan olingan rasmlarda o'qitilgan. Yuz uchun — ko'z balandligiga yaqin.
- Yorug'lik. Kamera quyoshga yoki derazaga qarab tursa (teskari yorug'lik), odam qora siluet bo'ladi. Tunda — IQ masofasi hujjatda yoziladi.
- Ko'p kamera (15-bob): muhim hududni kamida ikki kamera turli tomondan ko'rsin — to'silgan odam boshqasida ko'rinadi.
- Tarmoq va saqlash. Kameralar soni × bitreyt: masalan, 16 kamera × 2 Mbit/s = 32 Mbit/s tarmoq va bir sutkada ~350 GB disk.
- Sinov kadri. O'rnatgandan keyin bitta kadr oling, eng uzoqdagi odam necha piksel ekanini o'lchang va jadval bilan solishtiring.
Asosiy fikr
- Piksel zichligi ρ = f / Z = W / (2 Z tan(HFOV/2)). DORI (2014): 25 / 62.5 / 125 / 250 px/m; 2025 tahriri ishonchli identifikatsiya uchun 500 px/m ni tavsiya qiladi.
- YOLO11n odamni modelga kiradigan rasmda ~64 px balandlikdan boshlab ishonchli topadi (tiniq tasvirda 96% dan ko'p), 32 px dan pastda — deyarli yo'q. Real uzoq odam kichraytirilgan tiniq odamdan qiyinroq: zaxira qoldiring.
- Kaska odam bo'yining ~14% i va ~24 px talab qiladi — "kaskasiz" nazorati uchun odam sanashdagidan 2.5 barobardan ko'proq piksel kerak.
- Detection masofasini
imgszva ko'rish burchagi belgilaydi, kamera ruxsati emas; katta ruxsat asl kadrdan qirqib olinadigan vazifalar (yuz, kaska) uchun kerak. - 1080p uchun H.264 da ~2 Mbit/s (kadrga ~65 kbit) detection ni saqlaydi; H.265 shuni taxminan yarim bitreyt da beradi.
Mashqlar
- O'z kamerangizning hujjatidan HFOV ni oling va kalkulyator bilan odam, kaska va yuz masofalarini hisoblang. Keyin bitta kadrda eng uzoq odamning piksel balandligini o'lchab, hisob bilan solishtiring.
- Kaska tajribasini
imgszni ikki barobar oshirib takrorlang (kichraytirib_topda imgsz ni 2 ga ko'paytiring). Kichik kaskalar yaxshiroq topiladimi? Tiniq tasvirni kattalashtirish yangi ma'lumot qo'shmasligini qanday tushuntirasiz? - Bitreyt tajribasini harakatlanuvchi kamerali MOT17-10 da takrorlang. Bir xil bitreyt da yo'qotish kattaroqmi?
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.