Skip to main content

Reconcile Camera Captures and Image Files After a UG32 Mapping Flight

A Python workflow for joining planned exposures, capture feedback and recovered photos across camera restarts.

Updated 13 min readMarkdown

The flight log says the survey line ran to its end. The recovered folder contains hundreds of photographs. A few of the expected exposures still have no identifiable image, and one camera counter appears to have started again halfway through the sortie. Feeding the entire folder into a photogrammetry project can hide the problem until alignment or coverage review, when the field crew may already have left the site.

A capture reconciliation makes that gap visible while the flight evidence is still available. It compares the exposures the acquisition plan expected with the results reported by the camera and the photographs recovered after landing. Every planned exposure gets a state, including exposures with no surviving record. The result gives a mapping engineer a short list of questions to investigate before building the map.

Define the evidence at the payload boundary

The UG32 is a fixed wing VTOL aircraft listed for mapping, inspection and patrol. UNITED UAV lists a payload capacity of up to 5 kg. That figure describes the airframe offering. Build the capture check around the actual camera, trigger interface, logger, firmware and storage configuration proposed for the project. Confirm which component can report exposure results and which records the operator can export after a flight. UNITED UAV UG32 product and configuration page

Oblique top view of the white UNITED UAV UG32 fixed wing VTOL aircraft with its lift propellers visible.

Figure 1. UG32 catalog view. Use the delivered payload configuration to establish which exposure records can be exported.

Start with three separate inputs. The expected exposure list records what the acquisition plan required on each line. The capture feedback records what the camera or a validated feedback circuit reported. The recovered image inventory records which primary image files the operator could actually read and associate with an exposure. Keep the controller request log as a fourth diagnostic stream when available. It helps explain where a break occurred between expectation and feedback.

An expected exposure should represent an actual obligation in a saved plan revision. Distance based triggering might define those obligations as stations along a line, with run-in and turn intervals excluded. An intervalometer might define them from a bounded capture window and a known starting point. Preserve the distinction between a station that was planned and a station the aircraft actually reached. If a line was skipped or shortened, keep the planned stations and label the route exception separately. A denominator regenerated from the photographs would silently erase that gap.

Agree on what constitutes the primary image before writing the importer. Some cameras can produce a JPEG and a raw file from the same exposure. A team that needs the raw observation can use it as the primary file and track the JPEG as a companion. A multisensor setup can reconcile each stream independently and then compare their timing. The example below expects a single primary image per capture key, so it treats two distinct primary paths as a conflict requiring investigation.

Use a key that survives restarts

Start with a manifest scoped to one sortie and one acquisition revision. Give each expected exposure a key formed from a capture session, a camera or stream identity and a local exposure number. Store the flight line and planned station in a separate table keyed by that same value. Inside a long mission, a camera restart, a storage reset or an intentional capture sequence restart can create a new numbering interval. Make that boundary explicit when the recorded evidence supports it.

This is an internal key. A mission planner may number expected stations, the controller may number trigger requests and the camera may maintain its own image index. Treat each number as belonging to its own source until an integration test establishes the correspondence. For instance, a bench sequence that deliberately skips a trigger and then restarts the camera can show whether the event numbers stay in step, diverge, or need a separate mapping. Record that behavior for the firmware versions actually used.

Avoid splitting a session every time a number decreases. A logger can receive delayed records, and recovery from packet loss can insert an older record after a newer one. A startup marker, an independently logged reconnect, or a validated camera uptime reset can support a boundary. A lone backwards index is an ambiguity to investigate. Retain the original order, timestamps and identifiers in the raw logs so the session assignment can be reviewed.

When the payload implements MAVLink Camera Protocol v2, the capture request can receive a command acknowledgment while per-image feedback arrives as a separate message. CAMERA_IMAGE_CAPTURED includes a capture result and an image index, and the protocol describes how missing capture notifications can be requested again on a supporting implementation. Treat the successful capture report as one evidence stream and the recovered file as another. Confirm what the installed implementation supplies before mapping these fields into the reconciler. MAVLink Camera Protocol v2

Some integrations expose only a trigger output or a controller log. Carry those rows as requests with an unknown capture result until you have a separately verified result. When a camera reports UTC as unknown, its time since boot can still help order events within that boot, provided you establish which clock produced it. Preserve the distinction in the adapter and let the reconciliation surface any resulting gaps. MAVLink captured image message definition

Build a small reconciler before adding heuristics

