feat(perception): add camera ego-motion evidence

This commit is contained in:
DCCONSTRUCTIONS
2026-07-24 13:37:39 +03:00
parent 230cba4b21
commit 7fba39a629
11 changed files with 2348 additions and 0 deletions
@@ -0,0 +1,261 @@
# LAB E26 — camera ego-motion evidence and conservative LiDAR fusion
Date: 2026-07-24
Status: recorded diagnostic accepted; navigation and safety acceptance are false
Immutable replay:
`LAB E26.1 · Camera + ego-motion · full RAVNOVES00`
## Objective
E25 proved that persistent map-frame LiDAR support can stop parked vehicles
from looking dynamic, but it cannot recover motion when the K1 supplies too few
current LiDAR returns. E26 adds an independent camera measurement without
inventing metric velocity:
1. preserve the original detector track in image space;
2. remove the apparent motion caused by the moving rig;
3. test whether observations from several calibrated camera poses are
consistent with one static point in `k1-map`;
4. fuse the resulting relative camera state with E25 LiDAR evidence;
5. represent disagreement and missing evidence explicitly as `unknown`;
6. keep every unknown object occupied and outside navigation authority.
The experiment changes the measurement, not the visual smoothing of 3D boxes.
Rerun remains the operator viewer. The perception result is produced before
Rerun and can be consumed independently of it.
## Immutable inputs
- Source observation: `20260720T065719Z_viewer_live` (`RAVNOVES00`).
- Published lab session: `lab-e26-1-camera-ego-motion`.
- Camera: `sensor.camera.right`, factory calibration slot `camera_1`.
- Coordinate frame: `k1-map`.
- Frames: 4,489.
- Source timeline: 35.421857292484.044857292 s.
- Published replay duration: 448.623 s.
- Factory projection model: KB4.
- Calibration SHA-256:
`05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9`.
- Camera/detector source result:
`e10-integrated-perception-34ade557b5636717aa497fc00355b84df7063e483a175f2f5d3f04c03df1c898`.
- E25.3 LiDAR-evidence source result:
`e10-integrated-perception-9034a5cf306b379cc248c1da17639f140fc896bb55e412bef43f89974de892a3`.
- Detector: YOLOX-S, COCO-80, 640×640.
- Semantic source:
`tue-mps/cityscapes_semantic_eomt_large_1024`, FP16 autocast.
- E26 profile SHA-256:
`17668cea5a8bddd621271ed0457ca3a91e6d3957133d53213d6705d9b03d039e`.
- E26 benchmark SHA-256:
`c3523c8f1ca22631120a129d20c73a8429be8c908279cc1c3286661f33d5fe57`.
E26 does not repeat detector or segmentation inference. It reuses immutable
model outputs, E25 LiDAR evidence, rig poses and factory calibration. It writes
a new integrated result and hard-links immutable source payloads where
possible. The source results and raw sensor data are not modified.
## Camera measurement
For each original 2D detector track, E26 takes a bottom-centre image point with
a small configurable inset. The point is unprojected through the factory KB4
model into a three-dimensional camera ray. The calibrated camera-to-rig
extrinsic and the recorded rig pose transform that ray into `k1-map`.
Over a bounded two-second history, a least-squares ray intersection estimates
the one world point that would explain the observations if the target were
static. The tracker evaluates:
- median angular residual to the static-world hypothesis;
- p80 angular residual;
- positive-depth fraction;
- estimated range;
- rig baseline covered by the observations;
- observation count, time span, detector score and box size.
Low residuals support `static`. A well-observed track with residuals beyond the
class thresholds supports `dynamic`. A hypothesis behind the camera can support
`dynamic` only under the stricter score and repeated-observation gate.
Degenerate geometry, insufficient baseline, weak detections, missing pose or an
invalid KB4 edge ray yields `unknown`.
The camera branch measures relative inconsistency with a static world
hypothesis. It does not measure metric object velocity. Camera-only
`speed_mps` and `velocity_map_mps` are therefore deliberately null.
## Bounded streaming contract
The implementation is a single forward pass with no lookahead:
- history: at most 2.0 s;
- maximum 32 observations per camera track;
- maximum 256 live tracks;
- maximum idle time: 2.5 s;
- minimum observations: 5;
- minimum observation span: 0.4 s;
- minimum rig baseline: 0.25 m;
- maximum normal-matrix condition: 1,000,000;
- minimum box diagonal: 35 px;
- minimum median detector score: 0.60;
- dynamic confirmation: 2 frames;
- static confirmation: 4 frames;
- evidence decay: 1 frame.
The run reached only 34 simultaneous tracks. Five unprojectable KB4 edge rays
were rejected fail-soft and counted; detector and LiDAR objects remained in the
result as unavailable camera evidence.
## Fusion contract
Camera and LiDAR are treated as independent evidence sources:
- camera and LiDAR agree: publish the common state with conservative
confidence;
- camera and LiDAR disagree: publish explicit `unknown`, clear velocity and
increment the conflict counter;
- camera only: publish relative `static` or `dynamic` with confidence scaled by
0.65, but without metric velocity;
- LiDAR only: preserve the metric E25 state and velocity;
- neither source qualifies: publish `unknown`.
Every branch carries:
- `unknown_is_occupied=true`;
- `camera_only_metric_velocity_valid=false`;
- `navigation_or_safety_accepted=false`.
There is no command output and no planner authority in E26.
## Operator-reviewed benchmark
The benchmark contains eight dynamic target windows and three parked-vehicle
control windows. The dynamic windows are bound to reviewed source detector
track IDs, so a nearby parked object cannot satisfy a moving-object event.
Dynamic windows:
- woman with dog at 5462 s: 24 dynamic hits, 82.76% evidence coverage;
- person loading a car at 6269 s: best run 4 hits over 0.896 s, 81.82% coverage;
- adult and child at 111120 s: 26 hits over 4.387 s, 91.49% coverage;
- person on the road at 124132 s: 11 hits over 1.0 s, 76.19% coverage;
- stroller group at 151161 s: best run 5 hits over 0.498 s, 84.0% coverage;
- oncoming vehicle at 167173 s: 3 hits over 0.172 s, 73.33% coverage;
- vulnerable-road-user group at 170179 s: best run 5 hits over 0.38 s,
72.09% coverage;
- same-direction vehicle at 180188 s: 12 hits over 1.734 s, 100% coverage.
Parked-vehicle controls:
- 7090 s: 853 source observations, 732 evidence observations,
57.51% classified, 418 static / 311 unknown / 3 dynamic,
false-dynamic fraction 0.00410;
- 132148 s: 820 source observations, 746 evidence observations,
78.42% classified, 585 static / 161 unknown / 0 dynamic,
false-dynamic fraction 0.0;
- 161166 s: 151 source observations, 120 evidence observations,
62.5% classified, 75 static / 45 unknown / 0 dynamic,
false-dynamic fraction 0.0.
Result: 11/11 benchmark windows pass.
This is a useful regression result, not independent ground truth. The windows
come from operator timestamps and reviewed detector-track binding on the same
recording used to develop the experiment. The result does not measure global
precision, recall, ID-switch rate, distance error or safety performance outside
those windows.
## Runtime and resource result
Camera ego-motion postprocessor:
- mean: 1.982 ms/frame;
- p50: 2.059 ms/frame;
- p95: 3.499 ms/frame;
- maximum: 106.171 ms/frame;
- camera observations: 20,513;
- created tracks: 1,466;
- peak live tracks: 34;
- pose-unavailable frames: 561;
- invalid camera rays: 5;
- published camera states: 6,105 static, 1,117 dynamic, 13,291 unknown;
- explicit camera/LiDAR conflicts: 374.
The p95 satisfies the configured 12 ms budget for this postprocessor. It is not
an end-to-end real-time measurement: detector inference, semantic inference,
sensor transport, decoding, synchronization, Rerun serialization, network
transfer and browser rendering are not included.
## Replay acceptance
The saved session is `ready`, `replayable=true`, and visible through the single
existing server on `http://127.0.0.1:8000/`.
An acceptance request through the same perception endpoint used by the browser
returned:
- HTTP 200;
- media type `application/vnd.rerun.rrd`;
- Rerun magic `RRF2`;
- payload: 69,735,518 bytes;
- SHA-256:
`0d7978468f829acb6afe2aa14f6e2f2f96d55ee35e21d8439cdd3ec3bdd107b1`;
- cold response time: 47.172 s.
The 47.172 s cold Rerun materialization is an operator-path cost, not perception
compute. It remains too slow for first connection in the field and must be
addressed separately through prebuilt/cached, chunked or live incremental
delivery. It does not invalidate the 3.499 ms motion-evidence p95, and the
3.499 ms number does not excuse the 47.172 s operator wait.
The server was not stopped, restarted or moved to another port during
acceptance.
## Validation
- Ruff passes for `src`, `tests` and `experiments/perception`.
- Strict mypy passes for the E26 compute, publication, calibration and CLI
modules.
- 44 targeted E10/E24/E25/E26 and KB4 projection tests pass.
- The complete repository `pytest` suite passes.
- The on-disk implementation, profile and benchmark SHA-256 values exactly
match those embedded in the immutable E26.1 report.
- The saved-session API reports `ready`, `replayable=true` and the expected
result ID.
- The single localhost service remains available on port 8000.
## Conclusion
E26 closes the specific E25 evidence gap on the reviewed recording: moving
people and the two moving vehicles can be detected from calibrated camera
parallax when current LiDAR support is sparse, while parked-vehicle false
dynamic remains near zero. The implementation is bounded and has no lookahead,
so its state structure is compatible with a later live shadow deployment.
The result is still not a planner-ready world model:
- camera-only motion has no metric velocity;
- 13,291 published camera observations remain unknown;
- 374 camera/LiDAR conflicts require explicit downstream handling;
- the benchmark is small and not independent;
- the detector and track association still inherit COCO-domain errors;
- no end-to-end live latency or dropped-frame envelope has been measured;
- the cold operator replay still takes approximately 47 s.
## E27 gate
E27 should not be another threshold-tuning pass. It should turn E26 into a
measurable world-model qualification:
1. create frame-level independent labels for the reviewed moving objects,
parked controls, ID switches and visibility;
2. quantify camera association integrity, dynamic/static precision and recall,
time-to-confirm and conflict duration;
3. add metric range/velocity only when LiDAR, ground contact or another
observable depth source supports it;
4. preserve occupancy and uncertainty independently of cuboid presentation;
5. replay the bounded stages at sensor cadence and record end-to-end deadline
misses, queue depth and dropped-frame policy;
6. keep the first live step shadow-only, with no navigation or safety authority;
7. prebuild or incrementally stream the operator product so connecting to the
vehicle never waits for a full 69 MB Rerun serialization.
E26.1 is accepted as a diagnostic evidence-fusion laboratory result. It is not
accepted for autonomous navigation, collision avoidance or safety decisions.
@@ -0,0 +1,77 @@
{
"schema_version": "missioncore.e26-camera-ego-motion-profile/v1",
"profile_id": "lab-e26-kb4-multiview-static-hypothesis-v1",
"mode": "recorded-streaming-qualification",
"source": {
"source_id": "sensor.camera.right",
"coordinate_frame": "k1-map",
"calibration_slot": "camera_1",
"calibration_sha256": "05f3ad9b38b3a4fc95388a8ec83da83c745e217709e51787b3d5aad0969f6fa9"
},
"camera_evidence": {
"history_seconds": 2.0,
"maximum_observations_per_track": 32,
"minimum_observations": 5,
"minimum_span_seconds": 0.4,
"minimum_ego_baseline_m": 0.25,
"maximum_normal_matrix_condition": 1000000.0,
"footpoint_inset_fraction": 0.03,
"minimum_bbox_diagonal_px": 35.0,
"minimum_median_detector_score": 0.6,
"minimum_negative_depth_detector_score": 0.7,
"minimum_valid_positive_depth_fraction": 0.8,
"negative_depth_dynamic_fraction": 0.6,
"minimum_static_hypothesis_range_m": 0.8,
"maximum_static_hypothesis_range_m": 80.0,
"dynamic_median_angular_residual_degrees": {
"person": 1.8,
"bicycle": 1.8,
"motorcycle": 1.8,
"vehicle": 3.5,
"default": 3.5
},
"dynamic_p80_angular_residual_degrees": {
"person": 2.8,
"bicycle": 2.8,
"motorcycle": 2.8,
"vehicle": 5.0,
"default": 5.0
},
"static_median_angular_residual_degrees": {
"person": 1.0,
"bicycle": 1.0,
"motorcycle": 1.0,
"vehicle": 1.4,
"default": 1.4
},
"static_p80_angular_residual_degrees": {
"person": 2.0,
"bicycle": 2.0,
"motorcycle": 2.0,
"vehicle": 2.5,
"default": 2.5
},
"dynamic_confirmation_frames": 2,
"static_confirmation_frames": 4,
"evidence_decay_frames": 1
},
"fusion": {
"camera_only_confidence_scale": 0.65,
"conflict_state": "unknown",
"unknown_is_occupied": true,
"camera_only_metric_velocity_valid": false
},
"bounds": {
"maximum_tracks": 256,
"maximum_track_idle_seconds": 2.5
},
"acceptance": {
"maximum_processing_p95_ms": 12.0,
"maximum_tracks_observed": 256,
"maximum_static_control_false_dynamic_fraction": 0.1
},
"authority": {
"commands_enabled": false,
"navigation_or_safety_accepted": false
}
}
@@ -0,0 +1,131 @@
{
"schema_version": "missioncore.e26-motion-benchmark/v1",
"benchmark_id": "ravnoves00-camera-ego-reviewed-anchors-v1",
"source_session_id": "20260720T065719Z_viewer_live",
"timeline": "session_seconds",
"annotation_status": "operator-approximate-with-reviewed-detector-track-binding",
"events": [
{
"id": "dynamic-person-woman-dog-left-58s",
"kind": "target-motion",
"window_seconds": [54.0, 62.0],
"class_group": "person",
"target_source_track_ids": [83],
"expected_motion": "dynamic",
"minimum_hits": 3,
"minimum_span_seconds": 0.2,
"minimum_coverage_fraction": 0.08
},
{
"id": "dynamic-person-loading-car-right-65s",
"kind": "target-motion",
"window_seconds": [62.0, 69.0],
"class_group": "person",
"target_source_track_ids": [112],
"expected_motion": "dynamic",
"minimum_hits": 3,
"minimum_span_seconds": 0.2,
"minimum_coverage_fraction": 0.1
},
{
"id": "static-vehicles-before-moving-70-90s",
"kind": "static-control",
"window_seconds": [70.0, 90.0],
"class_group": "vehicle",
"expected_motion": "static",
"minimum_source_observations": 80,
"minimum_coverage_fraction": 0.2,
"minimum_classified_fraction": 0.2,
"maximum_false_dynamic_fraction": 0.1
},
{
"id": "dynamic-person-adult-child-left-115s",
"kind": "target-motion",
"window_seconds": [111.0, 120.0],
"class_group": "person",
"target_source_track_ids": [229],
"expected_motion": "dynamic",
"minimum_hits": 3,
"minimum_span_seconds": 0.2,
"minimum_coverage_fraction": 0.08
},
{
"id": "dynamic-person-road-right-127s",
"kind": "target-motion",
"window_seconds": [124.0, 132.0],
"class_group": "person",
"target_source_track_ids": [253],
"expected_motion": "dynamic",
"minimum_hits": 2,
"minimum_span_seconds": 0.15,
"minimum_coverage_fraction": 0.08
},
{
"id": "static-vehicles-mid-run-132-148s",
"kind": "static-control",
"window_seconds": [132.0, 148.0],
"class_group": "vehicle",
"expected_motion": "static",
"minimum_source_observations": 80,
"minimum_coverage_fraction": 0.2,
"minimum_classified_fraction": 0.2,
"maximum_false_dynamic_fraction": 0.1
},
{
"id": "dynamic-person-stroller-group-left-155s",
"kind": "target-motion",
"window_seconds": [151.0, 161.0],
"class_group": "person",
"target_source_track_ids": [322, 328],
"expected_motion": "dynamic",
"minimum_hits": 3,
"minimum_span_seconds": 0.2,
"minimum_coverage_fraction": 0.08
},
{
"id": "static-vehicles-before-oncoming-161-166s",
"kind": "static-control",
"window_seconds": [161.0, 166.0],
"class_group": "vehicle",
"expected_motion": "static",
"minimum_source_observations": 25,
"minimum_coverage_fraction": 0.15,
"minimum_classified_fraction": 0.15,
"maximum_false_dynamic_fraction": 0.1
},
{
"id": "dynamic-vehicle-oncoming-right-170s",
"kind": "target-motion",
"window_seconds": [167.0, 173.0],
"class_group": "vehicle",
"target_source_track_ids": [365],
"expected_motion": "dynamic",
"minimum_hits": 3,
"minimum_span_seconds": 0.15,
"minimum_coverage_fraction": 0.1
},
{
"id": "dynamic-person-bike-group-left-173s",
"kind": "target-motion",
"window_seconds": [170.0, 179.0],
"class_group": "vulnerable_road_user",
"target_source_track_ids": [379, 386, 388],
"expected_motion": "dynamic",
"minimum_hits": 3,
"minimum_span_seconds": 0.2,
"minimum_coverage_fraction": 0.08
},
{
"id": "dynamic-vehicle-same-direction-right-183s",
"kind": "target-motion",
"window_seconds": [180.0, 188.0],
"class_group": "vehicle",
"target_source_track_ids": [373],
"binding_review": "frame-and-2d-track-reviewed; 391 is a parked vehicle",
"expected_motion": "dynamic",
"minimum_hits": 3,
"minimum_span_seconds": 0.2,
"minimum_coverage_fraction": 0.1
}
]
}