LightingTroubleshooting

Fiducial pose drift over time: check lighting before the code

Fiducial pose drift over time usually traces to the image, not the solver: rank the causes, run the replay test, then lock exposure, focus and light.

LumaSpec Vision11 min read Share on LinkedIn

On this page 9 sections
  1. 01Symptom: readings that creep, and a restart that hides it
  2. 02Likely causes, ranked
  3. 03The replay test: separate image drift from solver drift
  4. 04Worked example: 0.5 mm of drift is 0.73 pixels on a 160 mm tag
  5. 05Why lighting moves a sub-pixel corner
  6. 06Fix it: lock the capture, then own the light
    1. Lock exposure and gain
    2. Lock focus
    3. Own the light
  7. 07Choose the controller for a locked light
  8. 08Checks before you call it fixed
  9. 09Questions

Your camera and the fiducial tag have not moved, yet X, Y, Z, yaw, pitch and roll creep by 0.5 to 2 mm and a fraction of a degree over an hour, and restarting the code brings the numbers home. Before rewriting the pose math, look at the image: a per-frame pose solver has no memory, so slow drift with a reset on restart almost always lives in the capture path. In the bench case behind this guide — a 160 mm AprilTag read at about 1 m with a 1920 × 1080 camera — the whole day's drift in Y worked out to 0.73 pixels. You will rank the likely causes, run a replay test that proves where the drift lives, and lock exposure, focus and lighting so the readings hold across a full shift.

Symptom: readings that creep, and a restart that hides it

The signature is specific. The pose wanders in a band that widens over hours, nothing mechanical has moved, and restarting the process returns the numbers to where they started. On the bench case behind this guide, Y crept about 0.5 mm, the full set of channels moved roughly 2 mm peak to peak, and rotation wobbled by a fraction of a degree — all with the tag bolted in place and untouched.

A per-frame estimator has no memory: every pose comes from the corners found in that single frame. Code that cannot remember cannot drift, so if the output depends on when the process started, the state lives upstream of the solver — in the camera and its auto modes. A restart changes something precisely because re-opening the device re-applies driver defaults for exposure, gain and focus.

An offset that is constant instead of creeping is a different fault, and a calibration-and-frames one: the fiducial pose wrong-distance checklist covers that path.

Likely causes, ranked

Work down the list; each cause carries a test that clears or convicts it in minutes.

  1. Ambient light changed. Daylight through a window swings hard between morning and evening, and a shadow crossing the tag is worse: a published comparison of AprilTag detection found that a shadow over the marker raised median translation error by 89% on small boards and 70% on large ones. Test: difference the first and last frame of a run, and log one brightness statistic per frame.
  2. Auto exposure or auto gain is still on. Every auto mode rescales pixel values as the scene brightens, and sub-pixel edge positions move with them. Test: read the exposure and gain values every frame and log them beside the pose; the changes line up with the drift. The auto-modes-off guide lists the controls to pin.
  3. Auto focus moved. A focus motor step changes edge sharpness, and corner localization follows sharpness. The bench case pinned its V4L2 controls at open with focus_auto=0 and focus_absolute=5 — correct, but such settings revert whenever the capture stack re-opens the device, which is exactly what a restart does. Test: log the focus read-back beside exposure.
  4. Thermal and mechanical creep. Aluminum grows about 23 µm per metre per kelvin, so 10 K along a 1 m bracket arm moves tag or camera 0.23 mm — real, but an order of magnitude short of a 2 mm drift. Test: a dial indicator, or a second, unrelated fixed target in frame.
  5. Solver numerics. Last for a reason: the estimator is deterministic on a given image. Test: the replay test below. If a still image drifts, only then start reading detector source.

The replay test: separate image drift from solver drift

One saved frame settles the argument in under an hour.

  1. Save one frame at run start — the frame your first pose came from.
  2. Point the loop at the file instead of the camera and run the full path — detector, corners, then solvePnP from OpenCV's calib3d module — for an hour.
  3. Read the spread. Pose that holds to hundredths of a pixel on a still image clears the solver; the drift is in the capture path. Pose that wanders on a still image convicts the code: corner ordering, a threshold riding the histogram, or rounding injected downstream. Where the corner-to-pose step itself needs a refresher, the fiducial tag pose estimation guide walks from detected corners to R and t.
  4. Difference the first and last live frames of the real run. On the bench case the image had genuinely moved 1–2 pixels and the room lighting had visibly changed across the roughly 7-hour run: the data moved, the code did not.