The Python example uses normalized tuples as keys. The caller supplies an expected key sequence, capture feedback with explicit Boolean results, and recovered primary files as key and path pairs. A file row represents a readable primary image with a validated association. Keep unidentified files in a separate importer exception list. Give every path a stable namespace, such as a card identifier and original relative path, so two memory cards with the same filename can remain distinct.

In the file importer, record the source card, original path, file size and the evidence used to assign the key. Run a full image decode with the library you already use for ingest, because a truncated image can have a readable header. A failed decode belongs in the exception list with the original filename and error. This preserves the distinction between a missing transfer and a present but damaged file. If a recovered file carries only an unverified clock value, keep it as unidentified until the association can be reproduced.

The function collapses identical feedback results and identical path rows. Contradictory results, multiple primary files for a key and reuse of a single path by different keys all require review. A file can participate in a path conflict even when its other assigned key was outside the plan. The returned states follow the supplied plan order, which makes it easy to join them to flight lines. Unplanned keys are returned separately.

from collections import Counter, defaultdict


def reconcile(expected, feedback, recovered):
    expected = list(expected)
    planned = set(expected)
    if len(planned) != len(expected):
        raise ValueError("Expected capture keys must be unique")

    results = defaultdict(set)
    for key, succeeded in feedback:
        if type(succeeded) is not bool:
            raise ValueError("Capture feedback needs an explicit Boolean result")
        results[key].add(succeeded)

    files = defaultdict(set)
    owners = defaultdict(set)
    for key, path in recovered:
        files[key].add(path)
        owners[path].add(key)

    states = {}
    for key in expected:
        flags = results.get(key, set())
        paths = files.get(key, set())
        shared_path = any(len(owners[path]) > 1 for path in paths)
        if len(flags) > 1 or len(paths) > 1 or shared_path:
            state = "conflict"
        elif flags == {True}:
            state = "paired" if paths else "confirmed_file_missing"
        elif flags == {False}:
            state = "failed_with_file" if paths else "capture_failed"
        else:
            state = "file_only" if paths else "unobserved"
        states[key] = state

    unplanned = sorted((results.keys() | files.keys()) - planned)
    return states, unplanned


a = [("session-a", "rgb", n) for n in range(40, 46)]
b = [("session-b", "rgb", n) for n in range(40, 42)]
extra = ("session-b", "rgb", 99)
feedback = [
    (a[0], True), (a[0], True), (a[1], True), (a[3], False),
    (a[4], True), (a[4], False), (b[0], True), (b[1], True),
    (extra, True),
]
recovered = [
    (a[0], "card-a/0040.jpg"), (a[2], "card-a/0042.jpg"),
    (a[4], "card-a/0044.jpg"), (b[0], "card-b/0040.jpg"),
    (b[1], "card-b/0041.jpg"), (b[1], "card-b/alternate/0041.jpg"),
    (extra, "card-b/0099.jpg"),
]

states, unplanned = reconcile(a + b, feedback, recovered)
assert list(states.values()) == [
    "paired", "confirmed_file_missing", "file_only", "capture_failed",
    "conflict", "unobserved", "paired", "conflict",
]
assert unplanned == [extra]
print(dict(sorted(Counter(states.values()).items())))

The synthetic case has eight planned keys and two paired rows. In the first session, exposure 40 appears twice in the feedback stream, so it contributes one paired row. Exposure 40 in the second session gets its own row and its own file path. The extra key appears in both evidence streams and contributes nothing to the planned count. It can represent a legitimate test shot or a bad session assignment, depending on the raw records.

Read the problem states in the order that will save the most field time. For confirmed_file_missing, inspect the card and the transfer log before asking for a new sortie. For file_only, review the camera log or the corresponding identifier in the original metadata. A capture_failed row has an explicit negative result. An unobserved row leaves the break point open, so check the route, trigger stream and camera logger in sequence. A failed_with_file row warrants a closer look at file association, duplicate numbering or a retried capture.

For any conflict, export the competing raw row identifiers and paths alongside the state. The code keeps the decision compact, so a production adapter should retain the discarded duplicates and all raw evidence for review. Store a digest of the imported evidence and the importer version with the output. Running the same evidence through the same rules should give the same result. If a late card copy resolves a missing file, issue a new report tied to the updated input set and preserve the earlier decision.

Set a limit on time based matching

