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 = []
|
2020-11-11 21:32:12 +00:00
|
|
|
self.stats = dict()
|
|
|
|
|
|
2020-10-18 15:36:34 +00:00
|
|
|
self.bounds.append([data])
|
|
|
|
|
#print("Layer constructed")
|
|
|
|
|
|
2020-10-31 19:36:43 +00:00
|
|
|
def add(self, frameNumber, bound):
|
|
|
|
|
'''Adds a bound'''
|
2020-11-08 15:28:47 +00:00
|
|
|
if self.startFrame + len(self.bounds) - 1 > frameNumber:
|
|
|
|
|
if len(self.bounds[frameNumber - self.startFrame]) >= 1:
|
|
|
|
|
self.bounds[frameNumber - self.startFrame].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
|
|
|
|
2020-11-11 21:32:12 +00:00
|
|
|
def calcStats(self):
|
|
|
|
|
middles = []
|
|
|
|
|
for i, bounds in enumerate(self.bounds):
|
|
|
|
|
for j, bound in enumerate(bounds):
|
|
|
|
|
if None in bound:
|
|
|
|
|
continue
|
|
|
|
|
x = (bound[0] + bound[2]/2)
|
|
|
|
|
y = (bound[1] + bound[3]/2)
|
|
|
|
|
middles.append([x,y])
|
|
|
|
|
|
|
|
|
|
avgx = 0
|
|
|
|
|
avgy = 0
|
|
|
|
|
for i in range(1, len(middles), 2):
|
|
|
|
|
avgx += float(middles[i][0]-middles[i-1][0])/len(middles)
|
|
|
|
|
avgy += float(middles[i][1]-middles[i-1][1])/len(middles)
|
|
|
|
|
self.stats = dict()
|
2020-11-12 17:59:57 +00:00
|
|
|
self.stats["avg"] = round((avgx**2 + avgy**2)**(1/2),2)
|
2020-11-11 21:32:12 +00:00
|
|
|
|
|
|
|
|
x=0
|
|
|
|
|
y=0
|
|
|
|
|
for i in range(0, len(middles)):
|
|
|
|
|
x += (float(middles[i][0]) - avgx)**2
|
|
|
|
|
y += (float(middles[i][1]) - avgy)**2
|
|
|
|
|
|
|
|
|
|
x /= (len(middles)-1)
|
|
|
|
|
y /= (len(middles)-1)
|
|
|
|
|
|
2020-11-12 17:59:57 +00:00
|
|
|
self.stats["var"] = round((x**2 + y**2)**(1/2),2)
|
|
|
|
|
self.stats["dev"] = round((x + y)**(1/2), 2)
|
2020-11-11 21:32:12 +00:00
|
|
|
|
|
|
|
|
|
2020-10-18 15:36:34 +00:00
|
|
|
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 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-11-11 21:32:12 +00:00
|
|
|
|
|
|
|
|
kmeans = KMeans(init="random", n_clusters=clusterCount, n_init=10, max_iter=300, random_state=42)
|
|
|
|
|
kmeans.fit(mapped)
|
|
|
|
|
labels = list(kmeans.labels_)
|
|
|
|
|
|
|
|
|
|
maxm = 0
|
|
|
|
|
for x in set(labels):
|
|
|
|
|
y = labels.count(x)
|
|
|
|
|
if y > maxm:
|
|
|
|
|
maxm = y
|
|
|
|
|
|
2020-10-18 15:36:34 +00:00
|
|
|
|
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-11-08 15:28:47 +00:00
|
|
|
|
|
|
|
|
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))
|
|
|
|
|
if dis > noiseThreashold:
|
2020-10-18 15:36:34 +00:00
|
|
|
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")
|
|
|
|
|
|
|
|
|
|
|