multithreaded contour extractor
This commit is contained in:
parent
f52feb8f5f
commit
c187b8ce9f
|
|
@ -6,3 +6,5 @@ generate test footage/3.MP4
|
||||||
short.mp4
|
short.mp4
|
||||||
|
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|
||||||
|
*.mp4
|
||||||
|
|
|
||||||
|
|
@ -9,67 +9,75 @@ import traceback
|
||||||
import _thread
|
import _thread
|
||||||
import imageio
|
import imageio
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import time
|
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from multiprocessing import Queue, Process, Pool
|
from multiprocessing import Queue, Process, Pool
|
||||||
from multiprocessing.pool import ThreadPool
|
from multiprocessing.pool import ThreadPool
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
from VideoReader import VideoReader
|
from VideoReader import VideoReader
|
||||||
|
from queue import Queue
|
||||||
|
import threading
|
||||||
|
from multiprocessing.pool import ThreadPool
|
||||||
|
|
||||||
class ContourExtractor:
|
class ContourExtractor:
|
||||||
|
|
||||||
#X = {frame_number: [(contour, (x,y,w,h)), ...], }
|
#X = {frame_number: [(contour, (x,y,w,h)), ...], }
|
||||||
extractedContours = dict()
|
|
||||||
min_area = 100
|
|
||||||
max_area = 1000
|
|
||||||
threashold = 13
|
|
||||||
xDim = 0
|
|
||||||
yDim = 0
|
|
||||||
|
|
||||||
def getextractedContours(self):
|
def getextractedContours(self):
|
||||||
return self.extractedContours
|
return self.extractedContours
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.frameBuffer = Queue(16)
|
||||||
|
self.extractedContours = dict()
|
||||||
|
self.min_area = 30
|
||||||
|
self.max_area = 1000
|
||||||
|
self.threashold = 13
|
||||||
|
self.xDim = 0
|
||||||
|
self.yDim = 0
|
||||||
|
|
||||||
print("ContourExtractor initiated")
|
print("ContourExtractor initiated")
|
||||||
|
|
||||||
def extractContours(self, videoPath, resizeWidth):
|
def extractContours(self, videoPath, resizeWidth):
|
||||||
firstFrame = None
|
|
||||||
extractedContours = dict()
|
extractedContours = dict()
|
||||||
videoReader = VideoReader(videoPath)
|
videoReader = VideoReader(videoPath)
|
||||||
self.xDim = videoReader.w
|
self.xDim = videoReader.w
|
||||||
self.yDim = videoReader.h
|
self.yDim = videoReader.h
|
||||||
|
self.resizeWidth = resizeWidth
|
||||||
videoReader.fillBuffer()
|
videoReader.fillBuffer()
|
||||||
|
frameCount, frame = videoReader.pop()
|
||||||
|
|
||||||
|
#init compare image
|
||||||
|
frame = imutils.resize(frame, width=resizeWidth)
|
||||||
|
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||||
|
#gray = np.asarray(gray[:,:,1]/2 + gray[:,:,2]/2).astype(np.uint8)
|
||||||
|
gray = cv2.GaussianBlur(gray, (5, 5), 0)
|
||||||
|
self.firstFrame = gray
|
||||||
|
|
||||||
while not videoReader.videoEnded():
|
threads = 16
|
||||||
frameCount, frame = videoReader.pop()
|
start = time.time()
|
||||||
if frameCount % (60*30) == 0:
|
with ThreadPool(threads) as pool:
|
||||||
print("Minutes processed: ", frameCount/(60*30))
|
while not videoReader.videoEnded():
|
||||||
|
#FrameCount, frame = videoReader.pop()
|
||||||
if frame is None:
|
if frameCount % (60*30) == 0:
|
||||||
print("ContourExtractor: frame was None")
|
print(f"Minutes processed: {frameCount/(60*30)} in {round((time.time() - start), 2)} each")
|
||||||
continue
|
start = time.time()
|
||||||
|
|
||||||
# resize the frame, convert it to grayscale, and blur it
|
if videoReader.buffer.qsize() == 0:
|
||||||
frame = imutils.resize(frame, width=resizeWidth)
|
time.sleep(1)
|
||||||
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
|
||||||
|
|
||||||
# if the first frame is None, initialize it
|
tmpData = [videoReader.pop() for i in range(0, videoReader.buffer.qsize())]
|
||||||
if firstFrame is None:
|
frameCount = tmpData[-1][0]
|
||||||
#gray = np.asarray(gray[:,:,1]/2 + gray[:,:,2]/2).astype(np.uint8)
|
pool.map(self.getContours, tmpData)
|
||||||
gray = cv2.GaussianBlur(gray, (5, 5), 0)
|
|
||||||
firstFrame = gray
|
|
||||||
continue
|
|
||||||
x = self.getContours(gray, firstFrame)
|
|
||||||
if x is not None:
|
|
||||||
extractedContours[frameCount] = x
|
|
||||||
|
|
||||||
print("done")
|
|
||||||
videoReader.thread.join()
|
videoReader.thread.join()
|
||||||
self.extractedContours = extractedContours
|
|
||||||
return extractedContours
|
return self.extractedContours
|
||||||
|
|
||||||
def getContours(self, gray, firstFrame):
|
def getContours(self, data):
|
||||||
|
frameCount, frame = data
|
||||||
|
firstFrame = self.firstFrame
|
||||||
|
frame = imutils.resize(frame, width=self.resizeWidth)
|
||||||
|
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
||||||
gray = cv2.GaussianBlur(gray, (5, 5), 0)
|
gray = cv2.GaussianBlur(gray, (5, 5), 0)
|
||||||
frameDelta = cv2.absdiff(gray, firstFrame)
|
frameDelta = cv2.absdiff(gray, firstFrame)
|
||||||
thresh = cv2.threshold(frameDelta, self.threashold, 255, cv2.THRESH_BINARY)[1]
|
thresh = cv2.threshold(frameDelta, self.threashold, 255, cv2.THRESH_BINARY)[1]
|
||||||
|
|
@ -84,11 +92,17 @@ class ContourExtractor:
|
||||||
if ca < self.min_area or ca > self.max_area:
|
if ca < self.min_area or ca > self.max_area:
|
||||||
continue
|
continue
|
||||||
(x, y, w, h) = cv2.boundingRect(c)
|
(x, y, w, h) = cv2.boundingRect(c)
|
||||||
#print((x, y, w, h))
|
|
||||||
contours.append((x, y, w, h))
|
|
||||||
|
|
||||||
|
contours.append((x, y, w, h))
|
||||||
|
|
||||||
if len(contours) != 0 and contours is not None:
|
if len(contours) != 0 and contours is not None:
|
||||||
return contours
|
# this should be thread safe
|
||||||
|
self.extractedContours[frameCount] = contours
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def displayContours(self):
|
def displayContours(self):
|
||||||
|
|
|
||||||
57
Exporter.py
57
Exporter.py
|
|
@ -19,36 +19,47 @@ class Exporter:
|
||||||
writer.append_data(np.array(frame))
|
writer.append_data(np.array(frame))
|
||||||
writer.close()
|
writer.close()
|
||||||
|
|
||||||
def exportLayers(self, layers, outputPath, resizeWidth):
|
def exportLayers(self, layers, footagePath, outputPath, resizeWidth):
|
||||||
|
|
||||||
|
listOfFrames = self.makeListOfFrames(layers)
|
||||||
|
videoReader = VideoReader(footagePath, listOfFrames)
|
||||||
|
videoReader.fillBuffer()
|
||||||
|
maxLength = self.getMaxLengthOfLayers(layers)
|
||||||
underlay = cv2.VideoCapture(footagePath).read()[1]
|
underlay = cv2.VideoCapture(footagePath).read()[1]
|
||||||
|
underlay = cv2.cvtColor(underlay, cv2.COLOR_BGR2RGB)
|
||||||
|
frames = [underlay]*maxLength
|
||||||
|
exportFrame = 0
|
||||||
|
|
||||||
fps = self.fps
|
fps = self.fps
|
||||||
writer = imageio.get_writer(outputPath, fps=fps)
|
writer = imageio.get_writer(outputPath, fps=fps)
|
||||||
i = 0
|
while not videoReader.videoEnded():
|
||||||
for layer in layers:
|
frameCount, frame = videoReader.pop()
|
||||||
data = layer.data
|
if frameCount % (60*self.fps) == 0:
|
||||||
contours = layer.bounds
|
print("Minutes processed: ", frameCount/(60*self.fps))
|
||||||
if len(data) < 10:
|
if frame is None:
|
||||||
|
print("ContourExtractor: frame was None")
|
||||||
continue
|
continue
|
||||||
for frame, contour in zip(data, contours):
|
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||||
(x, y, w, h) = contour
|
|
||||||
frame = frame
|
|
||||||
frame1 = underlay
|
for layer in layers:
|
||||||
frame1 = imutils.resize(frame1, width=resizeWidth)
|
if layer.startFrame <= frameCount and layer.startFrame + len(layer.bounds) > frameCount:
|
||||||
frame1 = cv2.cvtColor(frame1, cv2.COLOR_BGR2RGB)
|
(x, y, w, h) = layer.bounds[frameCount - layer.startFrame]
|
||||||
frame1[y:y+frame.shape[0], x:x+frame.shape[1]] = frame
|
factor = videoReader.w / resizeWidth
|
||||||
cv2.putText(frame1, str(i), (30, 30),
|
x = int(x * factor)
|
||||||
cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2)
|
y = int(y * factor)
|
||||||
writer.append_data(np.array(frame1))
|
w = int(w * factor)
|
||||||
#cv2.imshow("changes overlayed", frame)
|
h = int(h * factor)
|
||||||
#cv2.waitKey(10) & 0XFF
|
# if exportFrame as index instead of frameCount - layer.startFrame then we have layer after layer
|
||||||
i += 1
|
frame2 = underlay
|
||||||
|
frame2[y:y+h, x:x+w] = frame[y:y+h, x:x+w]
|
||||||
|
writer.append_data(frame2)
|
||||||
|
|
||||||
|
|
||||||
|
videoReader.thread.join()
|
||||||
|
|
||||||
writer.close()
|
|
||||||
# cv2.destroyAllWindows()
|
|
||||||
|
|
||||||
def exportOverlayed(self, layers, footagePath, outputPath, resizeWidth):
|
def exportOverlayed(self, layers, footagePath, outputPath, resizeWidth):
|
||||||
|
|
||||||
|
|
||||||
listOfFrames = self.makeListOfFrames(layers)
|
listOfFrames = self.makeListOfFrames(layers)
|
||||||
videoReader = VideoReader(footagePath, listOfFrames)
|
videoReader = VideoReader(footagePath, listOfFrames)
|
||||||
videoReader.fillBuffer()
|
videoReader.fillBuffer()
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,6 @@ time compression
|
||||||
|
|
||||||
Time consumed reading video: 369.0188868045807s 3.06GB 26min 1080p downscaled 500p 30fps
|
Time consumed reading video: 369.0188868045807s 3.06GB 26min 1080p downscaled 500p 30fps
|
||||||
Time consumed reading video: 240.s 3.06GB 26min 1080p downscaled 500p 30fps when multithreaded
|
Time consumed reading video: 240.s 3.06GB 26min 1080p downscaled 500p 30fps when multithreaded
|
||||||
|
contour extraction: 10.5 Sec. when only 2 Threads
|
||||||
|
8 secs when also mapping getContours()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,13 +51,11 @@ class VideoReader:
|
||||||
|
|
||||||
def readFrames(self):
|
def readFrames(self):
|
||||||
while self.lastFrame < self.endFrame:
|
while self.lastFrame < self.endFrame:
|
||||||
if not self.buffer.full():
|
res, frame = self.vc.read()
|
||||||
res, frame = self.vc.read()
|
if res:
|
||||||
if res:
|
self.buffer.put((self.lastFrame, frame))
|
||||||
self.buffer.put((self.lastFrame, frame))
|
self.lastFrame += 1
|
||||||
self.lastFrame += 1
|
|
||||||
else:
|
|
||||||
sleep(0.5)
|
|
||||||
self.stopped = True
|
self.stopped = True
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
5
main.py
5
main.py
|
|
@ -21,14 +21,11 @@ def demo():
|
||||||
#print("Time consumed reading video: ", time.time() - start)
|
#print("Time consumed reading video: ", time.time() - start)
|
||||||
|
|
||||||
contours = ContourExtractor().extractContours(footagePath, resizeWidth)
|
contours = ContourExtractor().extractContours(footagePath, resizeWidth)
|
||||||
print("Time consumed in working: ", time.time() - start)
|
print("Time consumed extracting: ", time.time() - start)
|
||||||
layerFactory = LayerFactory(contours)
|
layerFactory = LayerFactory(contours)
|
||||||
print("freeing Data", time.time() - start)
|
|
||||||
layerFactory.freeData(maxLayerLength, minLayerLength)
|
layerFactory.freeData(maxLayerLength, minLayerLength)
|
||||||
print("sort Layers")
|
print("sort Layers")
|
||||||
layerFactory.sortLayers()
|
layerFactory.sortLayers()
|
||||||
#print("fill Layers")
|
|
||||||
#layerFactory.fillLayers(footagePath, resizeWidth)
|
|
||||||
|
|
||||||
Exporter().exportOverlayed(layerFactory.layers,footagePath, os.path.join(os.path.dirname(__file__), "./short.mp4"), resizeWidth)
|
Exporter().exportOverlayed(layerFactory.layers,footagePath, os.path.join(os.path.dirname(__file__), "./short.mp4"), resizeWidth)
|
||||||
print("Total time: ", time.time() - start)
|
print("Total time: ", time.time() - start)
|
||||||
|
|
|
||||||
BIN
short2.mp4
BIN
short2.mp4
Binary file not shown.
BIN
short3.mp4
BIN
short3.mp4
Binary file not shown.
Loading…
Reference in New Issue