A direct capture identifier is useful when each system carries it reliably. When a camera export supplies timestamps alone, first establish a common clock basis for each recording segment. A bench capture sequence with distinct gaps and independently observable events can help measure the relationship between the controller request, exposure feedback and image metadata. Keep the estimated offset, drift and remaining error separate. They can change after a reset or a change in timestamp mode.

A window based on measured residuals should contain the candidate your test can justify. If two exposures qualify for the same file, or two files qualify for one exposure, flag an ambiguous association. A nearest neighbor routine can produce a tidy result by assigning every file even when the evidence is insufficient. Enforce a one-to-one relationship and keep the rejected candidates visible. If the recording provides only whole-second timestamps and the exposure interval is shorter than a second, several captures may occupy one timestamp bucket. Another field or an independent observation is then needed.

Clock uncertainty also has a geometric consequence. At a hypothetical ground speed of 18 metres per second, an exposure time uncertainty of 0.12 seconds corresponds to about 2.16 metres of along-track position uncertainty from timing alone, under a constant speed assumption. A camera test can use that calculation to decide whether the measured timing error is small enough for the project. Include position, orientation and terrain uncertainty separately if the review depends on them.

Low front oblique view of the white UNITED UAV UG32 aircraft with its wings and landing legs visible.

Figure 2. UG32 catalog view from a lower angle. Flight records and payload documentation establish the timing and file interfaces for an installed camera.

Translate missing captures into coverage decisions

A summary fraction is useful for triage, but gaps cluster in space. Join the reconciled rows to the flight line and inspect each run of missing or ambiguous exposures. Use observed exposure locations when those positions have a known reference and acceptable timing. Keep the planned station available as context. Two missed exposures between good frames can matter more than several isolated gaps spread across parallel lines with strong coverage.

Consider a hypothetical straight line with exposure stations spaced 24 metres apart. Assume the usable along-track footprint is 100 metres at each exposure and the camera points consistently. The planned overlap between adjacent frames is 76 percent. If the middle two stations between a pair of usable frames are unresolved, the span between those usable frames becomes 72 metres. Under the same simplified assumptions, the overlap across the gap falls to 28 percent. Use that calculation as a flag for a local coverage review and confirm the footprint and overlap against the images and terrain before deciding whether a repeat pass is needed.

Make the coverage review operate on images that actually meet the survey task. A paired result supports file identity and capture evidence. Image quality still needs independent checks for sharpness, exposure, georeferencing and other requirements agreed for the deliverable. A tightly timed but blurred exposure can create the same practical overlap gap as a missing frame. Keep the quality flag next to the reconciliation state so a field engineer can see why a photograph was removed from the usable sequence.

For rework, return a line identifier, the first and last affected stations, the neighboring usable exposures and the missing evidence category. This helps the operator distinguish a processing recovery task from a capture task. When a repeat segment is needed, the mission planner can place its limits where the verified usable imagery supports joining the original block, subject to the project's route and operating constraints.

Keep an explicit boundary at the end of each flight line. A station on the next line might sit close on the map while belonging to a different look direction and footprint. Use the pair of adjacent usable frames on the original line for the simple along-track calculation, and use the actual image footprints to evaluate support across lines.

Test the procedure before leaving the site

Ask the integrator for one actual configuration sample containing a plan revision, the associated trigger or request record, capture feedback if available, and the original files. Confirm which component owns each timestamp and identifier. Test the same reconciliation procedure with an interrupted transfer, a duplicate notification and a restart of the recording session. If the installed camera cannot report exposure feedback, agree with the team which independent signal will support acceptance and how unknowns will be recorded.

Include a deliberate exception in the bench test. Keep a known photograph off the first transfer and verify that the corresponding row appears in the exception list. Import the missing file in a new revision and confirm that the resulting report changes only where that evidence applies. Also try repeating a filename on another card and assigning one file path to two keys. These cases are quick to produce and catch common importer errors before they affect a real survey.

The handover can then be compact. Deliver the plan revision, an exposure-level table with evidence references, a separate list of unplanned and unidentified items, and a line-level view of unresolved coverage. Ask the receiving engineer to reproduce the counts from the saved inputs and review at least one flagged span against the recovered imagery. This gives both teams a shared account of where the capture sequence is supported, what needs investigation, and which part of the flight would need new evidence.

About the author

  • UNITED UAV Official publishes technical guidance across multirotor, fixed-wing, and VTOL systems, including mapping, inspection, logistics, public safety, agriculture, payload integration, navigation, maintenance, APIs, and automation.

No comments yet. Be the first to comment.