Spaces:
Paused
Paused
Zhen Ye
commited on
Commit
·
811bd37
1
Parent(s):
d52f7cc
Fix tracking bugs: remove COCO fallback in HF mode, smooth velocity, and add track pruning
Browse files
LaserPerception/LaserPerception.js
CHANGED
|
@@ -2697,24 +2697,15 @@
|
|
| 2697 |
return [];
|
| 2698 |
}
|
| 2699 |
if (isHfMode(mode)) {
|
| 2700 |
-
|
| 2701 |
-
|
| 2702 |
-
|
| 2703 |
-
|
| 2704 |
-
//
|
| 2705 |
-
|
| 2706 |
-
|
| 2707 |
-
|
| 2708 |
-
|
| 2709 |
-
return preds
|
| 2710 |
-
.filter(p => p.score >= 0.45)
|
| 2711 |
-
.slice(0, 18)
|
| 2712 |
-
.map(p => ({ bbox: p.bbox, class: p.class, score: p.score }));
|
| 2713 |
-
} catch (err) {
|
| 2714 |
-
// ignore security error for fallback
|
| 2715 |
-
return [];
|
| 2716 |
-
}
|
| 2717 |
-
}
|
| 2718 |
return [];
|
| 2719 |
}
|
| 2720 |
if (mode === "coco") {
|
|
@@ -2770,72 +2761,94 @@
|
|
| 2770 |
bestIdx = i;
|
| 2771 |
}
|
| 2772 |
}
|
| 2773 |
-
|
|
|
|
| 2774 |
used.add(bestIdx);
|
| 2775 |
|
| 2776 |
-
//
|
| 2777 |
const cx0 = tr.bbox.x + tr.bbox.w * 0.5;
|
| 2778 |
const cy0 = tr.bbox.y + tr.bbox.h * 0.5;
|
| 2779 |
const cx1 = best.bbox.x + best.bbox.w * 0.5;
|
| 2780 |
const cy1 = best.bbox.y + best.bbox.h * 0.5;
|
| 2781 |
-
|
| 2782 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2783 |
|
| 2784 |
// smooth bbox update
|
| 2785 |
-
tr.bbox.x = lerp(tr.bbox.x, best.bbox.x, 0.
|
| 2786 |
-
tr.bbox.y = lerp(tr.bbox.y, best.bbox.y, 0.
|
| 2787 |
-
tr.bbox.w = lerp(tr.bbox.w, best.bbox.w, 0.
|
| 2788 |
-
tr.bbox.h = lerp(tr.bbox.h, best.bbox.h, 0.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2789 |
|
| 2790 |
-
tr.label = best.label || tr.label;
|
| 2791 |
tr.score = best.score || tr.score;
|
| 2792 |
// Update depth visualization (not for distance)
|
| 2793 |
if (Number.isFinite(best.depth_rel)) {
|
| 2794 |
tr.depth_rel = best.depth_rel;
|
| 2795 |
}
|
| 2796 |
tr.lastSeen = now();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2797 |
}
|
| 2798 |
}
|
| 2799 |
|
| 2800 |
// add unmatched detections as new tracks (optional)
|
| 2801 |
-
|
| 2802 |
-
|
| 2803 |
-
|
| 2804 |
-
|
| 2805 |
-
|
| 2806 |
-
|
| 2807 |
-
|
| 2808 |
-
|
| 2809 |
-
|
| 2810 |
-
|
| 2811 |
-
|
| 2812 |
-
|
| 2813 |
-
|
| 2814 |
-
|
| 2815 |
-
|
| 2816 |
-
|
| 2817 |
-
|
| 2818 |
-
|
| 2819 |
-
|
| 2820 |
-
|
| 2821 |
-
|
| 2822 |
-
|
| 2823 |
-
|
| 2824 |
-
|
| 2825 |
-
|
| 2826 |
-
|
| 2827 |
-
|
| 2828 |
-
|
| 2829 |
-
|
| 2830 |
-
|
| 2831 |
-
|
| 2832 |
-
|
| 2833 |
-
|
|
|
|
|
|
|
|
|
|
| 2834 |
}
|
| 2835 |
|
| 2836 |
// prune old tracks if they disappear
|
| 2837 |
const tNow = now();
|
| 2838 |
-
state.tracker.tracks = tracks.filter(tr => (tNow - tr.lastSeen) <
|
| 2839 |
}
|
| 2840 |
|
| 2841 |
function predictTracks(dtSec) {
|
|
|
|
| 2697 |
return [];
|
| 2698 |
}
|
| 2699 |
if (isHfMode(mode)) {
|
| 2700 |
+
// In HF mode, we DO NOT fall back to local COCO for tracking.
|
| 2701 |
+
// Why? Because local COCO will overwrite high-quality labels (e.g. "drone")
|
| 2702 |
+
// with generic ones ("airplane"), breaking the user experience.
|
| 2703 |
+
// Instead, we rely on the tracker's predictive coasting (predictTracks)
|
| 2704 |
+
// or wait for sparse updates if we implement backend streaming later.
|
| 2705 |
+
|
| 2706 |
+
// For now, return empty to prevent label pollution.
|
| 2707 |
+
// The tracker will maintain existing tracks via coasting/prediction
|
| 2708 |
+
// until they time out or we get a new "Reason" update.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2709 |
return [];
|
| 2710 |
}
|
| 2711 |
if (mode === "coco") {
|
|
|
|
| 2761 |
bestIdx = i;
|
| 2762 |
}
|
| 2763 |
}
|
| 2764 |
+
// Strict matching threshold
|
| 2765 |
+
if (best && bestI >= 0.25) {
|
| 2766 |
used.add(bestIdx);
|
| 2767 |
|
| 2768 |
+
// Velocity with Exponential Moving Average (EMA) for smoothing
|
| 2769 |
const cx0 = tr.bbox.x + tr.bbox.w * 0.5;
|
| 2770 |
const cy0 = tr.bbox.y + tr.bbox.h * 0.5;
|
| 2771 |
const cx1 = best.bbox.x + best.bbox.w * 0.5;
|
| 2772 |
const cy1 = best.bbox.y + best.bbox.h * 0.5;
|
| 2773 |
+
|
| 2774 |
+
const rawVx = (cx1 - cx0) / Math.max(1e-3, dtSec);
|
| 2775 |
+
const rawVy = (cy1 - cy0) / Math.max(1e-3, dtSec);
|
| 2776 |
+
|
| 2777 |
+
// Alpha of 0.3 means 30% new value, 70% history
|
| 2778 |
+
tr.vx = tr.vx * 0.7 + rawVx * 0.3;
|
| 2779 |
+
tr.vy = tr.vy * 0.7 + rawVy * 0.3;
|
| 2780 |
|
| 2781 |
// smooth bbox update
|
| 2782 |
+
tr.bbox.x = lerp(tr.bbox.x, best.bbox.x, 0.7);
|
| 2783 |
+
tr.bbox.y = lerp(tr.bbox.y, best.bbox.y, 0.7);
|
| 2784 |
+
tr.bbox.w = lerp(tr.bbox.w, best.bbox.w, 0.6);
|
| 2785 |
+
tr.bbox.h = lerp(tr.bbox.h, best.bbox.h, 0.6);
|
| 2786 |
+
|
| 2787 |
+
// Logic: Only update label if the new detection is highly confident
|
| 2788 |
+
// AND the current track doesn't have a "premium" label (like 'drone').
|
| 2789 |
+
// This prevents COCO's 'airplane' from overwriting a custom 'drone' label.
|
| 2790 |
+
const protectedLabels = ["drone", "uav", "missile"];
|
| 2791 |
+
const isProtected = protectedLabels.some(l => (tr.label || "").toLowerCase().includes(l));
|
| 2792 |
+
|
| 2793 |
+
if (!isProtected || (best.label && protectedLabels.some(l => best.label.toLowerCase().includes(l)))) {
|
| 2794 |
+
tr.label = best.label || tr.label;
|
| 2795 |
+
}
|
| 2796 |
|
|
|
|
| 2797 |
tr.score = best.score || tr.score;
|
| 2798 |
// Update depth visualization (not for distance)
|
| 2799 |
if (Number.isFinite(best.depth_rel)) {
|
| 2800 |
tr.depth_rel = best.depth_rel;
|
| 2801 |
}
|
| 2802 |
tr.lastSeen = now();
|
| 2803 |
+
} else {
|
| 2804 |
+
// Decay velocity if not seen to prevent "coasting" into infinity
|
| 2805 |
+
tr.vx *= 0.9;
|
| 2806 |
+
tr.vy *= 0.9;
|
| 2807 |
}
|
| 2808 |
}
|
| 2809 |
|
| 2810 |
// add unmatched detections as new tracks (optional)
|
| 2811 |
+
// Limit total tracks to prevent memory leaks/ui lag
|
| 2812 |
+
if (tracks.length < 50) {
|
| 2813 |
+
for (let i = 0; i < detObjs.length; i++) {
|
| 2814 |
+
if (used.has(i)) continue;
|
| 2815 |
+
// create new track only if big enough (avoid clutter)
|
| 2816 |
+
const a = detObjs[i].bbox.w * detObjs[i].bbox.h;
|
| 2817 |
+
if (a < (w * h) * 0.0025) continue;
|
| 2818 |
+
|
| 2819 |
+
const newId = `T${String(state.tracker.nextId++).padStart(2, "0")}`;
|
| 2820 |
+
const ap = defaultAimpoint(detObjs[i].label);
|
| 2821 |
+
tracks.push({
|
| 2822 |
+
id: newId,
|
| 2823 |
+
label: detObjs[i].label,
|
| 2824 |
+
bbox: { ...detObjs[i].bbox },
|
| 2825 |
+
score: detObjs[i].score,
|
| 2826 |
+
aimRel: { relx: ap.relx, rely: ap.rely, label: ap.label },
|
| 2827 |
+
baseAreaFrac: (detObjs[i].bbox.w * detObjs[i].bbox.h) / (w * h),
|
| 2828 |
+
baseRange_m: +rangeBase.value,
|
| 2829 |
+
baseDwell_s: 5.5,
|
| 2830 |
+
reqP_kW: 42,
|
| 2831 |
+
// Depth visualization only, GPT handles distance
|
| 2832 |
+
depth_rel: detObjs[i].depth_rel,
|
| 2833 |
+
// GPT properties (will be populated by updateTracksWithGPT)
|
| 2834 |
+
gpt_distance_m: null,
|
| 2835 |
+
gpt_direction: null,
|
| 2836 |
+
gpt_description: null,
|
| 2837 |
+
// Track state
|
| 2838 |
+
lastSeen: now(),
|
| 2839 |
+
vx: 0, vy: 0,
|
| 2840 |
+
dwellAccum: 0,
|
| 2841 |
+
killed: false,
|
| 2842 |
+
state: "TRACK",
|
| 2843 |
+
assessT: 0
|
| 2844 |
+
});
|
| 2845 |
+
log(`New track created: ${newId} (${detObjs[i].label})`, "t");
|
| 2846 |
+
}
|
| 2847 |
}
|
| 2848 |
|
| 2849 |
// prune old tracks if they disappear
|
| 2850 |
const tNow = now();
|
| 2851 |
+
state.tracker.tracks = tracks.filter(tr => (tNow - tr.lastSeen) < 1500 || tr.killed);
|
| 2852 |
}
|
| 2853 |
|
| 2854 |
function predictTracks(dtSec) {
|