Video bilan ishlash va tezlik byudjeti
Video — bu kadrlar oqimi
OpenCV da video bilan ishlash rasm bilan ishlashdan bitta narsa bilan farq qiladi: kadrlar ketma-ket keladi va ularni ulgurib qayta ishlash kerak. Shuning uchun bu bobda asosiy mavzu — tezlik.
cv2.VideoCapture fayl, kamera yoki tarmoq oqimini ochadi:
VIDEO = DATA.parent.parent / "YOLO_Tracking_Kitob" / "datasets" / "pexels" / "13921040_2560x1440.mp4" cap = cv2.VideoCapture(str(VIDEO)) print("ochildimi:", cap.isOpened()) xossalar = { "FPS": cv2.CAP_PROP_FPS, "kadrlar soni": cv2.CAP_PROP_FRAME_COUNT, "kenglik": cv2.CAP_PROP_FRAME_WIDTH, "balandlik": cv2.CAP_PROP_FRAME_HEIGHT, } show(pd.DataFrame([{"xossa": n, "qiymat": round(cap.get(p), 2)} for n, p in xossalar.items()])) fps = cap.get(cv2.CAP_PROP_FPS) n_kadr = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) print(f"video uzunligi: {n_kadr / fps:.1f} soniya | bitta kadrga byudjet: {1000 / fps:.1f} ms") cap.release()
ochildimi: True video uzunligi: 20.3 soniya | bitta kadrga byudjet: 33.4 ms
| xossa | qiymat |
|---|---|
| FPS | 29.97 |
| kadrlar soni | 607 |
| kenglik | 2 560 |
| balandlik | 1 440 |
Bitta kadrga byudjet — bu bobning eng muhim soni. 29.97 FPS video uchun u 33.4 ms. Agar sizning qayta ishlashingiz kadriga 50 ms olsa, real vaqtda ishlay olmaysiz: har soniyada 30 kadr keladi, siz esa 20 tasini ulgurasiz.
:::gotcha release() ni unutmang
VideoCapture va VideoWriter — operatsion tizim resurslari. Ularni release() qilmasangiz fayl
band bo'lib qoladi yoki kamera boshqa dasturga ochilmaydi. Eng xavfsizi — try/finally yoki
with contextlib.closing(...) ishlatish.
:::
Kadrlarni o'qish
cap = cv2.VideoCapture(str(VIDEO)) kadrlar = [] oqilgan = 0 while len(kadrlar) < 4: ok, kadr = cap.read() if not ok: # video tugadi yoki kadr buzuq break oqilgan += 1 if oqilgan % 120 == 1: # har 120-kadrni saqlaymiz kadrlar.append(cv2.resize(kadr, (480, 270))) cap.release() print(f"o'qilgan kadrlar: {oqilgan} | saqlangan: {len(kadrlar)}") ko_rsat(*kadrlar, nom="video_kadrlar.png", kenglik=15, sarlavhalar=[f"kadr {i * 120 + 1}" for i in range(len(kadrlar))], izoh="Bir videodan turli vaqtdagi kadrlar. Kamera qimirlaydi, shuning uchun sahna siljiydi.")
o'qilgan kadrlar: 361 | saqlangan: 4

