Can You Recover Missing Ground Control From Old Drone Images?

If you've flown big enough jobs, you've merged images from consecutive days — that's routine. But when a customer asked if I could merge images separated by months, I started to squirm. Then things got weird.

TL;DR: yes, you can — but I almost published the wrong reason why.

Here's the whole story.

A footpath, flown wide

The job started simple. A client sent me a corridor to map — a KML with a single linework string down the centerline of a footpath. The path itself is maybe 8 feet wide; call it 20 to 30 feet once you count the shoulders. Not much.

Oblique point-cloud view of the mapped corridor — a footpath threading up the middle, flanked by scrub and trees
The mapped corridor as a point cloud — the footpath itself is the bare strip threading up the middle.

I flew it 600 feet wide anyway. When you map a corridor you give yourself margin — you don't want to discover a missing strip after you've packed up the drone. So I covered a generous band, processed the images, and delivered. The client paid. I moved on.

One detail from that first job matters later, so I'll put it on the table now and leave it there. I didn't place the aerial targets myself — the surveyor's field crew set the targets before I ever reached the site, and handed them off as a CSV of coordinates. The CSV gave coordinates but never specified a datum. I assumed state plane; that lined up with the corridor KML. Nobody had written down whether the elevations were ellipsoidal or geoid heights. I made a call, processed the data, and shipped it. The deliverable was correct and the client used it without a hitch.

"Where are the spurs?"

Months later, an email: where are the spurs?

A corridor like this isn't necessarily just a line. This one has spurs — short branches off the main path — and a few of them hadn't made it into the linework KML I'd been given. My flight covered most of them — but they ran out to the ragged edge of the corridor, where side overlap thins and accuracy degrades fast. Mostly-covered isn't covered enough to deliver. That meant a re-fly.

Schematic showing the corridor centerline, the ~600-ft flight coverage, the control targets, and the spurs branching off the trail
Setup schematic: the corridor centerline, the ~600-ft flight coverage (two flights), the control targets, and the spurs branching off the trail past the flown band.

And a re-fly months later raises a question that has nothing to do with flying. Those original ground-control targets had been sitting out in the weather. Were they still there? Had animals or workers disturbed them? Were they too faded?

The surveyor — the licensed PLS on the project — put it to me directly. Send a crew out to set fresh targets (please, no), or… could I just process the new flight together with the old images, and pull the now-missing ground control out of the old set?

The question I couldn't let go

Here's the anticlimax: I never had to answer it.

I drove out, and most of the original targets were exactly where I'd left them. Two had gone missing; I reset those with my Emlid RS3. That was enough. I processed the new flight on its own, delivered it, and the client was done. Clean.

But the question — could you rebuild missing ground control out of an old flight? — wouldn't leave me alone. So I decided to actually answer it.

The surveyor's instinct had been "use all the images from both flights." That turns out to be impossible before you even start. The new flight alone is about 4,400 images. My workstation gives out somewhere around 6,000 to 7,000 images. Both flights together is roughly 8,100. I can't process that mountain of data.

So the old set had to be cut down. And here my curiosity took the wheel. The obvious question is how do I cram all those images through? I got interested in the opposite one: how little of the old flight do I actually need? So I went to the extreme. I kept only the old images that already carried ground-control marks — a couple hundred of them, the bare skeleton — and threw everything else away. All the new images, a skeleton of the old. Then I hit process.

It came apart

A healthy photogrammetry project resolves into one connected block — every camera tied to its neighbors, one coherent model. This resolved into about eight. Eight disconnected islands of cameras that couldn't agree with each other. Ground-control error came back around 18 feet, on a job where sub-inch is the expectation. And whole patches of the surface were floating — sitting something like 70 feet off from where they belonged.

I want to be honest about what I did with those floating blocks, because if you know your datums you're already wincing: I clocked them, decided they were a symptom of the shattered bundle rather than the disease, and moved on. Fix the structure first, I figured — the offset would sort itself out once the model was whole.