Log image statistics next to pose from day one — the machine vision lighting stability test lists the metrics worth recording before a deployment.

Worked example: 0.5 mm of drift is 0.73 pixels on a 160 mm tag

Inputs, each with its source:

  • Tag size: 160 mm — the printed side length of the bench case's tag.
  • Tag span: 235 px — measured across the tag in the first saved frame.
  • Logged drift: 0.5 mm in Y — read off the pose plot after hours of run.
  • Focal length: fx = 1506.099 px — the case's fixed calibration, frame 1920 × 1080.

Rule. On the tag's plane, one pixel covers tag size ÷ span, so any millimetre drift divided by that is the image shift the solver actually saw. The pinhole model turns it around into stand-off: distance = focal length × size ÷ span.

mm_per_px = tag_mm / span_px drift_px = drift_mm / mm_per_px Z_mm = f_px × tag_mm / span_px

Arithmetic.

  • Millimetres per pixel: 160 mm ÷ 235 px = 0.681 mm/px
  • Drift in pixels: 0.5 mm ÷ 0.681 mm/px = 0.73 px
  • Stand-off: 1506.099 px × 160 mm ÷ 235 px = 1025 mm, about 1.03 m
  • Rotation wiggle: 0.73 px ÷ 235 px = 0.0031 rad, about 0.18°

Result. At hour seven the solver was asked to place the tag's corners 0.73 px away from where it placed them at hour zero. That sits squarely inside the range where sub-pixel corner estimates move when illumination and exposure change, so the 0.5 mm of pose drift was the image moving — not the math.

Decision. Divide your repeatability tolerance by millimetres per pixel to get the station's pixel budget. For ±0.2 mm on this rig: 0.2 mm ÷ 0.681 mm/px = 0.29 px. Above about 0.3 px of image motion over a shift, this station cannot hold ±0.2 mm — so lock exposure and focus, make the tag's own light dominate the room, and hold the 12-hour replay spread under 0.3 px.

Why lighting moves a sub-pixel corner

A pose from corners inherits everything from the intensity profile across the black-white boundary. Detection binarizes or traces gradients to place each corner to a fraction of a pixel, and the crossing point rides on that profile. Rescale it with auto exposure, dim it with a passing cloud, shade it with a shoulder, and the corners move by fractions of a pixel. At a metre of stand-off, fractions of a pixel are tenths of a millimetre — the exact size of the drift in the logs.

Blur makes it worse. The bench case's tag edges smeared across roughly 6 pixels, far softer than a crisp print; a wide, flat gradient gives the corner estimator a noisy target and stacks random scatter on top of the drift. Print the tag matte and dense, focus hard, and the same solver holds hundredths of a pixel.

Two room effects deserve their own checks. Mains-driven room lights can beat against the exposure and modulate brightness frame to frame — the camera flicker diagnosis guide walks that fault. And when the station must pass an acceptance test, write the ambient rejection into the spec explicitly, as the ambient light rejection guide shows.

Fix it: lock the capture, then own the light

Lock exposure and gain

Set a fixed exposure time and fixed gain, and turn every auto mode off. Size the exposure so the tag's white cells sit around 70–85% of full scale with no clipping: bright enough that ambient swings are a small perturbation rather than the signal. A static target has no motion-blur limit, so exposures of tens of milliseconds remain available when you want sensor noise averaged down.

Lock focus

Disable auto focus when the device opens, re-assert the pinned value after every re-open, and log the read-back. On V4L2-based stacks the controls are focus_auto and focus_absolute; set them once at open and again after any re-open, and a focus hunt becomes impossible to miss in the log.

Own the light

Shield the target from the window, put a small ring light or bar light on the tag at a locked setting, and mount the print on something matte and rigid. When ambient still dominates, or the target moves, strobe. Shortening the camera's exposure from 30 ms to a 1 ms strobe pulse cuts the ambient photons the sensor integrates by 30:1, while the pulsed light holds the tag bright inside that millisecond; the same room swing then moves the corner estimate roughly thirty times less. That factor is an estimate — the scaling tracks the ambient share of the signal — but it is the reason strobed fiducial stations shrug off sunrise.

If the ambient cannot be removed, measure it and follow it: the closed-loop lighting control guide shows a controller trimming intensity against a feedback reading, and the ambient light variation guide covers the shielding and scheduling moves for cells that live with daylight.

Choose the controller for a locked light

