@camera.ui/rust-postprocessor 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.yarnrc.yml +1 -0
- package/CHANGELOG.md +8 -0
- package/CONTRIBUTING.md +1 -0
- package/Cargo.toml +29 -0
- package/README.md +5 -0
- package/build.rs +5 -0
- package/index.d.ts +254 -0
- package/index.js +585 -0
- package/package.json +86 -0
- package/rust-toolchain.toml +2 -0
- package/src/iou.rs +83 -0
- package/src/lib.rs +471 -0
- package/src/line_crossing.rs +214 -0
- package/src/merge.rs +312 -0
- package/src/nms.rs +296 -0
- package/src/tracker.rs +1064 -0
- package/src/types.rs +40 -0
- package/src/zone_filter.rs +567 -0
package/.yarnrc.yml
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
nodeLinker: node-modules
|
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
All notable changes to this project will be documented in this file.
|
|
2
|
+
|
|
3
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
4
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
5
|
+
|
|
6
|
+
## [X.X.X] - ???
|
|
7
|
+
|
|
8
|
+
- Initial Release
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Contributing
|
package/Cargo.toml
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
edition = "2021"
|
|
3
|
+
name = "cameraui-rust-postprocessor"
|
|
4
|
+
version = "0.0.0"
|
|
5
|
+
|
|
6
|
+
[lib]
|
|
7
|
+
crate-type = ["cdylib"]
|
|
8
|
+
|
|
9
|
+
[dependencies]
|
|
10
|
+
napi = { version = "3.8.4", default-features = false, features = ["napi9"] }
|
|
11
|
+
napi-derive = "3.5.3"
|
|
12
|
+
|
|
13
|
+
# Tracker engine — Rust port of the Python `norfair` library used by Frigate.
|
|
14
|
+
# Same battle-tested algorithm: Kalman filter, IoU/Euclidean distance, ReID lifecycle.
|
|
15
|
+
norfair-rs = { version = "0.4.1", default-features = false }
|
|
16
|
+
|
|
17
|
+
# Stable nalgebra for the few places we construct DMatrix ourselves
|
|
18
|
+
nalgebra = "0.34.2"
|
|
19
|
+
|
|
20
|
+
# SIMD primitives for our NMS implementation (f32x8 = 8 floats parallel).
|
|
21
|
+
wide = "1.2.0"
|
|
22
|
+
|
|
23
|
+
[build-dependencies]
|
|
24
|
+
napi-build = "2.3.1"
|
|
25
|
+
|
|
26
|
+
[profile.release]
|
|
27
|
+
lto = true
|
|
28
|
+
strip = "symbols"
|
|
29
|
+
codegen-units = 1
|
package/README.md
ADDED
package/build.rs
ADDED
package/index.d.ts
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
/* auto-generated by NAPI-RS */
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
/**
|
|
4
|
+
* Multi-class IoU + Kalman object tracker.
|
|
5
|
+
*
|
|
6
|
+
* Wraps `norfair-rs` with one sub-tracker per class label. Track ids are
|
|
7
|
+
* stable across frames and globally unique across classes. The tracker is
|
|
8
|
+
* resolution-independent — feed normalized `[0.0, 1.0]` coordinates.
|
|
9
|
+
*/
|
|
10
|
+
export declare class ObjectTracker {
|
|
11
|
+
constructor(options?: ObjectTrackerOptions | undefined | null)
|
|
12
|
+
/**
|
|
13
|
+
* Process one frame's detections and return active tracks plus any
|
|
14
|
+
* line-crossing events that fired this frame. Detections without
|
|
15
|
+
* overlap to existing tracks spawn new ones; existing tracks without a
|
|
16
|
+
* matching detection are kept alive via Kalman extrapolation until
|
|
17
|
+
* `hitCounterMax` frames have elapsed.
|
|
18
|
+
*
|
|
19
|
+
* `timestampMs` is forwarded onto emitted crossing events for sequencing
|
|
20
|
+
* — pass `Date.now()` (or any monotonic millisecond clock).
|
|
21
|
+
*/
|
|
22
|
+
update(detections: Array<Detection>, timestampMs: number): UpdateResult
|
|
23
|
+
/**
|
|
24
|
+
* Replace the configured crossing lines. Pass an empty array to disable
|
|
25
|
+
* line crossings entirely. The `aspectRatio` is the camera's
|
|
26
|
+
* `width / height` so the perpendicular crossing line ends up visually
|
|
27
|
+
* perpendicular to the handle the user drew.
|
|
28
|
+
*
|
|
29
|
+
* Crossing memory is cleared on every reconfigure so existing tracks
|
|
30
|
+
* will fire again the moment they cross a freshly-edited line.
|
|
31
|
+
*/
|
|
32
|
+
setLines(lines: Array<DetectionLine>, aspectRatio: number): void
|
|
33
|
+
/**
|
|
34
|
+
* Replace the configured detection zones (privacy masks + active
|
|
35
|
+
* include/exclude regions). Pass an empty array to disable zone
|
|
36
|
+
* filtering entirely. Coordinates are in `[0, 100]` UI space — the
|
|
37
|
+
* tracker normalizes them internally and auto-closes polygons.
|
|
38
|
+
*
|
|
39
|
+
* The filter runs at the start of every `update()` call before
|
|
40
|
+
* detections reach the underlying tracker.
|
|
41
|
+
*/
|
|
42
|
+
setZones(zones: Array<DetectionZone>): void
|
|
43
|
+
/**
|
|
44
|
+
* Set the minimum detection confidence threshold. Detections below
|
|
45
|
+
* this score are dropped at the start of every `update()` call before
|
|
46
|
+
* they reach the zone filter or tracker. Default 0.0 (no threshold).
|
|
47
|
+
*/
|
|
48
|
+
setMinConfidence(minConfidence: number): void
|
|
49
|
+
/**
|
|
50
|
+
* Set how many frames a dead track stays available for ReID re-matching.
|
|
51
|
+
* When a new detection appears near a dead track's last position,
|
|
52
|
+
* norfair merges them — the old track ID is preserved with fresh
|
|
53
|
+
* Kalman state. Pass 0 to disable ReID. Use to tie track re-ID
|
|
54
|
+
* window to an external cascade timeout (e.g. `cascadeTimeout * fps`).
|
|
55
|
+
*/
|
|
56
|
+
setReidHitCounterMax(frames: number): void
|
|
57
|
+
/**
|
|
58
|
+
* Refresh the ReID counter for all dead tracks back to max. Call every
|
|
59
|
+
* frame while a cascade is active so dead tracks never expire during
|
|
60
|
+
* the cascade window. Stop calling when the cascade ends.
|
|
61
|
+
*/
|
|
62
|
+
refreshReid(): void
|
|
63
|
+
/**
|
|
64
|
+
* Apply the configured zones + confidence threshold to a list of
|
|
65
|
+
* detections WITHOUT advancing the tracker state. Returns the indices
|
|
66
|
+
* (positions in the input array) of the detections that pass the
|
|
67
|
+
* filter. Used for the external-sensor-write path where a plugin
|
|
68
|
+
* reports detections directly and we want to apply zone filtering
|
|
69
|
+
* without running the full tracker.
|
|
70
|
+
*/
|
|
71
|
+
filterIndices(detections: Array<Detection>): Array<number>
|
|
72
|
+
/**
|
|
73
|
+
* Drop every active track. The next `update()` starts from a clean
|
|
74
|
+
* slate and track ids restart from 1. Use this to align tracker state
|
|
75
|
+
* with external segment boundaries (e.g. cascade activate/deactivate).
|
|
76
|
+
*/
|
|
77
|
+
reset(): void
|
|
78
|
+
/** Total number of active tracks across all class buckets. */
|
|
79
|
+
get trackCount(): number
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Bounding box used by [`box_iou`]. */
|
|
83
|
+
export interface BoundingBox {
|
|
84
|
+
x: number
|
|
85
|
+
y: number
|
|
86
|
+
width: number
|
|
87
|
+
height: number
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Compute IoU between two normalized `[x, y, width, height]` boxes. */
|
|
91
|
+
export declare function boxIou(a: BoundingBox, b: BoundingBox): number
|
|
92
|
+
|
|
93
|
+
/** Detection in normalized image coordinates `[0.0, 1.0]`. */
|
|
94
|
+
export interface Detection {
|
|
95
|
+
x: number
|
|
96
|
+
y: number
|
|
97
|
+
width: number
|
|
98
|
+
height: number
|
|
99
|
+
confidence: number
|
|
100
|
+
label: string
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* User-facing crossing line definition.
|
|
105
|
+
*
|
|
106
|
+
* `points` are the two handle endpoints in `[0, 100]` UI coordinates.
|
|
107
|
+
* `labels` is the set of allowed detection labels — empty means "any label".
|
|
108
|
+
*/
|
|
109
|
+
export interface DetectionLine {
|
|
110
|
+
name: string
|
|
111
|
+
direction: LineDirection
|
|
112
|
+
labels: Array<string>
|
|
113
|
+
points: Array<Array<number>>
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* User-facing detection zone definition (matches the SDK shape).
|
|
118
|
+
*
|
|
119
|
+
* `points` are polygon vertices in `[0, 100]` UI coordinates. The
|
|
120
|
+
* polygon is auto-closed so the caller does not need to repeat the
|
|
121
|
+
* first vertex at the end.
|
|
122
|
+
*/
|
|
123
|
+
export interface DetectionZone {
|
|
124
|
+
labels: Array<string>
|
|
125
|
+
filter: ZoneFilterMode
|
|
126
|
+
/**
|
|
127
|
+
* Match mode (intersect vs contain). Mapped to `type` on the JS side
|
|
128
|
+
* of the SDK — the consumer is responsible for translating that field.
|
|
129
|
+
*/
|
|
130
|
+
matchType: ZoneMatchType
|
|
131
|
+
isPrivacyMask: boolean
|
|
132
|
+
points: Array<Array<number>>
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Crossing event emitted by [`ObjectTracker::update`]. */
|
|
136
|
+
export interface LineCrossingEvent {
|
|
137
|
+
lineName: string
|
|
138
|
+
direction: LineDirection
|
|
139
|
+
trackId: number
|
|
140
|
+
label: string
|
|
141
|
+
confidence: number
|
|
142
|
+
/** Frame timestamp in milliseconds (forwarded from the `update()` call). */
|
|
143
|
+
timestampMs: number
|
|
144
|
+
prevX: number
|
|
145
|
+
prevY: number
|
|
146
|
+
currX: number
|
|
147
|
+
currY: number
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Crossing line direction filter.
|
|
152
|
+
*
|
|
153
|
+
* `"both"` accepts either direction. `"a-to-b"` and `"b-to-a"` only fire
|
|
154
|
+
* when the track moves from one specific side to the other.
|
|
155
|
+
*/
|
|
156
|
+
export declare const enum LineDirection {
|
|
157
|
+
Both = 'both',
|
|
158
|
+
AToB = 'a-to-b',
|
|
159
|
+
BToA = 'b-to-a'
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Cluster nearby/overlapping same-label detections into single union
|
|
164
|
+
* boxes via union-find. Two boxes join the same cluster if their
|
|
165
|
+
* top-left corners are within `closeThreshold` along both axes OR if
|
|
166
|
+
* their IoU exceeds `iouThreshold`. Each cluster collapses to one
|
|
167
|
+
* `Detection` whose box covers the axis-aligned union of all members
|
|
168
|
+
* (clamped to `[0, 1]`) and whose confidence is the maximum in the
|
|
169
|
+
* cluster. Different labels are never clustered together.
|
|
170
|
+
*/
|
|
171
|
+
export declare function merge(detections: Array<Detection>, iouThreshold: number, closeThreshold: number): Array<Detection>
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Run greedy non-maximum suppression on a list of detections.
|
|
175
|
+
*
|
|
176
|
+
* Detections are suppressed only against higher-confidence boxes of the
|
|
177
|
+
* same `label`. Output is sorted by confidence descending. Pass
|
|
178
|
+
* `maxDetections` to cap the result length.
|
|
179
|
+
*/
|
|
180
|
+
export declare function nms(detections: Array<Detection>, iouThreshold: number, maxDetections?: number | undefined | null): Array<Detection>
|
|
181
|
+
|
|
182
|
+
/** Constructor options for [`ObjectTracker`]. */
|
|
183
|
+
export interface ObjectTrackerOptions {
|
|
184
|
+
/**
|
|
185
|
+
* IoU threshold above which a candidate is matched to an existing track.
|
|
186
|
+
* Higher = stricter matching. Default 0.3.
|
|
187
|
+
*/
|
|
188
|
+
iouThreshold?: number
|
|
189
|
+
/**
|
|
190
|
+
* Frames a track survives without a fresh detection (Kalman extrapolation
|
|
191
|
+
* continues during this window). Default 15.
|
|
192
|
+
*/
|
|
193
|
+
hitCounterMax?: number
|
|
194
|
+
/**
|
|
195
|
+
* Frames a new track must be matched before getting a permanent id —
|
|
196
|
+
* filters one-frame false positives. Default 3.
|
|
197
|
+
*/
|
|
198
|
+
initializationDelay?: number
|
|
199
|
+
/**
|
|
200
|
+
* Frames a dead track stays available for ReID re-matching. When a new
|
|
201
|
+
* detection appears near a dead track, norfair merges them — the old
|
|
202
|
+
* track ID is preserved. Default: disabled.
|
|
203
|
+
*/
|
|
204
|
+
reidHitCounterMax?: number
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Detection with stable identity assigned by [`ObjectTracker`]. */
|
|
208
|
+
export interface TrackedDetection {
|
|
209
|
+
x: number
|
|
210
|
+
y: number
|
|
211
|
+
width: number
|
|
212
|
+
height: number
|
|
213
|
+
confidence: number
|
|
214
|
+
label: string
|
|
215
|
+
/** Stable id across frames. Resets only on `ObjectTracker::reset()`. */
|
|
216
|
+
trackId: number
|
|
217
|
+
/** Number of frames this track has existed (1 on first emit). */
|
|
218
|
+
trackAge: number
|
|
219
|
+
/**
|
|
220
|
+
* True when the box is being kept alive by Kalman extrapolation
|
|
221
|
+
* instead of a fresh detection match this frame.
|
|
222
|
+
*/
|
|
223
|
+
trackLost: boolean
|
|
224
|
+
/**
|
|
225
|
+
* Average centroid speed in normalized units/second over a sliding
|
|
226
|
+
* ~1 s window of past positions. Consumers can compare against a
|
|
227
|
+
* threshold (typical: 0.05) to distinguish moving from stationary
|
|
228
|
+
* tracks. 0 when the track has only one sample so far.
|
|
229
|
+
*/
|
|
230
|
+
trackSpeed: number
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Result of [`ObjectTracker::update`] — active tracks plus any crossings
|
|
235
|
+
* fired this frame.
|
|
236
|
+
*/
|
|
237
|
+
export interface UpdateResult {
|
|
238
|
+
tracked: Array<TrackedDetection>
|
|
239
|
+
crossings: Array<LineCrossingEvent>
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** How an active zone affects matching detections. */
|
|
243
|
+
export declare const enum ZoneFilterMode {
|
|
244
|
+
Include = 'include',
|
|
245
|
+
Exclude = 'exclude'
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Where in the polygon the box must sit for the zone to apply. */
|
|
249
|
+
export declare const enum ZoneMatchType {
|
|
250
|
+
/** Any overlap with the polygon counts. */
|
|
251
|
+
Intersect = 'intersect',
|
|
252
|
+
/** All four corners of the box must be inside the polygon. */
|
|
253
|
+
Contain = 'contain'
|
|
254
|
+
}
|