So my plan was simply: feed it more.

Feeding the gap back in

So I thickened. For each orphaned old image, I went back to the original image set and pulled its flight-line neighbors — the frames shot just before and after it. Those neighbors share enough view with both the marked old image and the surrounding new coverage to act as stepping stones. I added enough of them to bring the old set up from a couple hundred images to about a thousand — still well under my machine's ceiling.

It worked. The eight blocks collapsed down to two. Twenty-two of my 23 tagged control points landed sub-foot. This was a real result — good enough that if I'd needed to, I could have turned it into a deliverable.

That could have been the finish line. But I couldn't stop at good enough.

Edge-on view of the thick-run 3-D model — cameras merged into one coherent sheet with one small orphan block hanging well below
The thick run in Matic's 3-D view — the two flights merged into one coherent sheet, all but the orphan block hanging well below it.

The fragment that wouldn't come home

Two blocks, not one. One small cluster of cameras — seven of them — still wouldn't join the main model. A stubborn little fragment, hanging off on its own.

So I looked harder at it. And the fragment was floating about 22 meters — call it 70-some feet — below where it should have been.

22 meters. That is not a random number. That is, almost exactly, the geoid undulation where I work.

Here's the detail I planted at the start of this story. A drone's GPS records ellipsoidal heights — referenced to the smooth mathematical ellipsoid that GNSS uses for positioning. Published survey elevations are orthometric — referenced to the geoid, which approximates mean sea level. The two surfaces don't coincide; in my area, they're about 22 meters apart. The old flight, processed straight from the drone, was ellipsoidal. The new flight and the ground control were effectively orthometric, because of how I'd set up the RTK base for the re-fly. I'd handed the base a known point (from the surveyor) and told it that point's height was ellipsoidal, when the value I typed was really orthometric. One label. The base believed me, and every image in the new flight inherited the error.

Cross-section diagram showing the ellipsoid and geoid reference surfaces with the ~22-metre gap between them
Geoid vs. ellipsoid: the two height surfaces and the ~22 m gap between them.

So the two flights were never in the same vertical world — a clean 22 meters apart. Each one, alone, was internally consistent (and constrained by the aerial targets); each deliverable had been fine. The mismatch only mattered when you tried to combine them. The merge was the first thing that forced the question.

So I fixed it properly.

Now I understood the gap. I could have fed more bridging images around that stubborn fragment — the same trick that had worked once already — or tagged a few new-flight images to anchor it directly. Either would have closed it out. I didn't, because once I could see the 22-meter mismatch, I couldn't leave it there — I don't like band-aids. So I corrected the old elevations, shifted them into the same frame as everything else, and reprocessed.

It fragmented. Worse than the skeleton run had — back to eight blocks, the merge in pieces. I thought I'd fat-fingered something, so I ran it again, more than one way. Same answer every time. With the datum corrected, the two flights would not knit together.

And there it was — three runs deep — a finding. A clean one. The merge had held when the two flights sat a full datum apart, and collapsed the moment I corrected them. The error, perversely, looked like the thing holding everything together. And I don't know why. That is a tidy, counterintuitive, click-baity thing to say. I could feel the blog post writing itself.

Friday, 11 p.m.

Here's what that finding had cost.

None of this was the job. The job — the deliverable the client actually paid for — had gone out the door days earlier. Everything since was a question I'd picked up because I couldn't put it down: three working days I'd rather have spent on paying work, poured into a corridor nobody was waiting on.

It was Friday night. Eleven o'clock. I was still at the laptop when my wife told me — not for the first time that week — to close it and come to bed. She was right. I was tired in the particular way you get tired when you've been chewing the same problem for days and the thing you have to show for it is strange — not a clean yes, not a clean no, but a weird, backwards little result that I half-believed and was already a little embarrassed by. It works! If you do it wrong…

The three-run finding would have made a real Part 1 post, and I'd been turning that over since Thursday. But it didn't quite sit right — three runs is thin for a conclusion that counterintuitive — and there was one case I'd never run.

