CV Trajectory Error (ATE / RPE)
How to tell whether your odometry is actually any good
- estimate
- reference
- aligned
- errors
- ate_rmse
- ate_mean
- ate_max
- scale
- rpe_rmse
- found
Ask someone whether their visual odometry "works" and you'll usually get a screenshot of a trajectory that looks a bit like the ground truth. That's not a measurement, it's a vibe. CV Trajectory Error (ATE / RPE) is the node that turns the vibe into numbers, in the two forms the SLAM literature actually standardised on.
- ATE (absolute trajectory error) - distance between corresponding points after the two tracks have been brought into one frame. Global error, tells you how far the whole thing drifted.
- RPE (relative pose error) - disagreement between consecutive steps. Sees local jitter and scale noise that a globally-fitted ATE smooths away entirely.
Alignment is the whole argument
Two odometry runs each start in their own coordinate frame. A monocular run doesn't even have a scale - it recovers the shape of the path and nothing about its size. Compare raw coordinates and you're measuring which arbitrary frame each run happened to pick, not accuracy.
The align dropdown is the answer:
- none - raw comparison. Only meaningful if both tracks genuinely already share a frame and a scale. Rarely.
- rigid (SE3) - fits a rotation and translation. Removes the frame choice.
- similarity (Sim3) - also fits one global scale factor. This is the honest default for monocular output, and it's the node's own default for that reason. Pair it with the
scaleoutput: if the fitted scale is far from 1.0, your estimate's size is off even if its shape is right.
Mechanically it's an Umeyama fit via cv2.estimateAffine3D(..., force_rotation=True) - the same routine behind CV Register Point Clouds (3D) - with the scale returned separately rather than baked into the matrix.
Inputs and outputs
estimate is the trajectory you're scoring, Nx3. reference is what you score it against, also Nx3, one row per estimate row; the longer one gets truncated. rpe_step sets the frame gap the relative error is measured over - 1 is frame-to-frame motion, larger values report drift across that window.
Note the comparison is strictly index-for-index. Row 40 of the estimate is compared to row 40 of the reference, so a dropped or duplicated frame silently poisons everything downstream. Align them before you get here.
Outputs:
- aligned - your estimate mapped into the reference's frame,
Nx3. Draw this, not the raw input, when you overlay the two paths. - errors -
(N,)per-point distance. Chart it against frame index to see where the drift happened, which is far more actionable than a single RMS number. - ate_rmse, ate_mean, ate_max - the summary statistics, in the reference's unit.
ate_maxis the worst single point: peak drift. - scale - the global scale the alignment applied (1.0 for
noneandrigid). - rpe_rmse - relative-pose RMS over
rpe_stepframes, after alignment. - found - false when fewer than three usable pairs survive. Then the metrics are zeroed and the estimate passes through unaligned rather than erroring.
The natural pairing
Score CV Visual Odometry (Sequence) output here - its trajectory output is exactly the Nx3 this wants, and the pack's own example does precisely that against the KITTI ground-truth poses. One warning before you redistribute anything: that poses file is CC BY-NC-SA, non-commercial, the only non-commercial asset in the repository. Your own GPS or wheel-odometry logs are the commercial-safe substitute.
Installing it
Manager → ComfyUI CV, or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Restart. Python ≥ 3.12, recent ComfyUI on the V3 node API, opencv-contrib-python-headless~=5.0.0.93. No models, no downloads. GPL-3.0, a fork of opencv-comfyui, and the author's warning that this is LLM-assisted code not meant for production without review applies with extra force to metric code - validate the numbers against a case where you know the answer before you quote them at anyone.
Where people get burned
Comparing a monocular run with rigid. You'll get a huge ATE that says more about the missing scale than about the odometry. Use Sim3 and read scale.
Treating NaN rows as fine. Non-finite points are excluded from the fit and get NaN in errors, so ate_mean can look respectable while a stretch of your trajectory was garbage. Check the mask, and look at errors as a curve.
Only reading the RMS. A run that's perfect for 400 frames and then jumps 30 metres has a modest RMSE. ate_max and the error curve are how you catch that.
Expecting an absolute unit. ATE is in the reference's unit. With a constant-scale monocular run against a reference in metres, the numbers are only meaningful once the alignment has fitted a scale - which is another reason Sim3 is the default.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| estimate | NPARRAY | The trajectory being scored, Nx3 (from 'CV Visual Odometry (Sequence)' or 'CV Compose Pose'). | |
| reference | NPARRAY | The trajectory to score AGAINST, Nx3, one row per estimate row. The longer one is truncated. | |
| align | COMBO | similarity (Sim3, monocular scale-free) | How much of the difference to treat as a choice of coordinate frame rather than error. 'none' compares raw coordinates (only meaningful when both already share a frame AND a scale). 'rigid (SE3)' fits rotation + translation. 'similarity (Sim3)' also fits one global scale - required for monocular odometry, which recovers shape but not size. |
| rpe_step | INT | 11–10000 | Frame gap the relative-pose error is measured over. 1 compares frame-to-frame motion; a larger gap reports drift over that window instead. |
Outputs (8)
| Name | Type | Description |
|---|---|---|
| aligned | NPARRAY | The estimate mapped into the reference's frame, Nx3 - draw this on top of the reference, not the raw input. |
| errors | NPARRAY | (N,) float64 per-point ATE distance. Chart it against the frame index to see WHERE the drift happened. |
| ate_rmse | FLOAT | Root-mean-square ATE, in the reference's unit. |
| ate_mean | FLOAT | — |
| ate_max | FLOAT | Worst single-point error - the drift at its peak. |
| scale | FLOAT | The global scale the alignment had to apply (1.0 for 'none' and 'rigid'). Far from 1 with Sim3 means the estimate's size is off even though its shape may be right. |
| rpe_rmse | FLOAT | Root-mean-square relative-pose error over 'rpe_step' frames, after alignment. |
| found | BOOLEAN | — |