Asosiy qismga o'tish

Video bilan ishlash va tezlik byudjeti

14-bob 10 kod bloki 2991 so'z

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:

python12 qator
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()
Chiqish
ochildimi: True
video uzunligi: 20.3 soniya | bitta kadrga byudjet: 33.4 ms
xossaqiymat
FPS29.97
kadrlar soni607
kenglik2 560
balandlik1 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

python15 qator
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.")
Chiqish
o'qilgan kadrlar: 361 | saqlangan: 4

Bir videodan turli vaqtdagi kadrlar. Kamera qimirlaydi, shuning uchun sahna siljiydi.

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

python10 qator
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 kadrhaqiqiy kadro'qildimivaqt, s
00True0
150150True5
300300True10.01
600600True20.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:

python36 qator
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)")
Chiqish
33.4 ms byudjetga sig'ishi kerak (29.97 FPS)
amal2560x1440, ms1280x720, ms640x360, ms
resize 640x3600.080.040.01
BGR -> kulrang0.130.040.01
GaussianBlur 5x50.610.170.05
Canny3.451.10.37
ORB (1000 nuqta)25.68.163.33
bilateralFilter29.188.4312.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:

python13 qator
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))
oqimlaro'lchamvaqt, msns / piksel
82560x144031.78.6
81280x7208.739.5
8640x36012.8555.8
12560x1440203.6955.3
11280x72051.9456.4
1640x36012.8255.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:

python8 qator
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'lchampiksellarquvur, msmumkin bo'lgan FPS33.4 ms ga sig'adimi
2560x14403.69 mln29.634ha
1280x7200.92 mln9.5105ha
640x3600.23 mln3.9257ha

Butun videoni qayta ishlash

Endi haqiqiy o'lchov: videoning bir qismini boshdan-oxir o'qib, real tezlikni o'lchaymiz.

python32 qator
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))
quvuro'qish, msishlov, msjami, msFPS
faqat o'qish2.702.7372
o'qish + resize 6402.50.22.7366
o'qish + resize + Canny2.20.83.1325
o'qish + resize + ORB2.33.35.6177

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:

python20 qator
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-kadro'qilganishlanganjami vaqt, skadr/shaqiqiy tahlil FPS
11201200.67177.8177.8
2120600.48248.3124.1
3120400.41290.296.7
5120240.37323.664.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

python18 qator
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()
Chiqish
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:

python14 qator
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 platformalarda waitKey qaytargan 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

  1. Tezlik jadvalini o'z kompyuteringizda qayta o'lchang. Qaysi amal sizda eng qimmat va bu kitobdagi tartibga mos keladimi?
  2. video_quvur ga o'qishni alohida oqimda (threading) bajaradigan variant yozing. FPS qancha oshdi?
  3. Videoni 10 ta teng bo'lakka bo'lib, har bo'lakdan bitta kadr oling va ularni bitta jadval rasm (montaj) qilib chizing.
  4. VideoWriter ga 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.

Telegramda yozish Narxlar