diff --git a/Application/Analyzer.py b/Application/Analyzer.py deleted file mode 100644 index 2108108..0000000 --- a/Application/Analyzer.py +++ /dev/null @@ -1,57 +0,0 @@ -from imutils.video import VideoStream -import argparse -import datetime -import imutils -import time -import cv2 -import os -import traceback -import _thread -import imageio -import numpy as np -import matplotlib.pyplot as plt -from Application.VideoReader import VideoReader -from multiprocessing.pool import ThreadPool -import imutils - - -class Analyzer: - - def __init__(self, config): - print("Analyzer constructed") - videoReader = VideoReader(config) - videoReader.fillBuffer() - self.config = config - self.avg = imutils.resize(np.zeros((videoReader.h,videoReader.w,3),np.float), width=config["resizeWidth"]) - self.end = videoReader.endFrame - self.c = 0 - start = time.time() - fak = 10 - while not videoReader.videoEnded(): - self.c, frame = videoReader.pop() - if not self.c%fak == 0: - continue - if videoReader.endFrame - self.c <= fak: - break - frame = imutils.resize(frame, width=self.config["resizeWidth"]) - - - self.avg += frame.astype(np.float)/(self.end/fak) - if self.c%(1800*6) == 0: - print(f"{self.c/(60*30)} Minutes processed in {round((time.time() - start), 2)} each") - start = time.time() - - #print("done") - videoReader.thread.join() - self.avg = np.array(np.round(self.avg), dtype=np.uint8) - #return self.avg - cv2.imshow("changes overlayed", self.avg) - cv2.waitKey(10) & 0XFF - - - - def average(self, frame): - frame = imutils.resize(frame[1], width=self.config["resizeWidth"]) - self.avg += frame.astype(np.float)/(self.end/5) - - diff --git a/Application/ContourExctractor.py b/Application/ContourExctractor.py index 914454d..7679e13 100644 --- a/Application/ContourExctractor.py +++ b/Application/ContourExctractor.py @@ -1,28 +1,19 @@ -from imutils.video import VideoStream -import argparse -import datetime -import imutils -import time -import cv2 -import os -import traceback -import _thread -import imageio -import numpy as np +from Application.VideoReader import VideoReader +from Application.Config import Config + from threading import Thread from multiprocessing import Queue, Process, Pool from multiprocessing.pool import ThreadPool -import concurrent.futures -from Application.VideoReader import VideoReader from queue import Queue -import threading - -from Application.Config import Config +import imutils +import time +import cv2 +import numpy as np class ContourExtractor: - #extracedContours = {frame_number: [(contour, (x,y,w,h)), ...], } - # dict with frame numbers as keys and the contour bounds of every contour for that frame + # extracedContours = {frame_number: [(contour, (x,y,w,h)), ...], } + # dict with frame numbers as keys and the contour bounds of every contour for that frame def getExtractedContours(self): return self.extractedContours @@ -40,15 +31,15 @@ class ContourExtractor: self.resizeWidth = config["resizeWidth"] self.videoPath = config["inputPath"] self.xDim = 0 - self.yDim = 0 + self.yDim = 0 self.config = config self.lastFrames = None self.averages = dict() print("ContourExtractor initiated") - def extractContours(self): - videoReader = VideoReader(self.config) + def extractContours(self): + videoReader = VideoReader(self.config) self.fps = videoReader.getFPS() self.length = videoReader.getLength() videoReader.fillBuffer() @@ -61,10 +52,11 @@ class ContourExtractor: if videoReader.buffer.qsize() == 0: time.sleep(.5) - tmpData = [videoReader.pop() for i in range(0, videoReader.buffer.qsize())] + tmpData = [videoReader.pop() + for i in range(0, videoReader.buffer.qsize())] pool.map(self.computeMovingAverage, (tmpData,)) pool.map(self.async2, (tmpData,)) - #for data in tmpData: + # for data in tmpData: # self.getContours(data) frameCount = tmpData[-1][0] @@ -81,18 +73,21 @@ class ContourExtractor: while frameCount not in self.averages: time.sleep(0.1) firstFrame = self.averages.pop(frameCount, None) - + if frameCount % (10*self.fps) == 1: - print(f" \r {round((frameCount/self.fps)/self.length, 4)*100} % processed in {round(time.time() - self.start, 2)}s", end='\r') + print( + f" \r {round((frameCount/self.fps)/self.length, 4)*100} % processed in {round(time.time() - self.start, 2)}s", end='\r') gray = self.prepareFrame(frame) 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] # dilate the thresholded image to fill in holes, then find contours thresh = cv2.dilate(thresh, None, iterations=10) #cv2.imshow("changes x", thresh) #cv2.waitKey(10) & 0XFF - cnts = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) + cnts = cv2.findContours( + thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) cnts = imutils.grab_contours(cnts) contours = [] @@ -102,18 +97,15 @@ class ContourExtractor: (x, y, w, h) = cv2.boundingRect(c) if ca < self.min_area or ca > self.max_area: continue - contours.append((x, y, w, h)) - # the mask has to be packed like this, since np doesn't have a bit array, + # the mask has to be packed like this, since np doesn't have a bit array, # meaning every bit in the mask would take up 8bits, which migth be too much - masks.append(np.packbits(np.copy(thresh[y:y+h,x:x+w]), axis=0)) - - - - if len(contours) != 0 and contours is not None: + masks.append(np.packbits(np.copy(thresh[y:y+h, x:x+w]), axis=0)) + + if len(contours) != 0 and contours is not None: # this should be thread safe self.extractedContours[frameCount] = contours - self.extractedMasks[frameCount] = masks + self.extractedMasks[frameCount] = masks def prepareFrame(self, frame): frame = imutils.resize(frame, width=self.resizeWidth) @@ -129,29 +121,28 @@ class ContourExtractor: frame = frames[0][1] frame = self.prepareFrame(frame) for j in range(0, len(frames)): - frameNumber, _ = frames[j] + frameNumber, _ = frames[j] self.averages[frameNumber] = frame # put last x frames into a buffer - self.lastFrames = frames[-averageFrames:] + self.lastFrames = frames[-averageFrames:] return if self.lastFrames is not None: - frames = self.lastFrames + frames + frames = self.lastFrames + frames - tmp = [[j, frames, averageFrames] for j in range(averageFrames, len(frames))] + tmp = [[j, frames, averageFrames] + for j in range(averageFrames, len(frames))] with ThreadPool(16) as pool: pool.map(self.averageDaFrames, tmp) - self.lastFrames = frames[-averageFrames:] - + self.lastFrames = frames[-averageFrames:] def averageDaFrames(self, dat): j, frames, averageFrames = dat frameNumber, frame = frames[j] frame = self.prepareFrame(frame) - + avg = frame/averageFrames - for jj in range(0,averageFrames-1): + for jj in range(0, averageFrames-1): avg += self.prepareFrame(frames[j-jj][1])/averageFrames self.averages[frameNumber] = np.array(np.round(avg), dtype=np.uint8) - #self.averages[frameNumber] = self.prepareFrame(frames[j-averageFrames - 1][1]) diff --git a/Application/Exporter.py b/Application/Exporter.py index f9650de..b740c39 100644 --- a/Application/Exporter.py +++ b/Application/Exporter.py @@ -1,12 +1,13 @@ +from Application.Layer import Layer +from Application.VideoReader import VideoReader +from datetime import datetime import imageio import imutils import numpy as np -from Application.Layer import Layer import cv2 -from Application.VideoReader import VideoReader import pickle -import time -from datetime import datetime +import time + class Exporter: fps = 30 @@ -18,14 +19,13 @@ class Exporter: self.config = config print("Exporter initiated") - def export(self, layers, contours, masks, raw = True, overlayed = True): + def export(self, layers, contours, masks, raw=True, overlayed=True): if raw: self.exportRawData(layers, contours, masks) if overlayed: self.exportOverlayed(layers) else: self.exportLayers(layers) - def exportLayers(self, layers): @@ -42,7 +42,8 @@ class Exporter: start = time.time() for i, layer in enumerate(layers): - print(f"\r {i}/{len(layers)} {round(i/len(layers)*100,2)}% {round((time.time() - start), 2)}s", end='\r') + print( + f"\r {i}/{len(layers)} {round(i/len(layers)*100,2)}% {round((time.time() - start), 2)}s", end='\r') if len(layer.bounds[0]) == 0: continue videoReader = VideoReader(self.config) @@ -56,12 +57,15 @@ class Exporter: if x is None: continue factor = videoReader.w / self.resizeWidth - x, y, w, h = (int(x * factor), int(y * factor), int(w * factor), int(h * factor)) - + x, y, w, h = (int(x * factor), int(y * factor), + int(w * factor), int(h * factor)) + frame2[y:y+h, x:x+w] = np.copy(frame[y:y+h, x:x+w]) - time = datetime.fromtimestamp(int(frameCount/self.fps) + videoReader.getStartTime()) - cv2.putText(frame2, str(i) + " " + f"{time.hour}:{time.minute}:{time.second}", (int(x+w/2), int(y+h/2)), cv2.FONT_HERSHEY_SIMPLEX, 1,(255,255,255), 2) + time = datetime.fromtimestamp( + int(frameCount/self.fps) + videoReader.getStartTime()) + cv2.putText(frame2, str(i) + " " + f"{time.hour}:{time.minute}:{time.second}", (int( + x+w/2), int(y+h/2)), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2) #cv2.putText(frame2, str(layer.stats["avg"]) + " " + str(layer.stats["var"]) + " " + str(layer.stats["dev"]), (int(500), int(500)), cv2.FONT_HERSHEY_SIMPLEX, 1,(255,0,255), 2) writer.append_data(frame2) videoReader.vc.release() @@ -69,7 +73,6 @@ class Exporter: videoReader.vc.release() videoReader.thread.join() writer.close() - def exportOverlayed(self, layers): @@ -84,7 +87,6 @@ class Exporter: frames.append(np.copy(underlay)) exportFrame = 0 - while not videoReader.videoEnded(): frameCount, frame = videoReader.pop() if frameCount % (60*self.fps) == 0: @@ -98,25 +100,31 @@ class Exporter: if layer.startFrame <= frameCount and layer.startFrame + len(layer.bounds) > frameCount: for i in range(0, len(layer.bounds[frameCount - layer.startFrame])): underlay1 = underlay - (x, y, w, h) = layer.bounds[frameCount - layer.startFrame][i] + (x, y, w, + h) = layer.bounds[frameCount - layer.startFrame][i] mask = layer.masks[frameCount - layer.startFrame][i] if x is None: break factor = videoReader.w / self.resizeWidth - x, y, w, h = (int(x * factor), int(y * factor), int(w * factor), int(h * factor)) - + x, y, w, h = (int(x * factor), int(y * factor), + int(w * factor), int(h * factor)) + mask = imutils.resize(mask, width=w, height=h+1) - mask = np.resize(mask, (h,w)) + mask = np.resize(mask, (h, w)) mask = cv2.erode(mask, None, iterations=10) mask *= 255 frame2 = frames[frameCount - layer.startFrame] - xx = np.copy(cv2.bitwise_and(frame2[y:y+h, x:x+w], frame2[y:y+h, x:x+w], mask=cv2.bitwise_not(mask))) - frame2[y:y+h, x:x+w] = cv2.addWeighted(xx,1, np.copy(cv2.bitwise_and(frame[y:y+h, x:x+w], frame[y:y+h, x:x+w], mask=mask)),1,0) - frames[frameCount - layer.startFrame] = np.copy(frame2) + xx = np.copy(cv2.bitwise_and( + frame2[y:y+h, x:x+w], frame2[y:y+h, x:x+w], mask=cv2.bitwise_not(mask))) + frame2[y:y+h, x:x+w] = cv2.addWeighted(xx, 1, np.copy( + cv2.bitwise_and(frame[y:y+h, x:x+w], frame[y:y+h, x:x+w], mask=mask)), 1, 0) + frames[frameCount - layer.startFrame] = np.copy(frame2) #cv2.imshow("changes x", frame2) #cv2.waitKey(10) & 0XFF - time = datetime.fromtimestamp(int(frameCount/self.fps) + videoReader.getStartTime()) - cv2.putText(frames[frameCount - layer.startFrame], f"{time.hour}:{time.minute}:{time.second}", (int(x+w/2), int(y+h/2)), cv2.FONT_HERSHEY_SIMPLEX, 1,(255,255,255), 2) + time = datetime.fromtimestamp( + int(frameCount/self.fps) + videoReader.getStartTime()) + cv2.putText(frames[frameCount - layer.startFrame], f"{time.hour}:{time.minute}:{time.second}", (int( + x+w/2), int(y+h/2)), cv2.FONT_HERSHEY_SIMPLEX, 1, (255, 255, 255), 2) videoReader.thread.join() videoReader.vc.release() @@ -132,7 +140,7 @@ class Exporter: def exportRawData(self, layers, contours, masks): with open(self.config["importPath"], "wb+") as file: pickle.dump((layers, contours, masks), file) - + def getMaxLengthOfLayers(self, layers): maxLength = 0 for layer in layers: diff --git a/Application/Layer.py b/Application/Layer.py index 2c59d1f..c02910c 100644 --- a/Application/Layer.py +++ b/Application/Layer.py @@ -39,8 +39,8 @@ class Layer: return if frameNumber > self.lastFrame: for i in range(frameNumber - self.lastFrame): - self.bounds.append([bound]) - self.masks.append([mask]) + self.bounds.append([]) + self.masks.append([]) self.lastFrame = frameNumber if bound not in self.bounds[index]: diff --git a/Application/LayerFactory.py b/Application/LayerFactory.py index 150b3cf..0a9dda0 100644 --- a/Application/LayerFactory.py +++ b/Application/LayerFactory.py @@ -2,10 +2,9 @@ from Application.Layer import Layer from Application.Config import Config from Application.VideoReader import VideoReader from Application.Exporter import Exporter + from multiprocessing.pool import ThreadPool -import cv2 import numpy as np -import copy class LayerFactory: @@ -53,7 +52,7 @@ class LayerFactory: for x in tmp: self.getLayers(x) - #self.joinLayers() + # self.joinLayers() return self.layers def getLayers(self, data): @@ -150,14 +149,11 @@ class LayerFactory: for l in layers: if l.lastFrame < maxFrame: maxFrame = l.lastFrame - return maxFrame def contoursOverlay(self, l1, r1, l2, r2): - # If one rectangle is on left side of other if(l1[0] >= r2[0] or l2[0] >= r1[0]): return False - # If one rectangle is above other if(l1[1] <= r2[1] or l2[1] <= r1[1]): return False return True diff --git a/Application/LayerManager.py b/Application/LayerManager.py index 96e138d..bfc0e0f 100644 --- a/Application/LayerManager.py +++ b/Application/LayerManager.py @@ -48,26 +48,6 @@ class LayerManager: for layer in self.layers: layer.calcStats() - def removeStaticLayers(self): - '''Removes Layers with little to no movement''' - layers = [] - for i, layer in enumerate(self.layers): - checks = 0 - for bound in layer.bounds[0]: - if bound[0] is None: - continue - for bound2 in layer.bounds[-1]: - if bound2[0] is None: - continue - if abs(bound[0] - bound2[0]) < 10: - checks += 1 - if abs(bound[1] - bound2[1]) < 10: - checks += 1 - if checks <= 2: - layers.append(layer) - self.layers = layers - - def freeMin(self): self.data.clear() layers = [] @@ -76,21 +56,19 @@ class LayerManager: layers.append(l) self.layers = layers - def freeMax(self): layers = [] for l in self.layers: if len(l) < self.maxLayerLength: layers.append(l) self.layers = layers - def tagLayers(self): '''Use classifieres the tag all Layers, by reading the contour content from the original video, then applying the classifier''' print("Tagging Layers") exporter = Exporter(self.config) start = time.time() - for i, layer in enumerate(self.layers[20:]): + for i, layer in enumerate(self.layers): print(f"{round(i/len(self.layers)*100,2)} {round((time.time() - start), 2)}") start = time.time() if len(layer.bounds[0]) == 0: diff --git a/Application/VideoReader.py b/Application/VideoReader.py index 0be1bbd..8ccfd9b 100644 --- a/Application/VideoReader.py +++ b/Application/VideoReader.py @@ -1,28 +1,26 @@ - -import multiprocessing -import cv2 -from time import sleep -from queue import Queue -import threading -import pathlib from Application.Config import Config -import os + from datetime import datetime +from queue import Queue + +import cv2 +import threading +import os class VideoReader: listOfFrames = None w = 0 h = 0 - def __init__(self, config, setOfFrames = None): + def __init__(self, config, setOfFrames=None): videoPath = config["inputPath"] if videoPath is None: - print("ERROR: Video reader needs a videoPath!") - return None + raise Exception("ERROR: Video reader needs a videoPath!") self.videoPath = videoPath self.lastFrame = 0 - #buffer = Queue([(frameNumber, frame), ]) + # buffer data struct: + # buffer = Queue([(frameNumber, frame), ]) self.buffer = Queue(config["videoBufferLength"]) self.vc = cv2.VideoCapture(videoPath) self.stopped = False @@ -31,7 +29,7 @@ class VideoReader: self.calcLength() self.calcStartTime() if setOfFrames is not None: - self.listOfFrames = sorted(setOfFrames) + self.listOfFrames = sorted(setOfFrames) def getWH(self): '''get width and height''' @@ -44,14 +42,13 @@ class VideoReader: return self.buffer.get(block=True) def fillBuffer(self, listOfFrames=None): - if self.buffer.full(): - print("VideoReader::fillBuffer was called when buffer was full.") 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=()) + self.thread = threading.Thread( + target=self.readFramesByList, args=()) else: self.thread = threading.Thread(target=self.readFrames, args=()) self.thread.start() @@ -69,7 +66,7 @@ class VideoReader: 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]) @@ -91,9 +88,9 @@ class VideoReader: # 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 @@ -107,7 +104,7 @@ class VideoReader: 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() diff --git a/main.py b/main.py index 4305812..9ca8398 100644 --- a/main.py +++ b/main.py @@ -35,12 +35,11 @@ def main(): layerManager = LayerManager(config, layers) layerManager.transformLayers() - #layerManager.tagLayers() layers = layerManager.layers - #print([len(l) for l in sorted(layers, key = lambda c:len(c), reverse=True)[:20]]) if len(layers) == 0: exit(1) + exporter = Exporter(config) print(f"Exporting {len(contours)} Contours and {len(layers)} Layers") exporter.export(layers, contours, masks, raw=True, overlayed=True) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..9bc0261 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +opencv-python +numpy +imutils +imageio