A dual-sensor UAV can produce visible video, thermal frames, aircraft position, attitude, gimbal angles, target detections, and operator events at different rates. The difficult part is not collecting every stream. It is proving which observations belong together without inventing precision that the source system never provided.
This article develops a data contract for that problem. It uses the UNITED UAV UVH2 dual-sensor fixed-wing VTOL drone as a concrete product context. The current listing describes a K8T-V2 payload with an 8 MP visible channel, 4K visible recording, a native 640 × 512 thermal detector, processed 1280 × 1024 thermal super-resolution, and Ethernet, serial, and SBUS interfaces. It also identifies the communication protocol as proprietary.
That last detail defines the software boundary: obtain the supported interface-control document and use an approved adapter. The schemas and code below describe a downstream observation pipeline. They are not the UVH2 command protocol, do not reverse-engineer it, and must not be placed in a flight-critical control path.
Commercial disclosure: UNITED UAV Official publishes this technical article and links to a product sold through the UNITED UAV store.
Why a filename pair is not a data contract
A common first implementation writes visible_001.jpg and thermal_001.jpg, then assumes the matching number proves that the files were captured together. That assumption breaks when one encoder drops a frame, a camera restarts, a buffer flushes late, or a transmission path reorders messages.
The pipeline needs to preserve three different facts:
- what each source reported;
- how the integration correlated the sources;
- how confident the integration is in that correlation.
Those facts should remain visible to every downstream consumer. An inspection model may accept a 200 ms pairing tolerance. A moving-target workflow may require a much tighter limit. A reviewer should never have to infer that tolerance from filenames or application code.
Define an immutable capture bundle
Treat every correlated observation as an immutable bundle. Large media files stay in object storage; the event carries their checksums, dimensions, formats, timestamps, and references.
{
"schema_version": 1,
"capture_id": "01J6FGP8B0V5TGQ1M9A2X4K7CZ",
"vehicle_id": "uvh2-field-07",
"mission_id": "roof-baseline-2026-08-28-r3",
"observed_at": "2026-08-28T06:18:42.481Z",
"source_clock_ns": 5842284190031,
"clock_epoch": "payload-boot-0194",
"visible": {
"object_uri": "s3://uav-raw/2026/08/28/visible/01J6F.jpg",
"sha256": "...",
"width": 3840,
"height": 2160,
"encoding": "jpeg",
"source_sequence": 18422
},
"thermal": {
"object_uri": "s3://uav-raw/2026/08/28/thermal/01J6F.bin",
"sha256": "...",
"width": 640,
"height": 512,
"representation": "native_detector",
"source_sequence": 9211
},
"correlation": {
"method": "source_clock_nearest",
"delta_ms": 18.4,
"max_allowed_delta_ms": 50,
"quality": "accepted"
},
"pose": {
"position_source": "aircraft_telemetry",
"attitude_source": "aircraft_telemetry",
"gimbal_source": "payload_telemetry",
"sample_delta_ms": 11.2
},
"adapter": {
"version": "2.3.1",
"interface_document_revision": "vendor-approved-revision"
}
}
The example deliberately avoids claiming that every payload exposes the same fields. The adapter should record null plus a reason when the supported interface does not provide a value. Fabricated defaults are more dangerous than missing data.
Use two clocks, not one
Wall-clock time answers when an observation occurred in a shared timeline. A monotonic source clock answers how events were ordered inside one boot session. Both are necessary.
observed_at should use UTC with an explicit offset. source_clock_ns should never move backward during one clock_epoch. A payload restart creates a new epoch even if its sequence counter returns to zero. The ingestion service can then distinguish a reboot from delayed delivery.
Store clock-quality metadata separately:
- synchronization source, such as GNSS, PTP, NTP, or platform-provided time;
- last successful synchronization time;
- estimated offset and uncertainty;
- detection of an unexpected time step;
- adapter receive time and storage time.
A precise-looking timestamp is not proof of accurate synchronization. If uncertainty is unknown, say so and lower the correlation quality.
Keep native and derived thermal imagery distinct
The UVH2 listing separates the native 640 × 512 detector from 1280 × 1024 thermal super-resolution output. A data model should preserve that distinction. Upscaled or enhanced output is useful, but it is not a higher-resolution detector measurement.
Use an explicit representation field:
native_detector
processed_super_resolution
colorized_preview
visible_thermal_overlay
Derived assets should reference their parents and record the processing version, parameters, and checksum. Never overwrite the native input with an enhanced image. If a later model produces a different result, both versions should remain reproducible.
Thermal observation also does not automatically mean calibrated temperature measurement. If radiometric values are required, the contract needs documented units, calibration state, emissivity handling, reflected-temperature assumptions, measurement range, accuracy, and invalid-pixel behavior. When the source interface does not provide those fields, the pipeline should label the asset as qualitative thermal imagery.
Make partial pairs first-class events
A dual-sensor pipeline must tolerate one channel arriving late or not arriving at all. Use an explicit lifecycle:
received_visible_only
received_thermal_only
paired_pending_validation
paired_accepted
paired_rejected
expired_unpaired
Do not keep an event in memory forever while waiting for its partner. Persist the first observation, start a bounded correlation window, and finalize it as expired_unpaired when the deadline passes. A late partner can create a new reconciliation record without rewriting history.
The difference matters operationally. A missing visible frame may still leave a useful thermal observation. A missing thermal frame may still preserve visual context. Downstream systems can decide whether a partial bundle is usable, but they should not be told that a complete pair exists.
Validate at ingestion
Validation should reject malformed events before analytics or indexing. A dependency-free Python example can enforce some basic invariants:
from datetime import datetime, timezone
ALLOWED_THERMAL_REPRESENTATIONS = {
"native_detector",
"processed_super_resolution",
"colorized_preview",
"visible_thermal_overlay",
}
def parse_utc(value: str) -> datetime:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
if parsed.tzinfo is None:
raise ValueError("observed_at requires a timezone")
return parsed.astimezone(timezone.utc)
def validate_bundle(bundle: dict) -> None:
if bundle.get("schema_version") != 1:
raise ValueError("unsupported schema_version")
for field in ("capture_id", "vehicle_id", "mission_id", "clock_epoch"):
if not isinstance(bundle.get(field), str) or not bundle[field].strip():
raise ValueError(f"missing {field}")
parse_utc(bundle["observed_at"])
correlation = bundle["correlation"]
delta = float(correlation["delta_ms"])
limit = float(correlation["max_allowed_delta_ms"])
if delta < 0 or limit <= 0:
raise ValueError("invalid correlation timing")
if correlation["quality"] == "accepted" and delta > limit:
raise ValueError("accepted pair exceeds timing limit")
thermal = bundle.get("thermal")
if thermal and thermal["representation"] not in ALLOWED_THERMAL_REPRESENTATIONS:
raise ValueError("unknown thermal representation")
for channel in ("visible", "thermal"):
asset = bundle.get(channel)
if not asset:
continue
if asset["width"] <= 0 or asset["height"] <= 0:
raise ValueError(f"invalid {channel} dimensions")
if len(asset["sha256"]) != 64:
raise ValueError(f"invalid {channel} checksum")
Production validation should use a versioned schema format and bounded payload sizes. Keep rejected metadata in a quarantine stream with reason codes; do not silently discard it or mix it with accepted observations. Media decoders need their own resource limits because image dimensions in metadata cannot be trusted until the bytes are inspected.
Make idempotency content-aware
Networks retry. Edge computers reboot. Upload workers receive the same message twice. A stable event identity prevents those normal behaviors from multiplying observations.
Use capture_id as the business identity and verify that any repeat has the same immutable digest. The outcomes should be:
- same ID, same digest: harmless retry;
- same ID, different digest: conflict requiring investigation;
- new ID, same source sequences and clock epoch: probable duplicate from an upstream identity defect;
- new ID, same media checksums: duplicate asset or repeated capture requiring a documented decision.
Write the event and its asset references atomically, or keep an explicit state such as metadata_committed_assets_pending. A database row that points to missing media is not a complete ingestion result.
Separate observation from inference
Target recognition, tracking, anomaly scoring, mapping, and thermography interpretation are downstream results. Store them as derived records that point back to the immutable capture bundle. Each result should include:
- model or algorithm name and version;
- model-file checksum;
- input capture ID and asset representation;
- thresholds and preprocessing version;
- output coordinate system;
- confidence and known limitations;
- reviewer state when a human decision is required.
Do not write a detection back into the source event as if the camera observed a semantic truth. The camera produced pixels and metadata; an algorithm produced an interpretation. That separation makes reprocessing, disagreement analysis, and incident review possible.
Protect operational and personal data
Visible and thermal UAV data may reveal people, vehicles, infrastructure, precise locations, schedules, and operational patterns. Minimize collection to the approved mission, encrypt data in transit and at rest, use short-lived scoped credentials, and log access to original media.
Keep raw media separate from public application paths. Derived previews should remove unnecessary metadata and use redaction when the workflow requires it. Retention policy should apply to raw files, thumbnails, detections, exports, backups, and cached edge copies—not only to the primary database.
The adapter that reads the supported payload interface should have no broader command permission than it needs. Observation ingestion must not become an undocumented route to aircraft or gimbal control.
Test the failure modes
Before relying on the pipeline, replay at least these scenarios:
- visible frames arrive before, after, and without thermal frames;
- sequence counters restart after a payload reboot;
- wall time jumps while the monotonic clock continues;
- a processed thermal asset is incorrectly labelled as native;
- the same capture ID arrives with a different checksum;
- object storage succeeds but metadata commit fails, and vice versa;
- the adapter version changes during a mission;
- media is truncated, oversized, or declares false dimensions;
- telemetry and gimbal samples are older than the configured limit;
- a late event arrives after the correlation window expires.
For each test, define the expected event state and evidence before running it. A pipeline that merely avoids crashing is not necessarily correct. It should expose why a bundle was accepted, rejected, left partial, or quarantined.
A practical readiness checklist
Before production use, confirm that:
- the delivered payload and approved interface document match the adapter version;
- every boot session has a distinct clock epoch;
- wall-clock accuracy and monotonic ordering are recorded separately;
- pairing tolerance is explicit and mission-appropriate;
- native and processed thermal representations cannot be confused;
- partial and late observations have bounded, auditable states;
- media bytes are verified independently of metadata;
- retries are idempotent and digest conflicts stop processing;
- inference outputs never overwrite source observations;
- retention, redaction, encryption, and access logging cover edge and cloud copies.
A strong dual-sensor pipeline does not hide uncertainty. It turns timing, derivation, missing data, and interface revision into fields that can be tested. That makes the resulting data more useful to developers and more defensible to operators and reviewers.
Review the current UVH2 product page for the published platform and payload starting point. For a real integration, request the current K8T-V2 interface-control document, supported command and telemetry definitions, connector details, licensing terms, sample files, and a configuration-specific acceptance procedure.
Top comments (0)