You wired both cameras to a shared sync line, set one as master and the other as slave, hit record — and the capture shows a different frame count on each stream, with average rates a fraction of a hertz apart. That looks like failure, and it usually isn't: a handful of dropped frames produces both symptoms while the sync line does its job perfectly. This guide gives you three tests that settle the question, the numbers each test must meet, and the fix for each failure pattern they can show. It is for anyone running two or more cameras against one hardware pulse: stereo pairs, depth rigs, multi-camera stitch rows.
Unequal frame counts and rate gaps prove nothing
Message counts measure delivery, not alignment. At 30 fps, a 10-minute capture should hold 600 s × 30 fps = 18,000 frames per camera. Suppose the second camera records 17,961 frames: it dropped 39, or 0.22%, and its average reads 17,961 / 600 s = 29.94 Hz against 30.00 Hz. A 0.2% gap like that is explained completely by drops, with nothing wrong on the sync line. Two free-running cameras with no line connected at all will also land within a frame or two of 18,000 each, so identical counts prove just as little.
Use counts as triage only. A gap under about 1% carries no information about alignment, so go straight to the timestamp test. A gap above roughly 3% is a delivery problem — the slave is missing most pulses or the host is dropping frames under load. Isolate that first, one stream at a time, as in the three depth cameras at low frame rate guide, then judge alignment on a drop-free run.
What locked means in numbers
Define the pass band before you look at data, or every borderline run will talk you into a verdict. The frame period and the test statistic are:
At 30 fps that puts the fail band at ±16.7 ms: offsets scattered that wide mean nearest-frame pairing is slipping between neighbours, so the two sensors are exposing out of step. A constant offset C is harmless — it is just the difference between the two clocks' zero points. What must be small is ε. With device timestamps on both sides, identical cameras sharing one pulse typically hold ε in the tens of microseconds; anything under about 1 ms is comfortably locked.
Stamp both sides from the same clock domain. Comparing a host arrival stamp on one camera with a device stamp on the other injects milliseconds of USB or GigE delivery jitter into a band measured in microseconds, and the test becomes meaningless.
Step 1: pair frames and read the offset
Run this on a 60-second recording before any long capture.
- Read the sync role back from each camera, through the same interface you used to set it. Depth camera stacks often expose it as a driver parameter — inter_cam_sync_mode on some stereo depth modules — and a read-back that returns the free-running default means the setting never reached the camera. Both cameras silently free-running is the most common cause of a failed sync test.
- Record about 60 s with both streams live and nothing else loading the host.
- Pair frames by sequence number from each device, never by arrival order. The sequence-number pairing fix shows what arrival order does to a stream with drops, and pairing two synced cameras by timestamp covers the pairing logic itself.
- Compute offset_i for every pair, using device timestamps on both sides.
- Read the shape: one flat value with microsecond scatter is a pass; noise filling the ±16.7 ms band is a fail.
Worked example. The first five pairs of one 60 s run read 412 µs, 415 µs, 409 µs, 411 µs, 413 µs. The mean is (412 + 415 + 409 + 411 + 413) / 5 = 412 µs and the spread is 415 − 409 = 6 µs: locked, with 412 µs being only the two clocks' zero difference. A failing run looks like +15 ms, −12 ms, +4 ms, −16 ms — offsets swinging across the whole band while the slave exposes on its own clock.
Step 2: separate clock drift from misalignment
Plot offset_i against capture time. Three shapes decide it: a flat line is locked; a straight sloped line is locked with the two clocks drifting apart; a random walk across ±T/2 is not locked. Drift is normal — each camera's local crystal sits within a typical tolerance of 20-50 ppm:
Example: the offset climbs from 5,200 µs to 20,200 µs across a 600 s run. Δoffset is 15,000 µs, so the slope is 15,000 µs / 600 s = 25 µs/s = 25 ppm, right in the crystal band. At 25 µs/s, drift crosses one 33.3 ms frame period after 33,300 µs / 25 µs/s ≈ 1,332 s ≈ 22 min — which is why a short test passes the raw band and an hour-long panorama does not.
Fit the line, subtract it, and re-check the residuals: if they collapse back to tens of microseconds, the frames are still pulse-locked and only the stamps moved. For long recordings, stay in device timestamps and re-zero or detrend; the device-timestamp pairing guide shows how to keep both sides in one clock domain from the start.
Step 3: the moving-target test
The timestamp test lives or dies on clock domains; this one needs none. Put a rotor in view of both cameras. A blade of 100 mm radius spinning at 1 rev/s moves its tip at v = 2π × 0.1 m × 1/s = 0.63 m/s, so at 30 fps it travels 0.63 m/s × 33.3 ms ≈ 21 mm per frame period. Compare the blade tip's position in the master's frame with the same blade in the slave's paired frame:
- Same position, within a pixel or two: locked. With a 0.5 ms residual the tip moves only 0.63 m/s × 0.5 ms ≈ 0.3 mm, which stays invisible.
- Displaced by about 21 mm: the slave is exactly one frame period behind — pulses are not reaching it, or it is ignoring them.
- A different displacement every frame: both cameras are free-running.
Scale check: across a 300 mm field of view, a 12 MP sensor with roughly 4,000 pixels across resolves 300 mm / 4,000 px ≈ 75 µm per pixel, so a 21 mm displacement spans about 280 pixels — impossible to miss — while a locked pair's 0.3 mm stays under five.
The sharper variant is an LED panel driven from the master's frame-start output. Each master exposure toggles the pattern, so every paired frame must show the identical pattern; a slave that missed a pulse shows the previous one. One miss in ten thousand is a dropped frame, not a desync.
Use global-shutter cameras for this test. A rolling shutter exposes line by line and slants a fast blade even when both cameras are perfectly locked, faking offsets that do not exist. Two global-shutter options from the shop:
- Sensor format
- 1/1.7"
- Lens mount
- C-mount
- Sensor format
- 1.1"
- Lens mount
- C-mount
The MV-CU020-90GC runs the test over GigE at 2.5 MP. Step up to the MV-CH120-11UC when the rotor must be measured as well: 12 MP over USB3.
Skip the test with a factory-locked stereo pair
If the deliverable is paired depth rather than two independent streams, the verification shrinks to nothing. A stereo 3D camera is a factory-aligned pair in one housing sharing one clock by design, so there is no cross-camera sync line to test. Two such units:
- Type
- Stereo / structured light
- Type
- Stereo / structured light
Likely causes in order of probability
If Step 1 fails, work down this table; the first rows explain most failed sync tests.
| # | Cause | What you see | Confirm by | Fix |
|---|---|---|---|---|
| 1 | Sync role never applied | Both cameras free-run; counts look normal | Read the mode back before streaming | Set master and slave before the stream starts, then read back |
| 2 | Same role at both ends | Two masters: nobody follows. Two slaves: silent line | LED or scope on the line shows no pulse train | Exactly one master; every other camera listens |
| 3 | Sync line wiring fault | Slave free-runs, master steady | Continuity check plus a common-ground check | Rewire per the hardware sync wiring guide |
| 4 | Wrong edge or input disabled | Slave misses every pulse | Invert the polarity in a 5 s run | Match the polarity and enable the slave's input |
| 5 | Slave left in continuous acquisition | The line is ignored entirely | Check the acquisition and trigger mode; why the trigger stops after one frame covers the mode trap | Put the slave into triggered acquisition |
| 6 | Master pulses faster than the slave exposes | Counts diverge; offsets on surviving pairs stay constant | Step 1 passes while counts differ | Pulse at or below the slave's frame rate |
| 7 | Host drops frames | Rate gap grows as streams are added | One stream at a time on the host | Separate interfaces or reduce per-stream bandwidth |
Cameras on an embedded carrier often reach the host through a serializer, and the sync line runs through that chain too; its wiring follows the serializer's own rules, as in wiring a MAX9296A serializer for two GMSL2 cameras.
The decision for each outcome
Run the three tests in order and one of four verdicts follows:
- Offsets flat at C ± tens of microseconds on device stamps: locked. Record the panorama.
- Offsets drift linearly at 20-50 ppm but the detrended residuals are microseconds: locked — only the stamps move. Detrend long runs and ship the capture.
- Offsets swing across the ±16.7 ms band: not locked. Work the cause table top to bottom, re-run Step 1, then confirm with the rotor before any production capture.
- Stamps unusable because both sides come from the host: trust the rotor instead. The same blade angle in a hundred consecutive paired frames is sync you can build on.
Demand both proofs from any rig someone else wired for you: the flat offset shows the wiring is right, the rotor shows the images agree.
Checklist before you trust the recording
- Read the sync role back on every camera before the first frame — a value that never reached the sensor leaves both free-running.
- One master only, every other camera on the line set to listen.
- Common ground across all sync lines; a floating reference clocks random edges.
- Stamp with device timestamps where the driver offers them — host arrival stamps carry milliseconds of jitter.
- Log sequence numbers beside the stamps so drops cannot mix pairs.
- Run a 60 s baseline before the long capture; it exposes free-running in a minute.
- Finish with the rotor or LED check; physical proof beats every clock argument.
- Chase any rate gap above about 1% as a delivery problem before judging alignment.
Cameras, finders and help
Pick bodies that expose a hardware sync input and output plus a global shutter, and the whole verification collapses to a five-minute rotor test. The stereo and structured-light 3D sensors and area scan camera listings filter both traits, the camera finder narrows by interface and shutter, and the configurator's camera step pairs the body with trigger and cable accessories in one pass. More depth-rig material sits on the 3D vision topic page. If a rig resists every test above, send the wiring and the failing numbers through the contact page and we will work the bench with you.
Questions
Do identical frame counts prove two cameras are hardware-synced?
No. Two free-running 30 fps cameras each record about 18,000 frames in ten minutes, so matching counts happen by chance even with no sync line connected. Counts measure drops, never alignment. The proof is a constant timestamp offset with microsecond scatter, plus the rotor check: both cameras must freeze a spinning blade at the same angle, which free-running cameras cannot do beyond a frame or two.
How close must timestamps be to call hardware sync good at 30 fps?
Pair frames by sequence number, subtract device timestamps, and require one constant offset with scatter well inside the 33.3 ms frame period; tens of microseconds is typical for identical cameras stamped at exposure start. Scatter that swings across roughly ±16.7 ms, half a period, means nearest-frame pairing is slipping and the sensors expose out of step.
Why do the two clocks drift apart in a long recording, and does that mean sync failed?
No. Each camera's crystal runs within a typical 20-50 ppm tolerance, so device timestamps drift apart 20-50 µs per second, up to 3 ms per minute. At 30 fps that crosses one 33.3 ms frame period after roughly 11-28 minutes. Fit the slope, subtract it, and re-check the residuals: if they fall back to microseconds, the frames are still pulse-locked and only the stamps moved.
Can software timestamps from the host replace the hardware test?
Host arrival stamps carry milliseconds of USB or network delivery jitter, which swamps a pass band measured in microseconds, so treat them as a coarse check only. The moving-target test needs no clock domain at all: a 100 mm rotor at 1 rev/s moves its tip about 21 mm per 33.3 ms frame at 30 fps, so one frame of disagreement is plainly visible while a locked pair agrees to under half a millimetre.
What if one camera's frame count is far below the other's?
Treat it as a delivery problem first. A gap above a few percent usually means the slave is missing most pulses, its input is disabled, or the host drops frames under load. Read the sync role back, confirm one master and one listener, test the streams one at a time, and only then run timestamp pairing, because pairing across heavy drops mixes frames taken at different instants.
Does the rotor test work with rolling-shutter cameras?
Poorly. A rolling shutter exposes the frame line by line, so a fast blade appears slanted even when both cameras are perfectly locked, and that skew can mimic or hide a real offset. Use global-shutter bodies for the test, or slow the target until skew is negligible: at a quarter of the tip speed the same rotor still moves about 5 mm per frame, enough to separate off-by-one-frame from locked.



