Skip to content

Commit 233b6d1

Browse files
authored
Tracker (#764)
* Skeleton for abstract base class Tracker * Creating updateTracks() which cleans up track store based on track age. Also, updating property names to be mixed case instead of using underscores * Basic formatting updates. * Adding lowerCamelCase for all variables and a couple more formatting updates * Creating tracker_utils.ts to validate tracker config. * Making computeSimilarity() abstract to allow for batch implementations. Also fixing some comments and validation checks * Updating flow so that tracks are filtered by age prior to assignment with new detections * Updating the documentation for maxTracks to indicate that this should be set higher than maxPoses * Moving track filtering before the computation of similarity matrices
1 parent f0d12c9 commit 233b6d1

File tree

4 files changed

+173
-0
lines changed

4 files changed

+173
-0
lines changed

pose-detection/src/calculators/interfaces/common_interfaces.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import {Keypoint} from '../../types';
2+
import {BoundingBox} from './shape_interfaces';
23

34
/**
45
* @license
@@ -43,3 +44,13 @@ export interface KeypointsFilter {
4344
Keypoint[];
4445
reset(): void;
4546
}
47+
48+
export interface Track {
49+
id: number; // A unique identifier for each tracked person.
50+
lastTimestamp: number; // The last timestamp (in milliseconds) in which a
51+
// detection was linked with the track.
52+
keypoints?: Keypoint[]; // Keypoints associated with the tracked person.
53+
boundingBox?: BoundingBox; // Bounding box associated with the tracked
54+
// person.
55+
score?: number; // A confidence value of the track.
56+
}

pose-detection/src/calculators/interfaces/config_interfaces.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,3 +69,16 @@ export interface KeypointsSmoothingConfig {
6969
velocityFilter?: VelocityFilterConfig;
7070
oneEuroFilter?: OneEuroFilterConfig;
7171
}
72+
export interface TrackerConfig {
73+
maxTracks: number; // The maximum number of tracks that an internal tracker
74+
// will maintain. Note that this number should be set
75+
// larger than EstimationConfig.maxPoses. How to set this
76+
// number requires experimentation with a given detector,
77+
// but a good starting place is about 3 * maxPoses.
78+
maxAge: number; // The maximum duration of time (in milliseconds) that a
79+
// track can exist without being linked with a new detection
80+
// before it is removed. Set this value large if you would
81+
// like to recover people that are not detected for long
82+
// stretches of time (at the cost of potential false
83+
// re-identifications).
84+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC. All Rights Reserved.
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
* =============================================================================
16+
*/
17+
18+
import {Pose} from '../types';
19+
import {validateTrackerConfig} from './tracker_utils';
20+
import {Track} from './interfaces/common_interfaces';
21+
import {TrackerConfig} from './interfaces/config_interfaces';
22+
23+
/**
24+
* A stateful tracker for associating detections between frames. This is an
25+
* abstract base class that performs generic mechanics. Implementations must
26+
* inherit from this class.
27+
*/
28+
export abstract class Tracker {
29+
private tracks: Track[];
30+
private readonly maxTracks: number;
31+
private readonly maxAge: number;
32+
33+
constructor(config: TrackerConfig) {
34+
validateTrackerConfig(config);
35+
this.maxTracks = config.maxTracks;
36+
this.maxAge = config.maxAge;
37+
}
38+
39+
/**
40+
* Tracks person instances across frames based on detections.
41+
* @param poses An array of detected `Pose`s.
42+
* @param timestamp The timestamp associated with the incoming poses.
43+
* @returns An updated array of `Pose`s with tracking id properties.
44+
*/
45+
apply(
46+
poses: Pose[], timestamp: number): Pose[] {
47+
this.filterOldTracks(timestamp);
48+
const simMatrix = this.computeSimilarity(poses);
49+
this.assignTracks(poses, simMatrix, timestamp);
50+
this.updateTracks(timestamp);
51+
return poses;
52+
}
53+
54+
/**
55+
* Computes pairwise similarity scores between detections and tracks, based
56+
* on detected features.
57+
* @param poses An array of detected `Pose`s.
58+
* @returns A 2D array of shape [num_det, num_tracks] with pairwise
59+
* similarity scores between detections and tracks.
60+
*/
61+
abstract computeSimilarity(
62+
poses: Pose[]): number[][];
63+
64+
/**
65+
* Filters tracks based on their age.
66+
* @param timestamp The current timestamp in milliseconds.
67+
*/
68+
filterOldTracks(timestamp: number): void {
69+
this.tracks = this.tracks.filter(track => {
70+
return timestamp - track.lastTimestamp <= this.maxAge;
71+
});
72+
}
73+
74+
/**
75+
* Performs an optimization to link detections with tracks. The `poses`
76+
* array is updated in place by providing an `id` property. If incoming
77+
* detections are not linked with existing tracks, new tracks will be created.
78+
* @param poses An array of detected `Pose's.
79+
* @param simMatrix A 2D array of shape [num_det, num_tracks] with pairwise
80+
* similarity scores between detections and tracks.
81+
* @param timestamp The current timestamp in milliseconds.
82+
*/
83+
assignTracks(
84+
poses: Pose[], simMatrix: number[][], timestamp: number): void {
85+
//TODO: Implement optimization and track store mechanics.
86+
}
87+
88+
/**
89+
* Updates the stored tracks in the tracker. Specifically, the following
90+
* operations are applied in order:
91+
* 1. Tracks are sorted based on freshness (i.e. the most recently linked
92+
* tracks are placed at the beginning of the array and the most stale are
93+
* at the end).
94+
* 2. The tracks array is sliced to only contain `maxTracks` tracks (i.e. the
95+
* most fresh tracks).
96+
* @param timestamp The current timestamp in milliseconds.
97+
*/
98+
updateTracks(timestamp: number): void {
99+
// Sort tracks from most recent to most stale, and then only keep the top
100+
// `maxTracks` tracks.
101+
this.tracks.sort((ta, tb) => tb.lastTimestamp - ta.lastTimestamp);
102+
this.tracks = this.tracks.slice(0, this.maxTracks);
103+
}
104+
105+
/**
106+
* Removes specific tracks, based on their ids.
107+
*/
108+
remove(...ids: number[]): void {
109+
this.tracks = this.tracks.filter(track => !ids.includes(track.id));
110+
}
111+
112+
/**
113+
* Resets tracks.
114+
*/
115+
reset(): void {
116+
this.tracks = [];
117+
}
118+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* @license
3+
* Copyright 2021 Google LLC. All Rights Reserved.
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
* =============================================================================
16+
*/
17+
18+
import {TrackerConfig} from './interfaces/config_interfaces';
19+
20+
export function validateTrackerConfig(config: TrackerConfig): void {
21+
if (config.maxTracks < 1) {
22+
throw new Error(
23+
`Must specify 'maxTracks' to be at least 1, but ` +
24+
`encountered ${config.maxTracks}`);
25+
}
26+
if (config.maxAge <= 0) {
27+
throw new Error(
28+
`Must specify 'maxAge' to be positive, but ` +
29+
`encountered ${config.maxAge}`);
30+
}
31+
}

0 commit comments

Comments
 (0)