Upload 5 files
Browse filesUpload app.py file, and the logic to make inference
- app.py +33 -0
- centroidtracker.py +163 -0
- prediction.py +203 -0
- requirements.txt +13 -0
- trackableobject.py +11 -0
app.py
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from prediction import smartcities
|
3 |
+
|
4 |
+
# Streamlit Interface
|
5 |
+
st.header("Smart City Cars and Bikes detection")
|
6 |
+
st.markdown("Upload a video or select the example")
|
7 |
+
|
8 |
+
## Select video to inference
|
9 |
+
file_video = st.file_upload(" Upload a video ", type=["mp4"])
|
10 |
+
example = open("test_video.mp4")
|
11 |
+
st.video(example, width=250)
|
12 |
+
if st.button("example"):
|
13 |
+
file_video = "test_video.mp4"
|
14 |
+
|
15 |
+
##
|
16 |
+
if file_video is not None:
|
17 |
+
video = open(file_video)
|
18 |
+
video_bytes = video.read()
|
19 |
+
output = smartcities(video_bytes)
|
20 |
+
col1, col2 = st.columns(2)
|
21 |
+
|
22 |
+
if output is not None:
|
23 |
+
with col1:
|
24 |
+
st.subheader("Input: ")
|
25 |
+
st.video(video_bytes)
|
26 |
+
with col2:
|
27 |
+
st.subheader("Output: ")
|
28 |
+
st.video(output)
|
29 |
+
|
30 |
+
|
31 |
+
|
32 |
+
|
33 |
+
|
centroidtracker.py
ADDED
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# import the necessary packages
|
2 |
+
from scipy.spatial import distance as dist
|
3 |
+
from collections import OrderedDict
|
4 |
+
import numpy as np
|
5 |
+
|
6 |
+
class CentroidTracker:
|
7 |
+
def __init__(self, maxDisappeared=50, maxDistance=50):
|
8 |
+
# initialize the next unique object ID along with two ordered
|
9 |
+
# dictionaries used to keep track of mapping a given object
|
10 |
+
# ID to its centroid and number of consecutive frames it has
|
11 |
+
# been marked as "disappeared", respectively
|
12 |
+
self.nextObjectID = 0
|
13 |
+
self.objects = OrderedDict()
|
14 |
+
self.disappeared = OrderedDict()
|
15 |
+
|
16 |
+
# store the number of maximum consecutive frames a given
|
17 |
+
# object is allowed to be marked as "disappeared" until we
|
18 |
+
# need to deregister the object from tracking
|
19 |
+
self.maxDisappeared = maxDisappeared
|
20 |
+
|
21 |
+
# store the maximum distance between centroids to associate
|
22 |
+
# an object -- if the distance is larger than this maximum
|
23 |
+
# distance we'll start to mark the object as "disappeared"
|
24 |
+
self.maxDistance = maxDistance
|
25 |
+
|
26 |
+
def register(self, centroid):
|
27 |
+
# when registering an object we use the next available object
|
28 |
+
# ID to store the centroid
|
29 |
+
self.objects[self.nextObjectID] = centroid
|
30 |
+
self.disappeared[self.nextObjectID] = 0
|
31 |
+
self.nextObjectID += 1
|
32 |
+
|
33 |
+
def deregister(self, objectID):
|
34 |
+
# to deregister an object ID we delete the object ID from
|
35 |
+
# both of our respective dictionaries
|
36 |
+
del self.objects[objectID]
|
37 |
+
del self.disappeared[objectID]
|
38 |
+
|
39 |
+
def update(self, rects):
|
40 |
+
# check to see if the list of input bounding box rectangles
|
41 |
+
# is empty
|
42 |
+
if len(rects) == 0:
|
43 |
+
# loop over any existing tracked objects and mark them
|
44 |
+
# as disappeared
|
45 |
+
for objectID in list(self.disappeared.keys()):
|
46 |
+
self.disappeared[objectID] += 1
|
47 |
+
|
48 |
+
# if we have reached a maximum number of consecutive
|
49 |
+
# frames where a given object has been marked as
|
50 |
+
# missing, deregister it
|
51 |
+
if self.disappeared[objectID] > self.maxDisappeared:
|
52 |
+
self.deregister(objectID)
|
53 |
+
|
54 |
+
# return early as there are no centroids or tracking info
|
55 |
+
# to update
|
56 |
+
return self.objects
|
57 |
+
|
58 |
+
# initialize an array of input centroids for the current frame
|
59 |
+
inputCentroids = np.zeros((len(rects), 2), dtype="int")
|
60 |
+
|
61 |
+
# loop over the bounding box rectangles
|
62 |
+
for (i, (startX, startY, endX, endY)) in enumerate(rects):
|
63 |
+
# use the bounding box coordinates to derive the centroid
|
64 |
+
cX = int((startX + endX) / 2.0)
|
65 |
+
cY = int((startY + endY) / 2.0)
|
66 |
+
inputCentroids[i] = (cX, cY)
|
67 |
+
|
68 |
+
# if we are currently not tracking any objects take the input
|
69 |
+
# centroids and register each of them
|
70 |
+
if len(self.objects) == 0:
|
71 |
+
for i in range(0, len(inputCentroids)):
|
72 |
+
self.register(inputCentroids[i])
|
73 |
+
|
74 |
+
# otherwise, are are currently tracking objects so we need to
|
75 |
+
# try to match the input centroids to existing object
|
76 |
+
# centroids
|
77 |
+
else:
|
78 |
+
# grab the set of object IDs and corresponding centroids
|
79 |
+
objectIDs = list(self.objects.keys())
|
80 |
+
objectCentroids = list(self.objects.values())
|
81 |
+
|
82 |
+
# compute the distance between each pair of object
|
83 |
+
# centroids and input centroids, respectively -- our
|
84 |
+
# goal will be to match an input centroid to an existing
|
85 |
+
# object centroid
|
86 |
+
D = dist.cdist(np.array(objectCentroids), inputCentroids)
|
87 |
+
|
88 |
+
# in order to perform this matching we must (1) find the
|
89 |
+
# smallest value in each row and then (2) sort the row
|
90 |
+
# indexes based on their minimum values so that the row
|
91 |
+
# with the smallest value as at the *front* of the index
|
92 |
+
# list
|
93 |
+
rows = D.min(axis=1).argsort()
|
94 |
+
|
95 |
+
# next, we perform a similar process on the columns by
|
96 |
+
# finding the smallest value in each column and then
|
97 |
+
# sorting using the previously computed row index list
|
98 |
+
cols = D.argmin(axis=1)[rows]
|
99 |
+
|
100 |
+
# in order to determine if we need to update, register,
|
101 |
+
# or deregister an object we need to keep track of which
|
102 |
+
# of the rows and column indexes we have already examined
|
103 |
+
usedRows = set()
|
104 |
+
usedCols = set()
|
105 |
+
|
106 |
+
# loop over the combination of the (row, column) index
|
107 |
+
# tuples
|
108 |
+
for (row, col) in zip(rows, cols):
|
109 |
+
# if we have already examined either the row or
|
110 |
+
# column value before, ignore it
|
111 |
+
if row in usedRows or col in usedCols:
|
112 |
+
continue
|
113 |
+
|
114 |
+
# if the distance between centroids is greater than
|
115 |
+
# the maximum distance, do not associate the two
|
116 |
+
# centroids to the same object
|
117 |
+
if D[row, col] > self.maxDistance:
|
118 |
+
continue
|
119 |
+
|
120 |
+
# otherwise, grab the object ID for the current row,
|
121 |
+
# set its new centroid, and reset the disappeared
|
122 |
+
# counter
|
123 |
+
objectID = objectIDs[row]
|
124 |
+
self.objects[objectID] = inputCentroids[col]
|
125 |
+
self.disappeared[objectID] = 0
|
126 |
+
|
127 |
+
# indicate that we have examined each of the row and
|
128 |
+
# column indexes, respectively
|
129 |
+
usedRows.add(row)
|
130 |
+
usedCols.add(col)
|
131 |
+
|
132 |
+
# compute both the row and column index we have NOT yet
|
133 |
+
# examined
|
134 |
+
unusedRows = set(range(0, D.shape[0])).difference(usedRows)
|
135 |
+
unusedCols = set(range(0, D.shape[1])).difference(usedCols)
|
136 |
+
|
137 |
+
# in the event that the number of object centroids is
|
138 |
+
# equal or greater than the number of input centroids
|
139 |
+
# we need to check and see if some of these objects have
|
140 |
+
# potentially disappeared
|
141 |
+
if D.shape[0] >= D.shape[1]:
|
142 |
+
# loop over the unused row indexes
|
143 |
+
for row in unusedRows:
|
144 |
+
# grab the object ID for the corresponding row
|
145 |
+
# index and increment the disappeared counter
|
146 |
+
objectID = objectIDs[row]
|
147 |
+
self.disappeared[objectID] += 1
|
148 |
+
|
149 |
+
# check to see if the number of consecutive
|
150 |
+
# frames the object has been marked "disappeared"
|
151 |
+
# for warrants deregistering the object
|
152 |
+
if self.disappeared[objectID] > self.maxDisappeared:
|
153 |
+
self.deregister(objectID)
|
154 |
+
|
155 |
+
# otherwise, if the number of input centroids is greater
|
156 |
+
# than the number of existing object centroids we need to
|
157 |
+
# register each new input centroid as a trackable object
|
158 |
+
else:
|
159 |
+
for col in unusedCols:
|
160 |
+
self.register(inputCentroids[col])
|
161 |
+
|
162 |
+
# return the set of trackable objects
|
163 |
+
return self.objects
|
prediction.py
ADDED
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import tensorflow as tf
|
3 |
+
import numpy as np
|
4 |
+
import imutils
|
5 |
+
import time
|
6 |
+
import dlib
|
7 |
+
import cv2
|
8 |
+
from PIL import Image
|
9 |
+
import matplotlib.pyplot as plt
|
10 |
+
from imutils.video import VideoStream
|
11 |
+
from imutils.video import FPS
|
12 |
+
from centroidtracker import CentroidTracker
|
13 |
+
from trackableobject import TrackableObject
|
14 |
+
# import base64
|
15 |
+
|
16 |
+
|
17 |
+
class smartcities:
|
18 |
+
def __init__(self):
|
19 |
+
detect_fn = tf.saved_model.load("model/saved_model")
|
20 |
+
self.detect_fn = detect_fn
|
21 |
+
|
22 |
+
def predict(self):
|
23 |
+
# Ruta del video (Se debe cargar de manera manual)
|
24 |
+
PATH_VIDEO = "/tmp/in_video.mp4"
|
25 |
+
video_result = open(PATH_VIDEO, "wb")
|
26 |
+
# video_result.write(base64.b64decode(image_64_decode))
|
27 |
+
|
28 |
+
# Ruta del video en donde almacenaremos los resultados
|
29 |
+
PATH_OUTPUT = "/tmp/video_out.mp4"
|
30 |
+
|
31 |
+
# Cuántos frames vamos a saltarnos (Durante estos frames nuestro algoritmo de seguimiento funciona)
|
32 |
+
SKIP_FPS = 30
|
33 |
+
|
34 |
+
# Cuál será el umbral mínimo par que se considere una detección
|
35 |
+
TRESHOLD = 0.5
|
36 |
+
|
37 |
+
# Cargamos el video
|
38 |
+
vs = cv2.VideoCapture(PATH_VIDEO)
|
39 |
+
|
40 |
+
# Inicializamos el writer para poder guardar el video
|
41 |
+
writer = None
|
42 |
+
|
43 |
+
# Definimos ancho y alto
|
44 |
+
W = int(vs.get(cv2.CAP_PROP_FRAME_WIDTH))
|
45 |
+
H = int(vs.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
46 |
+
|
47 |
+
# Inicializamos la clase centroid tracker con dos variable fundamentales
|
48 |
+
# maxDissapared (Si pasa ese tiempo y no se detecta más el centroide lo elimina)
|
49 |
+
# Si la distancia es mayor a maxDistance no lo podra asociar como si fuera el mismo objecto.
|
50 |
+
ct = CentroidTracker(maxDisappeared= 40, maxDistance = 50)
|
51 |
+
|
52 |
+
# Inicializamos variables principales
|
53 |
+
trackers = []
|
54 |
+
trackableObjects = {}
|
55 |
+
|
56 |
+
totalFrame = 0
|
57 |
+
totalDown = 0
|
58 |
+
totalUp = 0
|
59 |
+
|
60 |
+
DIRECTION_PEOPLE = True
|
61 |
+
|
62 |
+
# Creamos un umbral para sabre si el carro paso de izquierda a derecha o viceversa
|
63 |
+
# En este caso lo deje fijo pero se pudiese configurar según la ubicación de la cámara.
|
64 |
+
POINT = [0, int((H/2)-H*0.1), W, int(H*0.1)]
|
65 |
+
|
66 |
+
# Los FPS nos van a permitir ver el rendimiento de nuestro modelo y si funciona en tiempo real.
|
67 |
+
fps = FPS().start()
|
68 |
+
|
69 |
+
# Definimos el formato del archivo resultante y las rutas.
|
70 |
+
fourcc = cv2.VideoWriter_fourcc(*'MP4V')
|
71 |
+
writer = cv2.VideoWriter(PATH_OUTPUT, fourcc, 20.0, (W, H), True)
|
72 |
+
|
73 |
+
# Bucle que recorre todo el video
|
74 |
+
while True:
|
75 |
+
# Leemos el primer frame
|
76 |
+
ret, frame = vs.read()
|
77 |
+
|
78 |
+
# Si ya no hay más frame, significa que el video termino y por tanto se sale del bucle
|
79 |
+
if frame is None:
|
80 |
+
break
|
81 |
+
|
82 |
+
status = "Waiting"
|
83 |
+
rects = []
|
84 |
+
|
85 |
+
# Nos saltamos los frames especificados.
|
86 |
+
if totalFrame % SKIP_FPS == 0:
|
87 |
+
status = "Detecting"
|
88 |
+
trackers = []
|
89 |
+
# Tomamos la imagen la convertimos a array luego a tensor
|
90 |
+
image_np = np.array(frame)
|
91 |
+
|
92 |
+
input_tensor = tf.convert_to_tensor(image_np)
|
93 |
+
input_tensor = input_tensor[tf.newaxis, ...]
|
94 |
+
|
95 |
+
# Predecimos los objectos y clases de la imagen
|
96 |
+
detections = self.detect_fn(input_tensor)
|
97 |
+
|
98 |
+
detection_scores = np.array(detections["detection_scores"][0])
|
99 |
+
# Realizamos una limpieza para solo obtener las clasificaciones mayores al umbral.
|
100 |
+
detection_clean = [x for x in detection_scores if x >= TRESHOLD]
|
101 |
+
|
102 |
+
# Recorremos las detecciones
|
103 |
+
for x in range(len(detection_clean)):
|
104 |
+
idx = int(detections['detection_classes'][0][x])
|
105 |
+
# Tomamos los bounding box
|
106 |
+
ymin, xmin, ymax, xmax = np.array(detections['detection_boxes'][0][x])
|
107 |
+
box = [xmin, ymin, xmax, ymax] * np.array([W,H, W, H])
|
108 |
+
|
109 |
+
(startX, startY, endX, endY) = box.astype("int")
|
110 |
+
|
111 |
+
# Con la función de dlib empezamos a hacer seguimiento de los boudiung box obtenidos
|
112 |
+
tracker = dlib.correlation_tracker()
|
113 |
+
rect = dlib.rectangle(startX, startY, endX, endY)
|
114 |
+
tracker.start_track(frame, rect)
|
115 |
+
|
116 |
+
trackers.append(tracker)
|
117 |
+
else:
|
118 |
+
# En caso de que no hagamos detección haremos seguimiento
|
119 |
+
# Recorremos los objetos que se les está realizando seguimiento
|
120 |
+
for tracker in trackers:
|
121 |
+
status = "Tracking"
|
122 |
+
# Actualizamos y buscamos los nuevos bounding box
|
123 |
+
tracker.update(frame)
|
124 |
+
pos = tracker.get_position()
|
125 |
+
|
126 |
+
startX = int(pos.left())
|
127 |
+
startY = int(pos.top())
|
128 |
+
endX = int(pos.right())
|
129 |
+
endY = int(pos.bottom())
|
130 |
+
|
131 |
+
rects.append((startX, startY, endX, endY))
|
132 |
+
|
133 |
+
# Dibujamos el umbral de conteo
|
134 |
+
cv2.rectangle(frame, (POINT[0], POINT[1]), (POINT[0]+ POINT[2], POINT[1] + POINT[3]), (255, 0, 255), 2)
|
135 |
+
|
136 |
+
objects = ct.update(rects)
|
137 |
+
|
138 |
+
# Recorremos cada una de las detecciones
|
139 |
+
for (objectID, centroid) in objects.items():
|
140 |
+
# Revisamos si el objeto ya se ha contado
|
141 |
+
to = trackableObjects.get(objectID, None)
|
142 |
+
if to is None:
|
143 |
+
to = TrackableObject(objectID, centroid)
|
144 |
+
|
145 |
+
else:
|
146 |
+
# Si no se ha contado, analizamos la dirección del objeto
|
147 |
+
y = [c[1] for c in to.centroids]
|
148 |
+
direction = centroid[1] - np.mean(y)
|
149 |
+
to.centroids.append(centroid)
|
150 |
+
if not to.counted:
|
151 |
+
if centroid[0] > POINT[0] and centroid[0] < (POINT[0]+ POINT[2]) and centroid[1] > POINT[1] and centroid[1] < (POINT[1]+POINT[3]):
|
152 |
+
if DIRECTION_PEOPLE:
|
153 |
+
if direction >0:
|
154 |
+
totalUp += 1
|
155 |
+
to.counted = True
|
156 |
+
else:
|
157 |
+
totalDown +=1
|
158 |
+
to.counted = True
|
159 |
+
else:
|
160 |
+
if direction <0:
|
161 |
+
totalUp += 1
|
162 |
+
to.counted = True
|
163 |
+
else:
|
164 |
+
totalDown +=1
|
165 |
+
to.counted = True
|
166 |
+
|
167 |
+
trackableObjects[objectID] = to
|
168 |
+
|
169 |
+
# Dibujamos el centroide y el ID de la detección encontrada
|
170 |
+
text = "ID {}".format(objectID)
|
171 |
+
cv2.putText(frame, text, (centroid[0]-10, centroid[1]-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 2)
|
172 |
+
cv2.circle(frame, (centroid[0], centroid[1]), 4, (0,255,0), -1)
|
173 |
+
|
174 |
+
# Totalizamos los resultados finales
|
175 |
+
info = [
|
176 |
+
("Subiendo", totalUp),
|
177 |
+
("Bajando", totalDown),
|
178 |
+
("Estado", status),
|
179 |
+
]
|
180 |
+
|
181 |
+
for (i, (k,v)) in enumerate(info):
|
182 |
+
text = "{}: {}".format(k,v)
|
183 |
+
cv2.putText(frame, text, (10, H - ((i*20) + 20)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0,0,255), 2)
|
184 |
+
|
185 |
+
# Almacenamos el framme en nuestro video resultante.
|
186 |
+
writer.write(frame)
|
187 |
+
totalFrame += 1
|
188 |
+
fps.update()
|
189 |
+
|
190 |
+
# Terminamos de analizar FPS y mostramos resultados finales
|
191 |
+
fps.stop()
|
192 |
+
|
193 |
+
print("Tiempo completo {}".format(fps.elapsed()))
|
194 |
+
print("Tiempo aproximado por frame {}".format(fps.fps()))
|
195 |
+
|
196 |
+
# Cerramos el stream the almacenar video y de consumir el video.
|
197 |
+
writer.release()
|
198 |
+
vs.release()
|
199 |
+
|
200 |
+
video = open(PATH_OUTPUT, "rb")
|
201 |
+
video_read = video.read()
|
202 |
+
|
203 |
+
return video_read
|
requirements.txt
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
tf_slim
|
2 |
+
tf-models-official
|
3 |
+
lvis
|
4 |
+
Cython
|
5 |
+
contextlib2
|
6 |
+
pillow
|
7 |
+
lxml
|
8 |
+
matplotlib
|
9 |
+
pycocotools
|
10 |
+
imutils
|
11 |
+
numpy
|
12 |
+
dlib
|
13 |
+
opencv-contrib-python
|
trackableobject.py
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
class TrackableObject:
|
3 |
+
def __init__(self, objectID, centroid):
|
4 |
+
# store the object ID, then initialize a list of centroids
|
5 |
+
# using the current centroid
|
6 |
+
self.objectID = objectID
|
7 |
+
self.centroids = [centroid]
|
8 |
+
|
9 |
+
# initialize a boolean used to indicate if the object has
|
10 |
+
# already been counted or not
|
11 |
+
self.counted = False
|