The Hail Mary

I didn't write it up. Because that untested case was right there, and at eleven at night, starting one more run is easier than admitting you're confused but done.

Let's run everything. Every image from both flights, all 8,000 of them — no skeleton, no thickening, the whole pile. It was well past the 6-7k where my machine gives up, and I'd be doing it with the datum corrected: the case that, by my brand-new finding, was supposed to shatter.

I queued it anyway, with a few "lower resolution" hacks which might help it swallow the massive dataset. The machine could spend the night failing without my help. I hit "go" and went to bed.

Call it a Hail Mary. I threw it the way you throw a Hail Mary — not because you expect it to land, but because the clock's run out and you're not going to win by kneeling on the ball.

Saturday morning

Saturday morning was coffee first. No need to rush over to see a "system error". But then…

It had finished. And it hadn't just finished — it was the best result of anything I'd run all week. More than eight thousand images, all but a few dozen of them calibrated. Ground-control error of about two-tenths of an inch. A full point cloud of the entire corridor, close to fifty million points — better, by every number that mattered, than either single flight had been on its own.

The datum was corrected. And the merge was clean.

Which meant my tidy Friday-night finding — the click-baity one I'd been ready to publish — was wrong.

What actually decided it

So I laid all four runs out in a grid.

Old-flight images in the merge
≈ 200 ≈ 1,000 ≈ 3,700
Old heightsleft uncorrected
Fragmented
8 blocks · ~18 ft control error
Merged
2 blocks · sub-foot control (one orphan)
not tested
Old heightscorrected not tested
Fragmented
8 blocks · merge in pieces
Best result
Merged
5 blocks · 0.018 ft control · full point cloud

Every run merged the entire new flight; the only thing that changed was how much of the old flight came with it. Read across each row: more old-flight imagery turns a shattered merge into a clean one. Correcting the old heights didn't decide the outcome — it just raised how much imagery the merge needed.

Sort by datum state and the story falls apart: the mismatched datum has a run that merged and a run that shattered; the corrected datum has a run that merged and a run that shattered. Sort by image count and it snaps: two hundred old images, shattered; a thousand, merged; a thousand in the corrected datum, shattered again; all thirty-seven hundred, merged cleanly. The variable was never the datum. It was how much of the old flight came along for the ride.

The datum offset had been a crutch, not a wall — it made the merge cheaper, not possible. Correct your datums and it still works; you just need more imagery. Why would a mistake lower the price? Honestly, I don't know — and since the right lesson is use enough old images, not try introducing errors, I'm not going to chase it.

So — the surveyor's question. Can you recover missing ground control from an old drone flight? Yes. You genuinely can. You process the new flight together with the old one, mark the control in the old images, and the new flight inherits it through the overlap. The one thing you have to get right is coverage: feed the project enough of that old flight to tie the two together honestly. Get that right and the precision holds: my best merged run came back at 0.018 ft of ground-control error — about two-tenths of an inch — across the whole corridor.

And to be crystal clear, DON'T manufacture a datum error to save on processing overhead.

One thing I will say without hedging, and it's the boring one — the one that made this such a journey: pin down your vertical datum the moment data changes hands. Ellipsoidal or geoid — get it in writing. On every single flight I ever delivered, the unspecified datum never once hurt me; each delivery was internally consistent, and each one was fine. The ambiguity cost nothing — right up until two datasets had to agree inside one project. Get it in writing not because today's job needs it, but because the day something finally has to line up, you do not want the answer to be a guess.

There's one thread I'm not going to pull on — what you trade away when you shrink processing scale to fit a limited machine. That one's worth its own post, another time.

For the nerds — here's what the bundle adjustment did on each of the four runs.

Bundle ΔZ chart across all four runs — how far the adjustment had to move each camera; the two merging runs made coherent corrections, the two fragmenting runs never converged
Bundle ΔZ across all four runs — how far the adjustment had to move each camera. The two runs that merged made a coherent correction; the two that fragmented never converged.