The controller is the first place to buy stability. Its job is to hold one intensity for a full shift and, when you want the ambient rejection of short pulses, to switch the light faster than continuous drive allows.

For a bench rig at a fixed stand-off, the LS-CTL-0290 gives digital control of four channels of 24 V constant-voltage output at 60 W total — enough for the small ring or bar light most tag stations need.

Light controllersDigital constant-voltage light controller, 60 W, 4 channels (LS-CTL-0290)LS-CTL-0290
Output
Constant voltage 24 V
Channels
4 channels
Control
Digital
$449.00View partBuild a system with it

When you want short pulses against ambient light, choose a strobe-capable box. The LS-CTL-0070 strobes four channels at up to 200 W, set over RS232.

Light controllersDigital control box, 4 ch, 200 W, RS232 (LS-CTL-0070)LS-CTL-0070
Output
Constant voltage 24 V
Channels
4 channels
Control
Digital
Feature
Strobe
$549.00View partBuild a system with it

In a larger cell, or one where the controller sits on the network, the LS-CTL-0264 drives two channels of 24 V high-power output at 400 W and carries a network option.

Light controllersHigh power digital controller (LS-CTL-S53, network option), 2 ch, 400 W (LS-CTL-0264)LS-CTL-0264
Channels
2 channels
Control
Digital
Feature
High power
Output
Constant voltage 24 V
$769.00View partBuild a system with it

Leave analog knob controllers off the shortlist for this job: their output is whatever the knob says today, with no logged value to compare against last month — and a setting you cannot log is a setting you cannot defend.

The light controllers category lists the whole range; narrow by strobe, channel count and constant-current drive in the light controller finder, or work through the lighting step of the configurator.

LightingLight controllers

Power, dim and strobe your lights

573 models · from $120.00

Checks before you call it fixed

  • 12-hour replay on the saved frame: pose spread inside the pixel budget from the worked example, with margin.
  • Exposure, gain and focus logged per frame: any change flags a device re-open or an auto mode that survived the configuration.
  • First-versus-last frame difference at the end of every shift: no visible motion.
  • An 8-hour pose log reported as peak-to-peak and standard deviation, in both millimetres and pixels — quote both, because the conversion is the whole argument.
  • Tag on a rigid, matte, unbowed mount; if you reprint or move it, re-measure the span in pixels before trusting old budgets.
  • One run on a bright afternoon and one after dark: the two logs should be indistinguishable.

Size the light and controller against your tag, stand-off and ambient, or send the numbers through the contact page and we will return a shortlist with the arithmetic attached.

Questions

How much fiducial pose drift is acceptable on a fixed set-up?

Set the budget from your tolerance, not from the solver. Convert it to pixels with millimetres per pixel = tag size ÷ span: the 160 mm tag spanning 235 px gives 0.681 mm/px, so ±0.2 mm repeatability allows about 0.29 px of image motion. With locked exposure, locked focus and a dominated room, tenths of a millimetre at a 1 m stand-off is a realistic target. Drift that grows over hours is a defect, not noise.

Why do camera coordinates drift more than tag coordinates?

Both poses come from the same corners, but the camera's position in the tag frame sits about 1.03 m from the tag, so rotation error is multiplied by that lever arm: 0.0031 rad of wiggle moves the camera origin by roughly 1.03 m × 0.0031, about 3.2 mm, while the tag's own position error stays near the 0.5 mm image shift. Fix the pixels and both frames settle together.

Does restarting the process fix fiducial pose drift?

A restart is a diagnostic, not a fix. It re-opens the camera and reloads driver defaults, which is why the readings return — and why they leave again at the next lighting change. Use it once to confirm the pattern, then run the replay test: an hour of log on one saved image tells you more than a week of restarts.

Does a bigger tag reduce pose drift?

A bigger tag improves rotation, not translation, at a fixed stand-off. Rotation error scales with pixel shift ÷ span, so doubling the printed tag halves the degrees per pixel of wiggle. Translation error is set by stand-off: millimetres per pixel ≈ distance ÷ focal length, so halving the camera-to-tag distance halves the millimetre drift. Do both if the mechanics allow.

What exposure should I lock for a static fiducial camera?

Whatever fills the tag's white cells to roughly 70–85% of full scale without clipping, at a fixed gain. A static target has no motion-blur limit, so tens of milliseconds are fine and average sensor noise down. Strobe when the scene moves or ambient dominates: a 1 ms pulse inside a 30 ms frame cuts ambient integration about 30:1.