← Back to main repo | Run 1 (original) →
Run 1 produced a gcp_errors.error.z of 17.58 m, originally suspected as a datum/height-reference mismatch. Per-observation inspection revealed a single mis-annotated image observation on GCP3 (MAX_0131.JPG). Removing that observation reduced the aggregate GCP Z error to 0.13 m — a 99% reduction. The 3D reconstruction error is essentially unchanged.
| Metric | Run 1 | Run 2 | Δ |
|---|---|---|---|
gcp_errors.error.z |
17.575 m | 0.128 m | −99.3% |
gcp_errors.le90 |
39.299 m | 0.198 m | −99.5% |
gcp_errors.error.x |
0.240 m | 0.032 m | −86.7% |
gcp_errors.error.y |
0.631 m | 0.023 m | −96.4% |
3d_errors.error.x |
0.133 m | 0.143 m | +7% |
3d_errors.error.y |
0.101 m | 0.147 m | +46% |
3d_errors.error.z |
0.309 m | 0.354 m | +15% |
3d_errors.le90 |
0.297 m | 0.294 m | ≈0% |
| Runtime (ODM) | 29m 17s | 22m 38s | −23% |
GCP residuals collapsed to centimeter-level. The 3D reconstruction error metrics ticked up slightly — the corrupted observation acted as an extra (incorrect) constraint that artificially tightened the internal self-consistency, and removing it restored honest residuals. LE90 vertical is unchanged.
Run 1 stats.json → gcp_errors reported error.z = 17.575 m, with std.z = 15.71 m. The mean Z bias was only +7.88 m — far smaller than the standard deviation, indicating one outlier point pulling the aggregate, not a uniform datum offset.
Pulled image GPS metadata with exiftool:
GPS Altitude : 513 m Above Sea Level
GPS Altitude Ref : Above Sea Level
GCP file heights: ~460–464 m, range only 3.5 m. Image altitude reference is orthometric (MSL). GCPs in EPSG:4326. No obvious unit mismatch.
EGM2008 geoid undulation for Austria ≈ +46 m. If a datum mismatch were the cause, expected offset would be ~46 m; observed was 17 m. Hypothesis rejected — uniform datum shift does NOT fit the math.
Opened opensfm/stats/ground_control_points.json (only generated when ≥ 2 observations per GCP):
| GCP | Z residual Run 1 | Observations |
|---|---|---|
| 1 | +0.052 m | 7 |
| 2 | −0.151 m | 7 |
| 3 | +39.299 m | 7 |
| 4 | +0.095 m | 7 |
| 5 | +0.095 m | 7 |
GCP3 alone accounted for the entire anomaly. All other GCPs were sub-decimeter.
GCP3's 7 image observations were inspected by comparing annotated (where the operator clicked) vs reprojected (where the reconstruction triangulated the GCP). Six observations had pixel-level agreement. Observation 7 (MAX_0131.JPG) showed:
| annotated (normalized) | reprojected (normalized) | Δ | |
|---|---|---|---|
| x | 0.7035 | 0.3401 | 0.363 |
| y | 0.7393 | 0.6560 | 0.083 |
In a 5472×3648 image, that pixel offset is ~1988 px X, 304 px Y — the annotated location was nowhere near the actual GCP target. Likely a misclick or wrong-image assignment during GCP marking.
Removed the single line containing MAX_0131.JPG for GCP3 from gcp_list.txt:
GCP1: 7 observations (unchanged)
GCP2: 7 observations (unchanged)
GCP3: 7 → 6 observations ← removed MAX_0131
GCP4: 7 observations (unchanged)
GCP5: 7 observations (unchanged)
Re-processed with identical preset (Default / GPU). Verified the corrected GCP file actually reached ODM by comparing md5sums on the container-side input vs the intended edit before letting the task progress past feature extraction (lesson: WebODM ingests GCP from upload bundle, not from filesystem reference).
Run 2 GCP3 Z residual: −0.1942 m (was +39.299 m). All other GCPs unchanged. Aggregate metrics in Result comparison above.
Same as Run 1. See Run 1 README → Source & Acquisition.
- Points: 5
- Observations per point: 6 on GCP3, 7 on others (1 outlier removed)
- Coordinate system: EPSG:4326 (geographic) — auto-reprojected by ODM
- Removed observation:
MAX_0131.JPGannotated for GCP3 (mis-pointed by ~2000 px)
- Preset: Default / Medium
- GPU: NVIDIA RTX 3060 (CUDA)
- Processing date: 21 May 2026, 05:08 UTC
- Total runtime: 22 min 38 s (ODM internal)
{
"mean": {"x": 0.054, "y": 0.048, "z": 0.130},
"std": {"x": 0.133, "y": 0.139, "z": 0.330},
"error": {"x": 0.143, "y": 0.147, "z": 0.354},
"ce90": 0.164,
"le90": 0.294
}{
"mean": {"x": 0.005, "y": 0.005, "z": -0.056},
"std": {"x": 0.031, "y": 0.022, "z": 0.115},
"error": {"x": 0.032, "y": 0.023, "z": 0.128},
"ce90": 0.062,
"le90": 0.198
}Centimeter-level GCP residuals on all axes. CE90 6.2 cm, LE90 19.8 cm.
{
"error": {"x": 0.63, "y": 0.58, "z": 2.43},
"ce90": 1.14,
"le90": 2.67
}Essentially identical to Run 1 — same RTK input, observation removal does not affect GPS pre-georeferencing baseline.
| File | Description |
|---|---|
preview.jpg |
Orthophoto thumbnail (1200 px, native RGB, drop alpha) |
stats.json |
Complete OpenSfM statistics |
ground_control_points.json |
Per-GCP and per-observation residuals (the diagnostic file) |
REPORT.md |
ODM-generated narrative report |
report.pdf |
ODM-generated visual report |
shots.geojson |
Camera positions and orientations |
cameras.json |
Self-calibrated camera parameters |
images.json, img_list.txt |
Per-image metadata |
coords.txt, proj.txt, gcp_list_utm.txt |
Georeferencing metadata |
options.json, benchmark.txt |
Processing config and stage timing |
log.json, task_output.txt |
Processing logs |
Heavy raster artifacts (orthophoto GeoTIFF, DSM, dense point cloud, mesh, textures, full OpenSfM workspace) preserved at E:\Portfolio0\uav-photogrammetry-pipeline\backup\helenenschacht-run2-task-fadc0dae-...\.
- 3D reconstruction error metrics ticked up slightly. This is expected: the corrupted observation acted as an extra constraint that artificially tightened internal self-consistency. The corrected solution shows honest residuals.
- Single-image annotation errors can dominate aggregate GCP statistics. With 5 GCPs, one bad observation out of 35 was enough to make the dataset look like it had a datum problem. Future workflows should examine per-GCP residuals before reporting only aggregate
gcp_errors. - No further outliers checked. Per-observation inspection on Run 2 was not repeated; only GCP3 was re-examined.
