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
Files changed (1) hide show
  1. LaserPerception/LaserPerception.js +74 -61
LaserPerception/LaserPerception.js CHANGED
@@ -2697,24 +2697,15 @@
2697
  return [];
2698
  }
2699
  if (isHfMode(mode)) {
2700
- if (!state.detector.hfTrackingWarned) {
2701
- state.detector.hfTrackingWarned = true;
2702
- log("HF mode active: Using local COCO model for live tracking (fallback).", "t");
2703
- }
2704
- // Fallback to COCO for live tracking so Radar/Cards aren't empty
2705
- await ensureCocoDetector();
2706
- if (state.detector.model) {
2707
- try {
2708
- let preds = await state.detector.model.detect(videoEngage);
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
- if (best && bestI >= 0.18) {
 
2774
  used.add(bestIdx);
2775
 
2776
- // velocity estimate
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
- tr.vx = (cx1 - cx0) / Math.max(1e-6, dtSec);
2782
- tr.vy = (cy1 - cy0) / Math.max(1e-6, dtSec);
 
 
 
 
 
2783
 
2784
  // smooth bbox update
2785
- tr.bbox.x = lerp(tr.bbox.x, best.bbox.x, 0.65);
2786
- tr.bbox.y = lerp(tr.bbox.y, best.bbox.y, 0.65);
2787
- tr.bbox.w = lerp(tr.bbox.w, best.bbox.w, 0.55);
2788
- tr.bbox.h = lerp(tr.bbox.h, best.bbox.h, 0.55);
 
 
 
 
 
 
 
 
 
 
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
- for (let i = 0; i < detObjs.length; i++) {
2802
- if (used.has(i)) continue;
2803
- // create new track only if big enough (avoid clutter)
2804
- const a = detObjs[i].bbox.w * detObjs[i].bbox.h;
2805
- if (a < (w * h) * 0.0025) continue;
2806
-
2807
- const newId = `T${String(state.tracker.nextId++).padStart(2, "0")}`;
2808
- const ap = defaultAimpoint(detObjs[i].label);
2809
- tracks.push({
2810
- id: newId,
2811
- label: detObjs[i].label,
2812
- bbox: { ...detObjs[i].bbox },
2813
- score: detObjs[i].score,
2814
- aimRel: { relx: ap.relx, rely: ap.rely, label: ap.label },
2815
- baseAreaFrac: (detObjs[i].bbox.w * detObjs[i].bbox.h) / (w * h),
2816
- baseRange_m: +rangeBase.value,
2817
- baseDwell_s: 5.5,
2818
- reqP_kW: 42,
2819
- // Depth visualization only, GPT handles distance
2820
- depth_rel: detObjs[i].depth_rel,
2821
- // GPT properties (will be populated by updateTracksWithGPT)
2822
- gpt_distance_m: null,
2823
- gpt_direction: null,
2824
- gpt_description: null,
2825
- // Track state
2826
- lastSeen: now(),
2827
- vx: 0, vy: 0,
2828
- dwellAccum: 0,
2829
- killed: false,
2830
- state: "TRACK",
2831
- assessT: 0
2832
- });
2833
- log(`New track created: ${newId} (${detObjs[i].label})`, "t");
 
 
 
2834
  }
2835
 
2836
  // prune old tracks if they disappear
2837
  const tNow = now();
2838
- state.tracker.tracks = tracks.filter(tr => (tNow - tr.lastSeen) < 2200 || tr.killed);
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) {