UBR Stack · team 15 · NVIDIA Open Models Codefest 2026

The Off-the-Shelf Alternative

Whether our synthetic training pipeline beats a detector that already runs on the board unchanged. Two off-the-shelf detectors, one posture rule written down in advance, two frame sets with opposite asymmetries — and a decision rule that is deliberately not symmetric, because a tie is a switch.

Task one binary decision per frame Arms B0+r · B1+r · B2 · D1 Rule w/h ≥ 1.3, fixed Status designed, unmeasured
The question

A baseline has to be something we did not build

Our first detector was YOLO26n fine-tuned on our own synthetic renders. Using it as the left column of an ablation measures our training against itself, which answers nothing. The question a baseline has to answer is narrower and less comfortable: does our data pipeline beat what a competitor downloads on a Friday afternoon, on real frames?

So the reference is now off-the-shelf, our own detector is a result rather than a baseline, and the ablation reads B0/B1 → D1 → D2 → D3. The headline claim is the step from the B row to the D rows. If there is no step, that is the finding.

The trap that comes first

Without a task definition the comparison produces two artefacts, both wrong

B0 and B1 detect people in any posture. D1 detects people lying down. Score them naively on the same frames and you get two numbers that look decisive and mean nothing.

What naive scoring does
FramesWhat the baseline doesHow it reads
lying positives Fires. A prone person is still a person, so COCO person finds them without knowing anything about posture. Baseline looks strong on recall — for the wrong reason.
standing people
(our negatives)
Fires, by design. It was trained to find standing people and it is doing its job correctly. Baseline looks catastrophic on false alarms — for the wrong reason.

So the task is fixed once, before anything runs, and every arm is held to it: does this frame contain a person lying on the ground? One binary decision per frame. And the off-the-shelf alternative is not a bare person detector — it is a person detector plus a cheap posture rule, which is exactly what a competitor would build in an afternoon.

Rule r, chosen from geometry and recorded before the run: box aspect ratio w/h ≥ 1.3. A prone adult at rover camera height is wider than tall. Nothing is tuned on evaluation data; a tuned threshold, if anyone wants one later, is fitted on the calibration frames only and reported as its own arm.

The arms

B0 and B1 are not the same experiment

Both are off-the-shelf, so both test the training question. Only one of them can also be the answer — because "off the shelf" turns out to be three independent things (no training cost, a permissive licence, and a deployment path already built) and they do not line up across the arms.

Four arms, one task
ArmDetectorPosture fromLicenceWhat it answers
B0+r yolo26n pretrained COCO, person class, as downloaded the rule AGPL-3.0 Did our training earn anything? Same architecture family as ours, so the only difference is the data. The clean scientific control — and no help on licensing.
B1+r PeopleNet as NVIDIA ships it, no retraining — pruned, INT8-calibrated, TensorRT and DeepStream ready the rule NVIDIA Open
Model License
Should we ship something else? The deployment candidate. The only arm that removes the AGPL obligation from the rover — and the only one that arrives already optimised for the runtime we deploy on, calibration cache included.
B2 Grounding-DINO zero-shot, "a person lying on the ground" the prompt Apache-2.0 Optional ceiling check. Weights are already on the B300, but it will not fit an edge board — so if it wins it becomes a teacher, not a deployment.
D1 our sim-trained detector (git tag v0) its training AGPL-3.0 The thing under test. Relabelled from "baseline" to D1-sim — a result, not a reference.
The framework

Two instruments, opposite asymmetries

The two frame sets are not a big one and a small one. They fail in opposite directions, and that is what makes the pair worth running rather than either alone. Read the matrix cell by cell: the same outcome means different things depending on whose training distribution it happened on.

