From 1f0679825f94a830d70b2990276ede42d3a858d9 Mon Sep 17 00:00:00 2001 From: Askill Date: Tue, 29 Dec 2020 10:54:52 +0100 Subject: [PATCH] initial --- README.md | 44 ++++++++- VideoReader.py | 132 +++++++++++++++++++++++++ __pycache__/VideoReader.cpython-37.pyc | Bin 0 -> 4129 bytes test.py | 14 +++ 4 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 VideoReader.py create mode 100644 __pycache__/VideoReader.cpython-37.pyc create mode 100644 test.py diff --git a/README.md b/README.md index bee2f84..9aa6891 100644 --- a/README.md +++ b/README.md @@ -1 +1,43 @@ -CV2 Threaded Video Capture +# CV2 Threaded Video Capture + +Not much to say here, it enables you to read an entire video or a number of frames from a video in an extra thread with some nice syntax. + +This project was initially part of my video synopsis project, wich is why the config dict() is required. + +### Full video +```python +from VideoReader import VideoReader +import os + +fileName = "out.mp4" +dirName = os.path.join(os.path.dirname(__file__), "generate test footage") + +config = {} +config["inputPath"] = os.path.join(dirName, fileName) +config["videoBufferLength"] = 100 + +with VideoReader(config) as reader: + while not reader.videoEnded(): + framenumber, frame = reader.pop() + print(framenumber) +``` + +### Selection of Frames +```python +from VideoReader import VideoReader +import os + +fileName = "out.mp4" +dirName = os.path.join(os.path.dirname(__file__), "generate test footage") + +config = {} +config["inputPath"] = os.path.join(dirName, fileName) +config["videoBufferLength"] = 100 + +frameList = list(range(100, 500)) + +with VideoReader(config, frameList) as reader: + while not reader.videoEnded(): + framenumber, frame = reader.pop() + print(framenumber) +``` \ No newline at end of file diff --git a/VideoReader.py b/VideoReader.py new file mode 100644 index 0000000..ae77f69 --- /dev/null +++ b/VideoReader.py @@ -0,0 +1,132 @@ +from queue import Queue + +import cv2 +import threading +import os + +class VideoReader: + listOfFrames = None + w = None + h = None + + def __init__(self, config, setOfFrames=None): + videoPath = config["inputPath"] + self.videoPath = videoPath + + if videoPath is None: + raise Exception("VideoReader:: Video reader needs a videoPath!") + if not os.path.exists(videoPath): + raise Exception("VideoReader:: Provided video path does not exist") + + self.lastFrame = 0 + # buffer data struct: + # buffer = Queue([(frameNumber, frame), ]) + self.buffer = Queue(config["videoBufferLength"]) + self.vc = cv2.VideoCapture(videoPath) + self.stopped = False + self.getWH() + self.calcFPS() + self.calcLength() + self.calcStartTime() + if setOfFrames is not None: + self.listOfFrames = sorted(setOfFrames) + + def __enter__(self): + self.fillBuffer() + return self + + def __exit__(self, type, value, traceback): + self.stop() + + def stop(self): + self.thread.join() + self.vc.release() + + def pop(self): + return self.buffer.get(block=True) + + def fillBuffer(self, listOfFrames=None): + self.endFrame = int(self.vc.get(cv2.CAP_PROP_FRAME_COUNT)) + if listOfFrames is not None: + self.listOfFrames = listOfFrames + + if self.listOfFrames is not None: + self.thread = threading.Thread( + target=self.readFramesByList, args=()) + else: + self.thread = threading.Thread(target=self.readFrames, args=()) + self.thread.start() + + def readFrames(self): + '''Reads video from start to finish''' + while self.lastFrame < self.endFrame: + res, frame = self.vc.read() + if res: + self.buffer.put((self.lastFrame, frame)) + self.lastFrame += 1 + + self.stopped = True + + def readFramesByList(self): + '''Reads all frames from a list of frame numbers''' + self.vc.set(1, self.listOfFrames[0]) + self.lastFrame = self.listOfFrames[0] + self.endFrame = self.listOfFrames[-1] + + while self.lastFrame < self.endFrame: + if self.lastFrame in self.listOfFrames: + res, frame = self.vc.read() + if res: + self.buffer.put((self.lastFrame, frame)) + else: + print("READING FRAMES IS FALSE") + # since the list is sorted the first element is always the lowest relevant framenumber + # [0,1,2,3,32,33,34,35,67,68,69] + self.listOfFrames.pop(0) + self.lastFrame += 1 + else: + # if current Frame number is not in list of Frames, we can skip a few frames + self.vc.set(1, self.listOfFrames[0]) + self.lastFrame = self.listOfFrames[0] + + self.stopped = True + + def videoEnded(self): + if self.stopped and self.buffer.empty(): + return True + else: + return False + + def calcFPS(self): + self.fps = self.vc.get(cv2.CAP_PROP_FPS) + + def getFPS(self): + if self.fps is None: + self.calcFPS() + return self.fps + + def calcLength(self): + fc = int(self.vc.get(cv2.CAP_PROP_FRAME_COUNT)) + self.length = fc / self.getFPS() + + def getLength(self): + if self.length is None: + self.calcLength() + return self.length + + def calcStartTime(self): + starttime = os.stat(self.videoPath).st_mtime + length = self.getLength() + starttime = starttime - length + self.starttime = starttime + + def getStartTime(self): + return self.starttime + + def getWH(self): + '''get width and height''' + if self.w is None or self.h is None: + res, image = self.vc.read() + self.w = image.shape[1] + self.h = image.shape[0] + return (self.w, self.h) diff --git a/__pycache__/VideoReader.cpython-37.pyc b/__pycache__/VideoReader.cpython-37.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cce826869d94907ac74afc99c4d06832ae7631e3 GIT binary patch literal 4129 zcmai1-EJGl6`q;>;gX{MXj+kCCtbTiTDG+ts7(tvX&OtBk^nBPT51dsMz?FuQX)l> z)a+8RS-Kas(Tf5t`T)J4E_2%l=!48{uiQr{3bfyuUH(FDNz9y?*|RfezH{cAvpdVn z1%{vf?|%j#tugiw>WqID8lR%XKS3p#@ErqmR@tP}b>AYgK zoOERl(vzOdL*`{cE?NO&N6`MWKvTwN94P&qks4E=v4o zR6{QL7JFe0tsx885YU`@>fMA(@sj_ZC!%GI?4fnZ<86#tG$w6mI*E%noh|l~qnAs( zQJ%EWE=U*k(vXdcmy9#W{m)UKbctP_ny_GUefG#wCKj#rkS*BW{7pZ9mdJN3eO@XXg2q$ znMhmpi|ty`ShO4VlD>+xxTxz~oOFA=NNVReN*;fqbM>%Y-#<9i1!|t$*mCZS4> znrD%A<(=5cK)jwTP43sb&fY7^pLAJ*?Z>8Wlj zsweT2-LJO&qZ5On%mB^Idtz)&1Lkh;x%Psf*=Z(0P{bs$kBV`RyTU_V6m78}~-oG}wRpvlRY7AQfAKM{>s)gz+9r|oY2v?3HaOt!5`Nz$$X0T1KVI zyY~-*gGUb!g8fJLf3X+rKKxbnNPAI7rYHS6DdkhwG&{%IO^=jbql1;6&zG};YMtt&`PWn-`l2c1)s2@;eBG)DyvM2(_t5rOySm}wgR1|Bc zVThED#q`W80^?(pnBtF#6}|#6O1vcI!I*+P2Zn=pOq~544cg`i@l7yDvBfS0Z8vRW zi?{^Qwze=9*w6le9*x)l?r&LJoVw~SXtR-X$tEDSG2)JLMkfQY<0T&Q3Iit~&&w&a zNEZyW*7;pC)BmvN{4T(UbLd*7>v3==1wU-JeS=jD0S|;e-3@-Xk&gPE{@K$=#cCUS zHn-gC|KK-x>(So*&%UgF?whba^uIjx_wPSA+=H{0QWsgcMU;uksqcP7lMwdm$EdW^ zQ*>m$LjyA&R3Fj+rCy5x{X>lHqr^8+F;T{~QG^$Z#*by;Sw+5Pl@Z>9o3p_^^=EpP zCSIMpNN7$%QI%1HK`zmv6o|69?BpNqBgn|oH1wS4td~501BDeXD4*EtAaD3J-iYZU zWqgg>3&A#3n=^|}p9!`y8mV(BhccOwJ!W-hfo^KWDcv9wuo}JiYU|WGW|b&og1t72 zSC&c4*+WEY>>1xQCci7;<{jTSly%|$6N*llS>WomwF}ZjqH;{jr>GR@;w{aZkg7~9 zn4|gk@ZuuPzu-jfbd+w;kpsb}*5uUp$QmMzDJ!e*Q}<`6wA(g$TE?O^(Wo!RTnTDz zlya5Vh|Q%c6Rn9I6hf0Q`Yt*NoP|&V zf3z$B#zyufBkCTCO_l-}IAh>k<>3uvhDn&1Bv5;C5}YMSBh}C8N#1ZZ^)upCI~Z1G zHw(0T4G;P#F-^&ESSimNY|Z+6E>E58fRLnO0*^LM2O)JVH_N>NvLe7Z9A~}1#e3s0 z-<(}@R%x;%0XB*C{cK6ePrcaC0=3jV0+I5ojJ8F~rYit($A;8SX`i4bgL#8K2kk)_ z>-k?dB|aX)j`UBW=J82Vof2!s(fn)8Ptsr|s>Bt4680we5-n<{c@`c=i$O{k{O?d= z^Gyq4BvLH$!TQ2}AAe!e<7#DtvhohTHG)7Fg5azx`)z6$gW$X$w$mpmeSd~|)gD!E zQ$;kXZc#<~VM^YVXqd}Y5ksqws3L|jXUxR5i5W$Kpvaku44&D92^dPt72%&liQh-% z;d;hj6gE8Dv#1n`UcuY+T=X3D9cr0(Dz0|U=|kImJelN6Q3h$k*l1j}oc2CWxBdx* NnTgM$`7ZIqe*vjle!Tzy literal 0 HcmV?d00001 diff --git a/test.py b/test.py new file mode 100644 index 0000000..2b1f282 --- /dev/null +++ b/test.py @@ -0,0 +1,14 @@ +from VideoReader import VideoReader +import os + +fileName = "out.mp4" +dirName = os.path.join(os.path.dirname(__file__), "generate test footage") + +config = {} +config["inputPath"] = os.path.join(dirName, fileName) +config["videoBufferLength"] = 100 + +with VideoReader(config) as reader: + while not reader.videoEnded(): + framenumber, frame = reader.pop() + print(framenumber)