3
3
from collections import OrderedDict
4
4
import numpy as np
5
5
6
+
6
7
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
8
+ def __init__ (self , maxDisappeared = 50 , maxDistance = 50 ):
9
+ # initialize the next unique object ID along with two ordered
10
+ # dictionaries used to keep track of mapping a given object
11
+ # ID to its centroid and number of consecutive frames it has
12
+ # been marked as "disappeared", respectively
13
+ self .nextObjectID = 0
14
+ self .objects = OrderedDict ()
15
+ self .disappeared = OrderedDict ()
16
+
17
+ # store the number of maximum consecutive frames a given
18
+ # object is allowed to be marked as "disappeared" until we
19
+ # need to deregister the object from tracking
20
+ self .maxDisappeared = maxDisappeared
21
+
22
+ # store the maximum distance between centroids to associate
23
+ # an object -- if the distance is larger than this maximum
24
+ # distance we'll start to mark the object as "disappeared"
25
+ self .maxDistance = maxDistance
26
+
27
+ def register (self , centroid ):
28
+ # when registering an object we use the next available object
29
+ # ID to store the centroid
30
+ self .objects [self .nextObjectID ] = centroid
31
+ self .disappeared [self .nextObjectID ] = 0
32
+ self .nextObjectID += 1
33
+
34
+ def deregister (self , objectID ):
35
+ # to deregister an object ID we delete the object ID from
36
+ # both of our respective dictionaries
37
+ del self .objects [objectID ]
38
+ del self .disappeared [objectID ]
39
+
40
+ def update (self , rects ):
41
+ # check to see if the list of input bounding box rectangles
42
+ # is empty
43
+ if len (rects ) == 0 :
44
+ # loop over any existing tracked objects and mark them
45
+ # as disappeared
46
+ for objectID in list (self .disappeared .keys ()):
47
+ self .disappeared [objectID ] += 1
48
+
49
+ # if we have reached a maximum number of consecutive
50
+ # frames where a given object has been marked as
51
+ # missing, deregister it
52
+ if self .disappeared [objectID ] > self .maxDisappeared :
53
+ self .deregister (objectID )
54
+
55
+ # return early as there are no centroids or tracking info
56
+ # to update
57
+ return self .objects
58
+
59
+ # initialize an array of input centroids for the current frame
60
+ inputCentroids = np .zeros ((len (rects ), 2 ), dtype = "int" )
61
+
62
+ # loop over the bounding box rectangles
63
+ for (i , (startX , startY , endX , endY )) in enumerate (rects ):
64
+ # use the bounding box coordinates to derive the centroid
65
+ cX = int ((startX + endX ) / 2.0 )
66
+ cY = int ((startY + endY ) / 2.0 )
67
+ inputCentroids [i ] = (cX , cY )
68
+
69
+ # if we are currently not tracking any objects take the input
70
+ # centroids and register each of them
71
+ if len (self .objects ) == 0 :
72
+ for i in range (0 , len (inputCentroids )):
73
+ self .register (inputCentroids [i ])
74
+
75
+ # otherwise, are are currently tracking objects so we need to
76
+ # try to match the input centroids to existing object
77
+ # centroids
78
+ else :
79
+ # grab the set of object IDs and corresponding centroids
80
+ objectIDs = list (self .objects .keys ())
81
+ objectCentroids = list (self .objects .values ())
82
+
83
+ # compute the distance between each pair of object
84
+ # centroids and input centroids, respectively -- our
85
+ # goal will be to match an input centroid to an existing
86
+ # object centroid
87
+ D = dist .cdist (np .array (objectCentroids ), inputCentroids )
88
+
89
+ # in order to perform this matching we must (1) find the
90
+ # smallest value in each row and then (2) sort the row
91
+ # indexes based on their minimum values so that the row
92
+ # with the smallest value as at the *front* of the index
93
+ # list
94
+ rows = D .min (axis = 1 ).argsort ()
95
+
96
+ # next, we perform a similar process on the columns by
97
+ # finding the smallest value in each column and then
98
+ # sorting using the previously computed row index list
99
+ cols = D .argmin (axis = 1 )[rows ]
100
+
101
+ # in order to determine if we need to update, register,
102
+ # or deregister an object we need to keep track of which
103
+ # of the rows and column indexes we have already examined
104
+ usedRows = set ()
105
+ usedCols = set ()
106
+
107
+ # loop over the combination of the (row, column) index
108
+ # tuples
109
+ for (row , col ) in zip (rows , cols ):
110
+ # if we have already examined either the row or
111
+ # column value before, ignore it
112
+ if row in usedRows or col in usedCols :
113
+ continue
114
+
115
+ # if the distance between centroids is greater than
116
+ # the maximum distance, do not associate the two
117
+ # centroids to the same object
118
+ if D [row , col ] > self .maxDistance :
119
+ continue
120
+
121
+ # otherwise, grab the object ID for the current row,
122
+ # set its new centroid, and reset the disappeared
123
+ # counter
124
+ objectID = objectIDs [row ]
125
+ self .objects [objectID ] = inputCentroids [col ]
126
+ self .disappeared [objectID ] = 0
127
+
128
+ # indicate that we have examined each of the row and
129
+ # column indexes, respectively
130
+ usedRows .add (row )
131
+ usedCols .add (col )
132
+
133
+ # compute both the row and column index we have NOT yet
134
+ # examined
135
+ unusedRows = set (range (0 , D .shape [0 ])).difference (usedRows )
136
+ unusedCols = set (range (0 , D .shape [1 ])).difference (usedCols )
137
+
138
+ # in the event that the number of object centroids is
139
+ # equal or greater than the number of input centroids
140
+ # we need to check and see if some of these objects have
141
+ # potentially disappeared
142
+ if D .shape [0 ] >= D .shape [1 ]:
143
+ # loop over the unused row indexes
144
+ for row in unusedRows :
145
+ # grab the object ID for the corresponding row
146
+ # index and increment the disappeared counter
147
+ objectID = objectIDs [row ]
148
+ self .disappeared [objectID ] += 1
149
+
150
+ # check to see if the number of consecutive
151
+ # frames the object has been marked "disappeared"
152
+ # for warrants deregistering the object
153
+ if self .disappeared [objectID ] > self .maxDisappeared :
154
+ self .deregister (objectID )
155
+
156
+ # otherwise, if the number of input centroids is greater
157
+ # than the number of existing object centroids we need to
158
+ # register each new input centroid as a trackable object
159
+ else :
160
+ for col in unusedCols :
161
+ self .register (inputCentroids [col ])
162
+
163
+ # return the set of trackable objects
164
+ return self .objects
0 commit comments