conclusive — we change something conclusive — we keep D1 licenses no conclusion
IF THEY TIE IF OURS WINS B1+r ≈ D1 D1 ≫ B1+r DATASET v2 VAL 474 REAL FRAMES 2,396 images ~1,100 upright 386 lying-vest D1's own training data past missions real clutter real sensor nobody's training data STRONG AGAINST US PROVES LITTLE THE FINDING THE HEADLINE An off-the-shelf model matched ours on our own renders. Not a fluke — it is D1's home turf, so this is the hard version. → the pipeline is in question Expected, and uninformative: D1 trained on these renders. A win here is memorisation until the real set agrees. → run the real set, claim nothing Switch to PeopleNet. The AGPL obligation leaves the rover with D1, and "we measured and changed" is the story we report. → act on it the same day The synthetic pipeline earned its place. This is the stage-1 claim, stated as a false-alarm gap with its confidence interval. → keep D1, pay the AGPL cost
One outcome, two meanings. Dataset v2's validation split is a weak instrument for the conclusion we would like and a strong one for the conclusion we would not — because D1 trained on it. The 474 real mission frames are nobody's home turf, which is why they carry the headline. Both cells in the left column lead to a change; only the bottom-right cell lets us keep what we have. Three of the four outcomes are actionable, which is what makes this worth two hours.

The v2 validation set supersedes an earlier ruling of ours. We had written off "the synthetic val set" as saturated — 53 of 54 boxes in every arm and every seed — but that was the old val, 54 boxes of one camera view. The v2 split carries roughly 1,100 upright-person boxes across six hard-negative pose classes plus 386 lying-vest positives. It is a different instrument and it separates arms on false alarms. The saturation objection does not transfer.

Controls

What is held identical, and the one thing that cannot be

Held fixed across every arm

  • The frame list and its order — or the paired test is void
  • The task definition — one binary decision per frame
  • The confidence threshold, 0.25 everywhere
  • The posture rule and its threshold, recorded before the run
  • The scorer, run from per-frame CSVs, no metric computed by eye

Differs, and is reported as differing

  • The decode. PeopleNet is DetectNet_v2 — 960×544 input, stride 16, a 60×34 grid with a coverage head and a bbox-regression head. It cannot go through the brain's own cv2.dnn parser, so B1's boxes come from different code than D1's. The acceptance rule said one parser for every arm; that rule is relaxed here, deliberately, and stated in the report rather than quietly broken.
Failure modes

Three ways this produces a confident wrong answer

  1. A box-norm scale error that confirms our prior. Get DetectNet_v2's bbox scaling wrong and the boxes come out plausibly placed and systematically the wrong size. Nothing downstream errors. It surfaces as "PeopleNet has poor recall on prone people" — which is precisely the assumption we are testing. We would confirm ourselves for the wrong reason. Mitigation: draw decoded boxes back onto ten frames and look at them before scoring anything.
  2. A posture rule tuned on the evaluation set. The rule is the baseline's whole posture capability, so a threshold fitted where it is scored hands the baseline an advantage D1 never had. w/h ≥ 1.3 comes from geometry and is written down first. Any tuned variant is fitted on calibration frames and reported as a separate arm, never swapped in.
  3. Blaming the detector for the rule. If person_seated and person_crouching dominate the false alarms, that is a rule problem, not a PeopleNet problem — a seated person is genuinely wider than tall. Mitigation: per-pose confusion for every arm, so the rule's failures are visible separately from the detector's.
Statistics

Every cell carries its N

How each number is stated
MetricOn whatReported as
false-alarm rate ~1,100 upright boxes (v2)
474 frames (real)
Rate with a Wilson 95 % interval. The rate alone is not reportable at these N.
lying recall 386 positives (v2)
judge set, when captured
Overall, and per distance bucket with its own Wilson interval — at 5 positives per bucket that interval runs [0.57, 1.00], so a bare per-bucket recall is meaningless.
arm vs arm paired, same frames McNemar with the discordant counts b and c shown, not a p-value alone. At this N the discordance is the informative part.
per-pose confusion 6 hard-negative classes Which posture triggers each arm. Separates trap 3 from a real detector weakness.
The decision

A tie is not a tie

Why the rule is asymmetric

Equal accuracy is not a reason to keep ours. It is a reason to switch — and there are three independent reasons, only one of which is about accuracy at all.

1 · Licence. D1 carries AGPL-3.0 onto a product we intend to sell. PeopleNet carries the NVIDIA Open Model License.

