Video-Summary/Application/Layer.py

172 lines
5.5 KiB
Python
Raw Normal View History

2020-10-18 15:36:34 +00:00
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):
2020-10-31 19:36:43 +00:00
'''returns a Layer object
Layers are collections of contours with a StartFrame,
which is the number of the frame the first contour of
this layer was extraced from
A Contour is a CV2 Contour, which is a y*x*3 rgb numpy array,
but we only care about the corners of the contours.
So we save the bounds (x,y,w,h) in bounds[] and the actual content in data[]
'''
2020-10-18 15:36:34 +00:00
self.startFrame = startFrame
self.lastFrame = startFrame
self.config = config
self.data = []
self.bounds = []
self.bounds.append([data])
#print("Layer constructed")
2020-10-31 19:36:43 +00:00
def add(self, frameNumber, bound):
'''Adds a bound'''
2020-10-18 15:36:34 +00:00
if not self.startFrame + len(self.bounds) < frameNumber:
if len(self.bounds[self.startFrame - frameNumber]) >= 1:
2020-10-31 19:36:43 +00:00
self.bounds[self.startFrame - frameNumber].append(bound)
2020-10-18 15:36:34 +00:00
else:
self.lastFrame = frameNumber
2020-10-31 19:36:43 +00:00
self.bounds.append([bound])
2020-10-18 15:36:34 +00:00
self.getLength()
def getLength(self):
2020-10-31 19:36:43 +00:00
return len(self)
def __len__(self):
2020-10-18 15:36:34 +00:00
self.length = len(self.bounds)
return self.length
def fill(self, inputPath, resizeWidth):
2020-10-31 19:36:43 +00:00
'''deprecated
Fills the data[] array by iterateing over the bounds'''
2020-10-18 15:36:34 +00:00
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):
2020-10-31 19:36:43 +00:00
'''Uses a cluster analysis to remove contours which are not the result of movement'''
2020-10-18 15:36:34 +00:00
org = self.bounds
2020-10-31 19:36:43 +00:00
if len(org) == 1:
return
2020-10-18 15:36:34 +00:00
mapped = []
mapping = []
clusterCount = 1
2020-10-18 17:24:55 +00:00
noiseSensitivity = self.config["noiseSensitivity"]
noiseThreashold = self.config["noiseThreashold"]
2020-10-31 19:36:43 +00:00
# calculates the middle of each contour in the 2d bounds[] and saves it in 1d list
# and saves the 2d indexes in a mapping array
2020-10-18 15:36:34 +00:00
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
2020-10-31 19:36:43 +00:00
# the loop isn't nessecary (?) if the number of clusters is known, since it isn't the loop tries to optimize
2020-10-18 15:36:34 +00:00
while True:
2020-11-01 16:43:05 +00:00
kmeans = KMeans(init="random", n_clusters=clusterCount, n_init=10, max_iter=300, random_state=42)
2020-10-18 15:36:34 +00:00
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
2020-10-23 22:14:43 +00:00
if maxm > len(mapped)*(noiseSensitivity) and clusterCount+1<=len(kmeans.cluster_centers_):
2020-10-18 15:36:34 +00:00
clusterCount += 1
else:
centers = kmeans.cluster_centers_
break
2020-10-31 19:36:43 +00:00
# transformes the labels array
# new array:
# the index is the cluster id, the array is the id of the contour
# [
# [1,2,3]
# [3,4,5]
# [6,7,8,9]
# ]
2020-10-18 15:36:34 +00:00
classed = [[]]
for i, x in enumerate(list(labels)):
while len(classed) <= x:
classed.append([])
classed[x].append(i)
2020-10-31 19:36:43 +00:00
# calculates the euclidean distance (without the sqrt) of each point in a cluster to the cluster center
2020-10-18 15:36:34 +00:00
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)
2020-10-31 19:36:43 +00:00
# copy all contours of the clusters with more movement than the threshold
2020-10-18 15:36:34 +00:00
newContours = [[]]
for i, dis in enumerate(dists):
2020-10-31 19:36:43 +00:00
# copy contours which are spread out, delete rest by not copying them
2020-10-18 15:36:34 +00:00
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
2020-10-18 17:24:55 +00:00
#print(f"{clusterCount} clusters identified {dists}")
2020-10-18 15:36:34 +00:00
#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")