added cluster analyses on layers
This commit is contained in:
parent
559fef7bc4
commit
fe42e7b5c7
|
|
@ -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
|
||||
|
||||
|
|
@ -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:
|
||||
|
||||
|
|
@ -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)
|
||||
|
|
@ -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")
|
||||
|
||||
|
||||
|
|
@ -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.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()
|
||||
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
53
Layer.py
53
Layer.py
|
|
@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
18
main.py
18
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
|
||||
|
|
|
|||
BIN
output/short.txt
BIN
output/short.txt
Binary file not shown.
Loading…
Reference in New Issue