Bir videodan turli vaqtdagi kadrlar. Kamera qimirlaydi, shuning uchun sahna siljiydi.
cap.read() ikkita qiymat qaytaradi: muvaffaqiyat bayrog'i va kadr. Bayroqni tekshirmaslik —
video bilan ishlashdagi eng ko'p uchraydigan xato, chunki video tugaganda kadr None bo'ladi va
keyingi cv2.cvtColor tushunarsiz xato beradi.
Kerakli kadrga sakrash
cap = cv2.VideoCapture(str(VIDEO)) qatorlar = [] for nishon in (0, 150, 300, 600): cap.set(cv2.CAP_PROP_POS_FRAMES, nishon) ok, k = cap.read() haqiqiy = int(cap.get(cv2.CAP_PROP_POS_FRAMES)) - 1 qatorlar.append({"so'ralgan kadr": nishon, "haqiqiy kadr": haqiqiy, "o'qildimi": ok, "vaqt, s": round(nishon / fps, 2)}) cap.release() show(pd.DataFrame(qatorlar))
| so'ralgan kadr | haqiqiy kadr | o'qildimi | vaqt, s |
|---|---|---|---|
| 0 | 0 | True | 0 |
| 150 | 150 | True | 5 |
| 300 | 300 | True | 10.01 |
| 600 | 600 | True | 20.02 |
Sakrash qulay, lekin sekin: kodek kalit kadrdan (keyframe) boshlab qayta dekodlaydi. Ketma-ket o'qish har doim tezroq. Agar har 10-kadr kerak bo'lsa, sakrash emas, o'qib tashlab yuborish afzal.
Tezlik byudjeti: nima qancha vaqt oladi
Endi asosiy o'lchov. Turli amallarni bitta kadrga necha millisekund olishini o'lchaymiz va ularning qaysilari 33 ms byudjetga sig'ishini ko'ramiz:
import time cap = cv2.VideoCapture(str(VIDEO)) cap.set(cv2.CAP_PROP_POS_FRAMES, 100) _, katta = cap.read() cap.release() orta = cv2.resize(katta, (1280, 720)) kichik = cv2.resize(katta, (640, 360)) orb = cv2.ORB_create(nfeatures=1000) amallar = { "resize 640x360": lambda k: cv2.resize(k, (640, 360)), "BGR -> kulrang": lambda k: cv2.cvtColor(k, cv2.COLOR_BGR2GRAY), "GaussianBlur 5x5": lambda k: cv2.GaussianBlur(k, (5, 5), 0), "Canny": lambda k: cv2.Canny(cv2.cvtColor(k, cv2.COLOR_BGR2GRAY), 50, 150), "ORB (1000 nuqta)": lambda k: orb.detectAndCompute(cv2.cvtColor(k, cv2.COLOR_BGR2GRAY), None), "bilateralFilter": lambda k: cv2.bilateralFilter(k, 9, 75, 75), } def olch(f, kadr, byudjet=0.25): """Amalni kamida 0.25 soniya davomida takrorlab, bitta chaqiruvning o'rtacha vaqtini beradi.""" f(kadr) # birinchi chaqiruv — "isitish", uni sanamaymiz n = 0 t0 = time.perf_counter() while time.perf_counter() - t0 < byudjet: f(kadr) n += 1 return (time.perf_counter() - t0) / n * 1000 qatorlar = [] for nom, f in amallar.items(): qatorlar.append({"amal": nom, "2560x1440, ms": round(olch(f, katta), 2), "1280x720, ms": round(olch(f, orta), 2), "640x360, ms": round(olch(f, kichik), 2)}) show(pd.DataFrame(qatorlar)) print("33.4 ms byudjetga sig'ishi kerak (29.97 FPS)")
33.4 ms byudjetga sig'ishi kerak (29.97 FPS)
| amal | 2560x1440, ms | 1280x720, ms | 640x360, ms |
|---|---|---|---|
| resize 640x360 | 0.08 | 0.04 | 0.01 |
| BGR -> kulrang | 0.13 | 0.04 | 0.01 |
| GaussianBlur 5x5 | 0.61 | 0.17 | 0.05 |
| Canny | 3.45 | 1.1 | 0.37 |
| ORB (1000 nuqta) | 25.6 | 8.16 | 3.33 |
| bilateralFilter | 29.18 | 8.43 | 12.81 |
Bu jadval loyiha rejalashtirishda ishlatiladigan asosiy hujjat. Undan ikkita xulosa chiqadi.
Birinchisi: o'lcham hamma narsani hal qiladi. Piksellar soni kvadratik o'sadi — 2560x1440 da 3.7 million piksel, 640x360 da atigi 230 ming, ya'ni 16 barobar kam. Har ustundagi sonlar shunga yaqin nisbatda kamayadi.
Ikkinchisi: arzon va qimmat amallar orasidagi farq ulkan. resize va cvtColor mikrosekundlar
oladi, ORB va bilateralFilter esa o'nlab millisekund. Byudjetni faqat shu ikkinchi guruh yeydi,
shuning uchun optimallashtirishni aynan shulardan boshlang.
Jadvaldagi g'alati qator
bilateralFilter qatoriga qarang: 1280x720 da 8.94 ms, lekin 640x360 da 12.67 ms. Rasm to'rt
barobar kichik, ishlash esa sekinroq. Bu xatoga o'xshaydi — tekshiramiz:
def piksel_narxi(kadr, oqimlar): cv2.setNumThreads(oqimlar) ms = olch(lambda k: cv2.bilateralFilter(k, 9, 75, 75), kadr) return ms, ms * 1e6 / (kadr.shape[0] * kadr.shape[1]) qatorlar = [] for oqimlar in (0, 1): for nom, kadr in [("2560x1440", katta), ("1280x720", orta), ("640x360", kichik)]: ms, ns = piksel_narxi(kadr, oqimlar if oqimlar else cv2.getNumberOfCPUs()) qatorlar.append({"oqimlar": cv2.getNumThreads(), "o'lcham": nom, "vaqt, ms": round(ms, 2), "ns / piksel": round(ns, 1)}) cv2.setNumThreads(cv2.getNumberOfCPUs()) show(pd.DataFrame(qatorlar))
| oqimlar | o'lcham | vaqt, ms | ns / piksel |
|---|---|---|---|
| 8 | 2560x1440 | 31.7 | 8.6 |
| 8 | 1280x720 | 8.73 | 9.5 |
| 8 | 640x360 | 12.85 | 55.8 |
| 1 | 2560x1440 | 203.69 | 55.3 |
| 1 | 1280x720 | 51.94 | 56.4 |
| 1 | 640x360 | 12.82 | 55.6 |
Javob "ns / piksel" ustunida. Bitta oqimda uchala o'lcham ham piksel boshiga bir xil vaqt oladi — taxminan 55 ns, ya'ni hammasi kutilganidek chiziqli. Ko'p oqimda esa katta rasmlar 8–9 ns ga tushadi, 640x360 esa 55 ns da qolib ketadi.
Sababi: OpenCV ishni oqimlarga bo'lishdan oldin rasm yetarlicha kattami, deb tekshiradi. Kichik rasmda oqimlarni ishga tushirish xarajati foydadan ko'p, shuning uchun u bitta oqimda bajariladi. Natijada rasmni kichraytirish kutilgan tezlanishni bermaydi.
Saboq: "kichikroq — tezroq" ni tekshirmasdan qabul qilmang. Ko'p yadroli protsessorda kutilmagan chegaralar bor va ularni faqat o'lchov ko'rsatadi.
Endi to'liq quvurni yig'amiz va byudjetga sig'ishini tekshiramiz:
QUVUR = ("BGR -> kulrang", "GaussianBlur 5x5", "Canny", "ORB (1000 nuqta)") qatorlar = [] for nom, kadr in [("2560x1440", katta), ("1280x720", orta), ("640x360", kichik)]: jami = sum(olch(amallar[a], kadr) for a in QUVUR) qatorlar.append({"o'lcham": nom, "piksellar": f"{kadr.shape[0] * kadr.shape[1] / 1e6:.2f} mln", "quvur, ms": round(jami, 1), "mumkin bo'lgan FPS": round(1000 / jami), "33.4 ms ga sig'adimi": "ha" if jami < 33.4 else "yo'q"}) show(pd.DataFrame(qatorlar))
| o'lcham | piksellar | quvur, ms | mumkin bo'lgan FPS | 33.4 ms ga sig'adimi |
|---|---|---|---|---|
| 2560x1440 | 3.69 mln | 29.6 | 34 | ha |
| 1280x720 | 0.92 mln | 9.5 | 105 | ha |
| 640x360 | 0.23 mln | 3.9 | 257 | ha |
Butun videoni qayta ishlash
Endi haqiqiy o'lchov: videoning bir qismini boshdan-oxir o'qib, real tezlikni o'lchaymiz.
def video_quvur(yol, n_kadr=120, kichraytir=None, ish=None): """Videoni o'qiydi, har kadrga `ish` ni qo'llaydi va vaqt taqsimotini qaytaradi.""" cap = cv2.VideoCapture(str(yol)) o_vaqt = i_vaqt = 0.0 n = 0 while n < n_kadr: t0 = time.perf_counter() ok, kadr = cap.read() t1 = time.perf_counter() if not ok: break if kichraytir: kadr = cv2.resize(kadr, kichraytir) if ish: ish(kadr) o_vaqt += t1 - t0 i_vaqt += time.perf_counter() - t1 n += 1 cap.release() return n, o_vaqt * 1000 / max(n, 1), i_vaqt * 1000 / max(n, 1) qatorlar = [] for nom, kich, ish in [ ("faqat o'qish", None, None), ("o'qish + resize 640", (640, 360), None), ("o'qish + resize + Canny", (640, 360), lambda k: cv2.Canny(cv2.cvtColor(k, cv2.COLOR_BGR2GRAY), 50, 150)), ("o'qish + resize + ORB", (640, 360), lambda k: orb.detectAndCompute(cv2.cvtColor(k, cv2.COLOR_BGR2GRAY), None)), ]: n, o_ms, i_ms = video_quvur(VIDEO, 120, kich, ish) qatorlar.append({"quvur": nom, "o'qish, ms": round(o_ms, 1), "ishlov, ms": round(i_ms, 1), "jami, ms": round(o_ms + i_ms, 1), "FPS": round(1000 / (o_ms + i_ms))}) show(pd.DataFrame(qatorlar))
| quvur | o'qish, ms | ishlov, ms | jami, ms | FPS |
|---|---|---|---|---|
| faqat o'qish | 2.7 | 0 | 2.7 | 372 |
| o'qish + resize 640 | 2.5 | 0.2 | 2.7 | 366 |
| o'qish + resize + Canny | 2.2 | 0.8 | 3.1 | 325 |
| o'qish + resize + ORB | 2.3 | 3.3 | 5.6 | 177 |
Ikki narsaga e'tibor bering.
Birinchidan, o'qishning o'zi ham vaqt oladi — kadriga 2.5 ms. Bu ko'p emas, lekin u sizning ishlov byudjetingizdan oldin sarflanadi va siz uni kamaytira olmaysiz. "Mening algoritmim 3.3 ms" degani "kadriga 3.3 ms" degani emas: jami 5.8 ms.
Ikkinchidan, oldingi jadval bilan solishtiring. U yerda to'liq o'lchamda quvur 30.4 ms edi va byudjetga
zo'rg'a sig'gandi. Bu yerda esa kadr avval 640x360 ga kichraytirilgani uchun jami 5.8 ms — 173 FPS.
Bitta resize butun masalani hal qildi.
Kamera bilan ishlaganda dekodlashni ham kamaytirish mumkin: kamerani pastroq rezolyutsiyada sozlang
(cap.set(cv2.CAP_PROP_FRAME_WIDTH, ...)), shunda u katta kadrni umuman yubormaydi. Qolgan yo'llar —
o'qishni alohida oqimda bajarish yoki kadrlarni tashlab yuborish.
Kadr tashlab yuborish
Agar ulgurmasangiz, eng oddiy yechim — har N-kadrni qayta ishlash:
qatorlar = [] for har in (1, 2, 3, 5): cap = cv2.VideoCapture(str(VIDEO)) t0 = time.perf_counter() n = ishlangan = 0 while n < 120: ok, kadr = cap.read() if not ok: break n += 1 if n % har == 0: k = cv2.resize(kadr, (640, 360)) orb.detectAndCompute(cv2.cvtColor(k, cv2.COLOR_BGR2GRAY), None) ishlangan += 1 dt = time.perf_counter() - t0 cap.release() qatorlar.append({"har N-kadr": har, "o'qilgan": n, "ishlangan": ishlangan, "jami vaqt, s": round(dt, 2), "kadr/s": round(n / dt, 1), "haqiqiy tahlil FPS": round(ishlangan / dt, 1)}) show(pd.DataFrame(qatorlar))
| har N-kadr | o'qilgan | ishlangan | jami vaqt, s | kadr/s | haqiqiy tahlil FPS |
|---|---|---|---|---|---|
| 1 | 120 | 120 | 0.67 | 177.8 | 177.8 |
| 2 | 120 | 60 | 0.48 | 248.3 | 124.1 |
| 3 | 120 | 40 | 0.41 | 290.2 | 96.7 |
| 5 | 120 | 24 | 0.37 | 323.6 | 64.7 |
Jadval bu usulning chegarasini aniq ko'rsatadi. Har 5-kadrni ishlaganda ishlov besh barobar kamaydi, lekin umumiy tezlik 174.8 dan atigi 297.8 kadr/s ga chiqdi — ikki barobar ham emas.
Sababi: kadr tashlab yuborish dekodlash vaqtini kamaytirmaydi, kadrni baribir o'qish kerak. U faqat ishlov vaqtini tejaydi. Bizning quvurda dekodlash 2.5 ms, ishlov esa 3.3 ms edi — demak eng yaxshi holatda ham 2.5 ms dan pastga tusha olmaymiz.
Qoida: kadr tashlashdan oldin qaysi qism byudjetni yeyayotganini o'lchang. Dekodlash bo'yin bo'lsa, bu usul deyarli yordam bermaydi.
Video yozish
chiqish = DATA / "natija.mp4" cap = cv2.VideoCapture(str(VIDEO)) fourcc = cv2.VideoWriter_fourcc(*"mp4v") yozuvchi = cv2.VideoWriter(str(chiqish), fourcc, fps, (640, 360)) print("yozuvchi ochildimi:", yozuvchi.isOpened()) n = 0 while n < 60: ok, kadr = cap.read() if not ok: break k = cv2.resize(kadr, (640, 360)) chekka = cv2.Canny(cv2.cvtColor(k, cv2.COLOR_BGR2GRAY), 50, 150) yozuvchi.write(cv2.cvtColor(chekka, cv2.COLOR_GRAY2BGR)) # yozish uchun 3 kanal kerak n += 1 cap.release() yozuvchi.release() print(f"yozilgan kadrlar: {n} | fayl hajmi: {chiqish.stat().st_size / 1024:.0f} KB") chiqish.unlink()
yozuvchi ochildimi: True yozilgan kadrlar: 60 | fayl hajmi: 4107 KB
:::gotcha VideoWriter jim ravishda ishlamay qo'yadi
VideoWriter xato bermaydi — u shunchaki bo'sh yoki 0 baytli fayl yozadi. Uch sabab bo'ladi:
(1) isOpened() False (kodek yo'q), (2) yozilayotgan kadr o'lchami VideoWriter ga aytilgan
o'lchamdan farq qiladi, (3) kadr uch kanalli emas. Har doim isOpened() ni tekshiring va
kadr o'lchamini aniq bir xil qiling.
:::
Kodek tanlash: mp4v — hamma joyda ishlaydigan xavfsiz tanlov; XVID — .avi uchun; H264/avc1 —
kichikroq fayl, lekin tizimda kodek bo'lishi kerak.
Kamera bilan ishlash
Kitobdagi kod fayl bilan ishlaydi, lekin kamera uchun farq faqat birinchi qatorda:
cap = cv2.VideoCapture(0) # 0 — birinchi kamera cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640) # kamerani sozlash — dekodlashni tejaydi cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 360) cap.set(cv2.CAP_PROP_BUFFERSIZE, 1) # eski kadrlar to'planib qolmasin while True: ok, kadr = cap.read() if not ok: break cv2.imshow("kamera", kadr) if cv2.waitKey(1) & 0xFF == ord("q"): # 1 ms kutadi; q bosilsa chiqadi break cap.release() cv2.destroyAllWindows()
Uchta muhim nuqta:
cv2.waitKey(1)shart — usiz oyna yangilanmaydi va dastur qotib qoladi.& 0xFF— turli platformalardawaitKeyqaytargan sonning yuqori bitlari farq qiladi.- Kamerani kerakli rezolyutsiyada sozlash yuqoridagi jadvaldagi eng katta tejamkorlik.
cv2.imshow faqat oynali muhitda ishlaydi. Jupyter daftarda uning o'rniga matplotlib ishlatiladi —
bizning ko_rsat funksiyamiz aynan shuni qiladi.
Xulosa
| Tushuncha | Asosiy fikr |
|---|---|
| Kadr byudjeti | 1000 / FPS ms; 29.97 FPS uchun 33.4 ms |
cap.read() |
Har doim ok bayrog'ini tekshiring |
CAP_PROP_POS_FRAMES |
Sakrash mumkin, lekin sekin — ketma-ket o'qish afzal |
| Rezolyutsiya | Eng katta tejamkorlik: quvur 30.4 ms dan 3.8 ms ga tushdi |
| Ko'p oqim | Kichik rasm parallellashmaydi: 640x360 da 55 ns/piksel, kattalarida 8 ns |
| Dekodlash | O'qishning o'zi kadriga 2.5 ms — uni ham byudjetga qo'shing |
| Kadr tashlash | Ishlov vaqtini tejaydi, dekodlash vaqtini emas (174 → 298 kadr/s) |
VideoWriter |
isOpened() ni tekshiring; kadr o'lchami va kanallar soni aniq mos kelsin |
| Kamera | VideoCapture(0), rezolyutsiyani sozlang, waitKey(1) shart |
Talaba ishi
- Tezlik jadvalini o'z kompyuteringizda qayta o'lchang. Qaysi amal sizda eng qimmat va bu kitobdagi tartibga mos keladimi?
video_quvurga o'qishni alohida oqimda (threading) bajaradigan variant yozing. FPS qancha oshdi?- Videoni 10 ta teng bo'lakka bo'lib, har bo'lakdan bitta kadr oling va ularni bitta jadval rasm (montaj) qilib chizing.
VideoWriterga noto'g'ri o'lchamli kadr bering (masalan 640x360 o'rniga 640x361). Xato chiqdimi yoki fayl jim ravishda buzildimi? Bu nimani o'rgatadi?
Bu bobni bepul o'qidingiz.
Kitob o'rgatadi, lekin sizning kodingiz nega ishlamaganini ayta olmaydi. Kursda uy vazifangiz tekshiriladi.