diff --git a/Analyzer.py b/Application/Analyzer.py similarity index 97% rename from Analyzer.py rename to Application/Analyzer.py index 8e3f7f4..2108108 100644 --- a/Analyzer.py +++ b/Application/Analyzer.py @@ -10,7 +10,7 @@ import _thread import imageio import numpy as np import matplotlib.pyplot as plt -from VideoReader import VideoReader +from Application.VideoReader import VideoReader from multiprocessing.pool import ThreadPool import imutils diff --git a/Config.py b/Application/Config.py similarity index 100% rename from Config.py rename to Application/Config.py diff --git a/ContourExctractor.py b/Application/ContourExctractor.py similarity index 98% rename from ContourExctractor.py rename to Application/ContourExctractor.py index 1d13598..5411f02 100644 --- a/ContourExctractor.py +++ b/Application/ContourExctractor.py @@ -13,11 +13,11 @@ from threading import Thread from multiprocessing import Queue, Process, Pool from multiprocessing.pool import ThreadPool import concurrent.futures -from VideoReader import VideoReader +from Application.VideoReader import VideoReader from queue import Queue import threading -from Config import Config +from Application.Config import Config class ContourExtractor: diff --git a/Exporter.py b/Application/Exporter.py similarity index 95% rename from Exporter.py rename to Application/Exporter.py index 3fb8ee8..69be7d6 100644 --- a/Exporter.py +++ b/Application/Exporter.py @@ -1,9 +1,9 @@ import imageio import imutils import numpy as np -from Layer import Layer +from Application.Layer import Layer import cv2 -from VideoReader import VideoReader +from Application.VideoReader import VideoReader import pickle class Exporter: @@ -49,6 +49,8 @@ class Exporter: for layer in layers: if layer.startFrame <= frameCount and layer.startFrame + len(layer.bounds) > frameCount: for (x, y, w, h) in layer.bounds[frameCount - layer.startFrame]: + if x is None: + break factor = videoReader.w / self.resizeWidth x = int(x * factor) y = int(y * factor) @@ -87,6 +89,8 @@ class Exporter: for layer in layers: if layer.startFrame <= frameCount and layer.startFrame + len(layer.bounds) > frameCount: for (x, y, w, h) in layer.bounds[frameCount - layer.startFrame]: + if x is None: + break factor = videoReader.w / self.resizeWidth x = int(x * factor) y = int(y * factor) diff --git a/Importer.py b/Application/Importer.py similarity index 100% rename from Importer.py rename to Application/Importer.py diff --git a/Application/Layer.py b/Application/Layer.py new file mode 100644 index 0000000..b4d5d5c --- /dev/null +++ b/Application/Layer.py @@ -0,0 +1,137 @@ +import numpy as np +import cv2 +import imutils + +from kneed import KneeLocator +from sklearn.datasets import make_blobs +from sklearn.cluster import KMeans +from sklearn.metrics import silhouette_score +from sklearn.preprocessing import StandardScaler +import matplotlib.pyplot as plt + +class Layer: + #bounds = [[(x,y,w,h), ],] + + startFrame = None + lastFrame = None + length = None + + def __init__(self, startFrame, data, config): + self.startFrame = startFrame + self.lastFrame = startFrame + self.config = config + self.data = [] + self.bounds = [] + self.bounds.append([data]) + #print("Layer constructed") + + def add(self, frameNumber, data): + if not self.startFrame + len(self.bounds) < frameNumber: + if len(self.bounds[self.startFrame - frameNumber]) >= 1: + self.bounds[self.startFrame - frameNumber].append(data) + else: + self.lastFrame = frameNumber + self.bounds.append([data]) + + self.getLength() + + def getLength(self): + self.length = len(self.bounds) + return self.length + + def fill(self, inputPath, resizeWidth): + '''reads in the contour data, needed for export''' + + cap = cv2.VideoCapture(inputPath) + self.data = [None]*len(self.bounds) + i = 0 + cap.set(1, self.startFrame) + while i < len(self.bounds): + ret, frame = cap.read() + + if ret: + frame = imutils.resize(frame, width=resizeWidth) + (x, y, w, h) = self.bounds[i] + self.data[i] = frame[y:y+h, x:x+w] + i+=1 + cap.release() + + def clusterDelete(self): + org = self.bounds + mapped = [] + mapping = [] + clusterCount = 1 + noiseSensitivity = 3/4 + noiseThreashold = 0.05 + for i, bounds in enumerate(org): + for j, bound in enumerate(bounds): + x = (bound[0] + bound[2]/2) / self.config["w"] + y = (bound[1] + bound[3]/2) / self.config["w"] + + mapped.append(list((x,y))) + mapping.append([i,j]) + + mapped = np.array(mapped).astype(np.float16) + labels = [] + centers = [] + kmeans = None + + while True: + kmeans = KMeans(init="random", n_clusters=clusterCount, n_init=5, max_iter=300, random_state=42) + kmeans.fit(mapped) + labels = list(kmeans.labels_) + + if kmeans.n_features_in_ < clusterCount: + break + + maxm = 0 + for x in set(labels): + y = labels.count(x) + if y > maxm: + maxm = y + + if maxm > len(mapped)*(noiseSensitivity): + clusterCount += 1 + else: + centers = kmeans.cluster_centers_ + break + + classed = [[]] + for i, x in enumerate(list(labels)): + while len(classed) <= x: + classed.append([]) + classed[x].append(i) + + dists = [] + for num, cen in enumerate(centers): + dist = 0 + for i in classed[num]: + dist += (mapped[i][0]-cen[0])**2 + (mapped[i][1]-cen[1])**2 + dist/=len(classed[num]) + dists.append(dist*1000) + + newContours = [[]] + for i, dis in enumerate(dists): + # copy contours which are spread out, delete rest by not yopying them + if dis > noiseThreashold: + for j in classed[i]: + x, y = mapping[j] + while x >= len(newContours): + newContours.append([]) + while y > len(newContours[x]): + newContours[x].append((None, None, None, None)) + newContours[x].append(org[x][y]) + + self.bounds = newContours + print(f"{clusterCount} clusters identified {dists}") + #fig, ax = plt.subplots() + #x=mapped[:,0] + #y=mapped[:,1] + + #ax.scatter(x, y, labels, s=10, cmap="rainbow") + #ax.grid(True) + #plt.show() + + #print("done") + + diff --git a/LayerFactory.py b/Application/LayerFactory.py similarity index 86% rename from LayerFactory.py rename to Application/LayerFactory.py index afc8b5a..50e81b8 100644 --- a/LayerFactory.py +++ b/Application/LayerFactory.py @@ -1,5 +1,5 @@ -from Layer import Layer -from Config import Config +from Application.Layer import Layer +from Application.Config import Config from multiprocessing.pool import ThreadPool class LayerFactory: @@ -12,6 +12,7 @@ class LayerFactory: self.maxLayerLength = config["maxLayerLength"] self.resizeWidth = config["resizeWidth"] self.footagePath = config["inputPath"] + self.config = config print("LayerFactory constructed") self.data = data if data is not None: @@ -25,7 +26,11 @@ class LayerFactory: 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: @@ -58,7 +63,7 @@ class LayerFactory: frameNumber = min(data) contours = data[frameNumber] for contour in contours: - self.layers.append(Layer(frameNumber, contour)) + self.layers.append(Layer(frameNumber, contour, self.config)) self.oldLayerIDs = [] @@ -72,9 +77,12 @@ class LayerFactory: #pool.map_async(self.getLayers, tmp) for x in tmp: self.getLayers(x) - self.freeData() - self.sortLayers() + self.sortLayers() + self.cleanLayers() + self.freeData() + + return self.layers def getLayers(self, data): @@ -98,7 +106,7 @@ class LayerFactory: break if not foundLayer: - self.layers.append(Layer(frameNumber, (x,y,w,h))) + self.layers.append(Layer(frameNumber, (x,y,w,h), self.config)) def contoursOverlay(self, l1, r1, l2, r2): # If one rectangle is on left side of other @@ -117,3 +125,7 @@ class LayerFactory: def sortLayers(self): self.layers.sort(key = lambda c:c.startFrame) + + def cleanLayers(self): + for layer in self.layers: + layer.clusterDelete() diff --git a/VideoReader.py b/Application/VideoReader.py similarity index 76% rename from VideoReader.py rename to Application/VideoReader.py index 528e6ba..5010ba3 100644 --- a/VideoReader.py +++ b/Application/VideoReader.py @@ -4,18 +4,18 @@ import cv2 from time import sleep from queue import Queue import threading -from Config import Config +from Application.Config import Config class VideoReader: - - listOfFrames = None + w = 0 + h = 0 def __init__(self, config, setOfFrames = None): videoPath = config["inputPath"] if videoPath is None: - print("Video reader needs a videoPath!") + print("ERROR: Video reader needs a videoPath!") return None self.videoPath = videoPath @@ -24,11 +24,15 @@ class VideoReader: self.buffer = Queue(config["videoBufferLength"]) self.vc = cv2.VideoCapture(videoPath) self.stopped = False + self.getWH() + if setOfFrames is not None: + self.listOfFrames = sorted(setOfFrames) + + def getWH(self): res, image = self.vc.read() self.w = image.shape[1] self.h = image.shape[0] - if setOfFrames is not None: - self.listOfFrames = sorted(setOfFrames) + return (self.w, self.h) def pop(self): return self.buffer.get(block=True) @@ -91,29 +95,3 @@ class VideoReader: def getFPS(self): return self.vc.get(cv2.CAP_PROP_FPS) - - def get_file_metadata(self, path, filename, metadata): - # Path shouldn't end with backslash, i.e. "E:\Images\Paris" - # filename must include extension, i.e. "PID manual.pdf" - # Returns dictionary containing all file metadata. - sh = win32com.client.gencache.EnsureDispatch('Shell.Application', 0) - ns = sh.NameSpace(path) - - # Enumeration is necessary because ns.GetDetailsOf only accepts an integer as 2nd argument - file_metadata = dict() - item = ns.ParseName(str(filename)) - for ind, attribute in enumerate(metadata): - attr_value = ns.GetDetailsOf(item, ind) - if attr_value: - file_metadata[attribute] = attr_value - - return file_metadata - - - - - - - - - diff --git a/Layer.py b/Layer.py deleted file mode 100644 index 8c5aaa0..0000000 --- a/Layer.py +++ /dev/null @@ -1,53 +0,0 @@ -import numpy as np -import cv2 -import imutils -class Layer: - #bounds = [[(x,y,w,h), ],] - - startFrame = None - lastFrame = None - length = None - - def __init__(self, startFrame, data): - self.startFrame = startFrame - self.lastFrame = startFrame - - self.data = [] - self.bounds = [] - self.bounds.append([data]) - #print("Layer constructed") - - def add(self, frameNumber, data): - if not self.startFrame + len(self.bounds) < frameNumber: - if len(self.bounds[self.startFrame - frameNumber]) >= 1: - self.bounds[self.startFrame - frameNumber].append(data) - else: - self.lastFrame = frameNumber - self.bounds.append([data]) - - self.getLength() - - def getLength(self): - self.length = len(self.bounds) - return self.length - - def fill(self, inputPath, resizeWidth): - '''reads in the contour data, needed for export''' - - cap = cv2.VideoCapture(inputPath) - self.data = [None]*len(self.bounds) - i = 0 - cap.set(1, self.startFrame) - while i < len(self.bounds): - ret, frame = cap.read() - - if ret: - frame = imutils.resize(frame, width=resizeWidth) - (x, y, w, h) = self.bounds[i] - self.data[i] = frame[y:y+h, x:x+w] - i+=1 - cap.release() - - - - diff --git a/main.py b/main.py index 31a9c6e..9478f59 100644 --- a/main.py +++ b/main.py @@ -1,12 +1,12 @@ import os import time -from ContourExctractor import ContourExtractor -from Exporter import Exporter -from LayerFactory import LayerFactory -from Analyzer import Analyzer -from Config import Config -from Importer import Importer -import cv2 +from Application.ContourExctractor import ContourExtractor +from Application.Exporter import Exporter +from Application.LayerFactory import LayerFactory +from Application.Analyzer import Analyzer +from Application.Config import Config +from Application.Importer import Importer +from Application.VideoReader import VideoReader #TODO # finden von relevanten Stellen anhand von zu findenen metriken für vergleichsbilder @@ -15,10 +15,14 @@ def demo(): start = time.time() config = Config() + config["inputPath"] = os.path.join(os.path.dirname(__file__), "generate test footage/3.mp4") #config["importPath"] = os.path.join(os.path.dirname(__file__), "output/short.txt") config["outputPath"] = os.path.join(os.path.dirname(__file__), "output/short.mp4") + vr = VideoReader(config) + config["w"], config["h"] = vr.getWH() + if config["importPath"] is None: #ana = Analyzer(config) #ref = ana.avg diff --git a/output/short.txt b/output/short.txt index 774f013..796a87a 100644 Binary files a/output/short.txt and b/output/short.txt differ