2 · Deployment cost. PeopleNet arrives pruned, INT8-calibrated and TensorRT/DeepStream ready. D1 is a .pt file: we export it, quantise it, source calibration data, build an engine per JetPack version and then prove the engine is still the model we scored. We have just spent a week learning exactly how much work that last clause is.

3 · No calibration coupling. Because PeopleNet ships its own calibration, it has no in-domain advantage to declare. Our own 4-bit comparison has to be reported twice — full and calibration-disjoint — because the quantised arm saw the evaluation scenes during calibration. An arm that needs none of our data cannot have that problem, and cannot be accused of it.

So the bar is not "does B1 beat D1". It is "does D1 beat B1 by enough to be worth an AGPL obligation, a quantisation pipeline we maintain, and a fairness caveat in every table". A statistically indistinguishable result resolves in PeopleNet's favour, and it resolves there before we have seen the number, because the rule is written down now.

Note what B0 does not do here: yolo26n is AGPL too and needs the same export work, so B0 answers the training question and offers no way out of the other two. It stays in as the control, not as an option.

Where it runs

Accuracy on a workstation, runtime on the board — and a gate between them

The obvious move is to run everything on the Orin Nano, since that is the hardware that ships. It is the right instinct and the wrong order: these are two measurements with different failure modes, and merging them costs both.

Accuracy — workstation, deployment-agnostic precision

  • The DetectNet_v2 decoder is the risk in this job, and debugging a box-norm scale factor through a TensorRT engine on an 8 GB board is several times harder than in Python on a workstation.
  • Orin runs INT8. If every number we ever take is INT8, we cannot separate "PeopleNet is weak on prone people" from "INT8 PeopleNet is weak on prone people" — and the first is the claim we are testing.
  • Iteration speed decides whether the stopping rule bites. 2,396 frames × 4 arms wants to be minutes.

Runtime — jetson1, and worth having for itself

  • FPS, latency, peak memory and watts per arm per input resolution, each with the power mode it was taken in. A tok/s or an FPS without its power mode is not a number.
  • PeopleNet's published figure — 140 FPS INT8 — is for Orin NX, not Orin Nano 8 GB. Our board's number is one nobody has published, the same gap the 4-bit reasoner filled.
  • Co-resident memory with the INT4 VLM engine, the detector, the ROS/GCS stack and the recorder on one 7,485 MB board. This is the measurement that actually decides what ships, and it is still open.
  • And it is cheap for B1 specifically: nothing to export or calibrate, so the board run is a build and a benchmark. The expensive arm to put on the Orin is ours.

The gate that joins them. An accuracy number taken on a workstation does not transfer to the board for free — INT8 quantisation and a different decode path can move it. So before any L1 claim, the two runs are compared per frame on a seeded subsample, not on aggregate accuracy: two runs can agree on the total while disagreeing on a quarter of the frames. Expect ≥ 99 % agreement; anything worse is a build problem to fix before the numbers mean anything. Same gate we wrote for the 4-bit reasoner's engine, same reason.

This is the evaluation ladder's own division, applied one layer down: the static comparison ranks the models, the board supplies the runtime. Neither figure is the other's, and no table should carry both in the same column.

Status

Designed, not measured

Everything above is a protocol. There is no number behind any of it yet, and the one claim we have made in writing about PeopleNet's behaviour — that recall drops on prone people — is an assumption, labelled as one.

What runs, and when
StepCost
B0 end to end20 minToday
PeopleNet fetch + artefact format15 minToday
DetectNet_v2 decoder + smoke45–90 minThe risk
Harness + v2 validation run30 minToday
474-frame run+40 min label passQueued
jetson1 runtime + co-resident memory~90 minAfter accuracy
Workstation ↔ Orin agreement gate~15 minGates L1

Stopping rule: if the decoder resists past 90 minutes, report B0 alone with B1 marked blocked on the DetectNet_v2 decode. B0 against D1 — COCO person plus ten lines of rule against our whole synthetic pipeline — is already a real result, and a partial table with a named blocker beats an afternoon lost to scale factors.