Cameras & sensorsExplainer

Software auto exposure for a raw camera: the bounded loop

Software auto exposure for a raw camera: use GenICam exposure in microseconds, a bounded update step of 0.5, and a worked example clamped at 2.5 ms.

LumaSpec Vision11 min read Share on LinkedIn

On this page 10 sections
  1. 01Where the exposure decision lives
  2. 02A five-minute proof that writes reach the sensor
  3. 03Measure what the sensor saw
  4. 04The bounded update rule
  5. 05Worked example: motion sets the ceiling
  6. 06Match every measurement to its frame
  7. 07Cameras and the host that run the loop
  8. 08When fixed exposure is the better engineering
  9. 09Commissioning checklist
  10. 10Questions

A raw camera hands every exposure decision to your software: with no ISP in the path, your code measures the image and writes the sensor's integration time itself. The loop needs four things — a writable exposure control, a stable brightness statistic, a bounded update rule and frame-accurate bookkeeping. Each one comes with working numbers below: the GenICam features a GigE Vision or USB3 Vision camera must expose, an update rule damped at 0.5 with ratio clamps of 0.5 to 2.0, and a worked example in which motion blur, not the sensor, caps exposure at 2.5 ms.

Where the exposure decision lives

Automatic exposure does two jobs: measure brightness, then change the sensor's integration time. Raw output describes the pixels, not the owner of those jobs. A camera that conforms to GigE Vision or USB3 Vision must describe its controls in a GenICam feature list, and the GenICam Standard Features Naming Convention defines exactly the pieces you need: ExposureTime as a time value in microseconds, ExposureAuto with the settings Off, Once and Continuous, and the limit pair AutoExposureTimeLowerLimit and AutoExposureTimeUpperLimit.

So the first move is one read:

Where the loop runs How you know Your move
In the camera ExposureAuto answers Once or Continuous Set it to Off and take over
In your software ExposureAuto answers Off and ExposureTime accepts writes Run the bounded loop below

The camera's own auto loop sits ahead of the pixel formatter, so raw Bayer output neither provides it nor removes it. White balance is a separate function that rescales color channels and says nothing about integration time — never infer one from the other. When the exposure control reports register counts or unusual units, translate them through camera exposure and gain units before building the loop; for writing ExposureTime from code, work from setting exposure time through a GenICam API in Python.

A five-minute proof that writes reach the sensor

Tune nothing until one test passes on the production path — the same cable, driver, pixel format and frame rate you will ship.

  1. Freeze the scene: gain fixed, illumination steady, every auto mode Off.
  2. Write ExposureTime = 2000 µs and capture one frame.
  3. Write ExposureTime = 4000 µs and capture a second frame of the same scene.
  4. Subtract the black offset from the region mean at both settings. Below about 90% of full scale, the corrected mean must double within a few percent.

If it doubles, your writes reach the sensor through the path that matters. If it does not, the command dies in a wrapper or the statistic is polluted by a saturated patch, and no loop tuning repairs that. Cameras also round writes to their exposure increment, so read back after writing: a 2828 µs request on a 10 µs increment lands at 2830 µs, and the loop should use the read-back value. A write cannot interrupt the frame being integrated, so it takes effect at the next frame boundary — see changing exposure while acquisition runs.

Measure what the sensor saw

Brightness has to represent the inspection, not whatever fills the frame. Measure a fixed patch of midtones that every part presents. A measurement box that follows the detector chases scene content instead of illumination, and the loop then hunts; the mechanism is laid out in exposure instability from a moving measurement region.

On 12-bit samples, full scale is 4095 counts and the example station's black offset is 64 counts. Subtract that offset before forming any ratio, because offset does not scale with exposure. Record three statistics per frame:

  • The mean of the corrected region — or a percentile if bright patches wander through, with the 60th a common starting choice to test on your samples.
  • The saturated fraction, alarmed above 1% of region pixels over 4000 counts; a healthy mean can coexist with clipped highlights.
  • A reliability floor: below about 40 corrected counts — one tenth of the example's working level — ratios are noise.

On raw Bayer color, measure one channel population consistently: green sites are half the samples in the pattern, so a green-only mean is stable and representative. Gamma-corrected previews are for operators only; gamma breaks the proportionality the controller depends on.

The bounded update rule

In its linear range — above the noise floor, below saturation — a sensor's collected signal is proportional to integration time. That single proportionality is the whole controller. With gain and illumination fixed:

S = mean(raw ROI) - black_offset r = clamp(S_target / max(S, S_floor), 0.5, 2.0) t_next = clamp(t_current × r^0.5, t_min, t_max)

S is the black-corrected region mean in counts and S_target its setpoint; S_floor is the reliability floor; r is the per-update ratio; t is the ExposureTime value in microseconds; t_min and t_max come from the camera's limits crossed with your motion and timing limits.

The two clamps do the stability work. With the exponent 0.5, each update shrinks the remaining error to its own square root: a factor-of-two error runs 2.0 to 1.41 to 1.19 to 1.09 to 1.04, inside a 5% band in four updates. The ratio clamp bounds any single write to 1.41x up or 0.71x down, so one polluted measurement cannot slam the sensor. Hold exposure while S stays within 5% of target either side; that deadband stops constant writes on a stable scene and must still fit inside the inspection's brightness tolerance. If you commission with the camera's own Once mode instead, first read checking the auto exposure upper limit at startup.

Worked example: motion sets the ceiling

A station runs 12-bit samples at 2000 µs. The region mean reads 464 counts, the black offset is 64 counts, and the target mean is 864 counts:

S = 464 - 64 = 400 counts r = (864 - 64) / (464 - 64) = 800 / 400 = 2.0 t_request = 2000 µs × 2.0^0.5 ≈ 2828 µs

All values are counts except t, in microseconds; the 464 and 864 counts are raw region means before black correction.

Before sending 2828 µs, check blur. The part moves at 40 mm/s, the image scale is 0.10 mm/pixel and the inspection tolerates 1 pixel of motion blur:

t_blur = blur_limit × image_scale / part_speed t_blur = 1 pixel × 0.10 mm/pixel / 40 mm/s = 0.0025 s = 2.5 ms

The blur limit is in pixels, the image scale in mm per pixel and the speed in mm per second, so t_blur comes out in seconds.

The controller clamps its request to 2500 µs. Expected corrected signal at that exposure: 400 × (2500 / 2000) = 500 counts. The 800-count target is out of reach, and no number of updates changes that, because blur caps exposure while light is short. The fix ladder runs in order: add light — this station needs 800 / 500 = 1.6x more to reach target inside 2.5 ms — then open the aperture, and only then accept gain, which brightens the image without collecting one additional photon.

Match every measurement to its frame

Capture, processing and control writes run asynchronously, so pair each statistic with the exposure that produced it, not with the most recent request. A write lands at the next frame boundary, and with one frame integrating while another is in flight, the statistic you compute reflects the exposure set two frames earlier: 67 ms of dead time at 30 fps. That dead time is the second reason the loop is damped rather than proportional-only.

Four rules keep the bookkeeping honest:

  • Log the frame ID, the corrected statistic, the exposure that produced it and any pending command.
  • Allow one outstanding command; send the next correction only after a frame carries evidence of the last one.
  • Never stack corrections from stale frames — a queued run of dark images can drive exposure skyward after the camera already received a sufficient increase.
  • Prefer frames stamped with their own exposure; GenICam chunk data names the stamp ChunkExposureTime, and per-frame exposure and gain metadata shows how to read it.

The pairing problem grows when the image also drives a downstream decision: matching asynchronous frames with timestamps covers it, and the full image-feedback latency budget puts a number on every queue in between.

Cameras and the host that run the loop

GigE Vision and USB3 Vision cameras carry the same GenICam exposure controls — conformity makes ExposureTime in microseconds part of the feature list — so choose by frame rate, cable reach and host, not by exposure support. Browse area scan cameras or filter the camera finder by sensor and mount.

The LS-CMR-0053 is the GigE pick: 12 MP from a 1/1.7" IMX226 rolling-shutter sensor at 9 fps.

Area scan cameras12 MP GigE area scan camera, IMX226, 1/1.7" rolling shutter, 9 fps (LS-CMR-0053)LS-CMR-0053
Sensor format
1/1.7"
Lens mount
C-mount
Interface
GigE
Resolution
12 MP
$560.00View partBuild a system with it

One frame every 111 ms at 9 fps means the four-update settle of this loop takes about 450 ms — timing will never bind before blur does at that rate.

The LS-CMR-0166 is the USB3 counterpart: the same 12 MP IMX226 sensor at 30 fps, 4000 × 3000 pixels.

Area scan cameras12 MP USB3 area scan camera, IMX226, 1/1.7" rolling shutter, 30 fps, 4000 × 3000 resolution (LS-CMR-0166)LS-CMR-0166
Sensor format
1/1.7"
Interface
USB3
Resolution
12 MP
Shutter
Rolling
$560.00View partBuild a system with it

A 30 fps loop gives a 33 ms frame period and the 67 ms two-frame delay from the section above — dead time that justifies the 0.5 damping on its own. Both sensors use rolling shutters, so at 40 mm/s check motion skew across the frame separately from brightness: a correct mean does not repair geometry.

Run the loop on a vision controller rather than a shared office PC, so acquisition, measurement and the exposure write share one machine and one timing domain. The MV-VC2140-128G40-1T is the host for this station: a vision box with camera ports, built to carry acquisition and processing on one controller.

Vision controllersHikrobot MV-VC2140-128G40-1T VC2100 Vision Controller, N97, 8GB+128G SSD+ 1THDDMV-VC2140-128G40-1T
LAN ports
3-4 LAN ports
USB 3 ports
2 USB 3 ports
Light outputs
4 built-in channels
Memory
8 GB
$1,679.00View partBuild a system with it

To lock the rest of the station, use the camera configurator, and bring the blur budget and control behavior to application support.

When fixed exposure is the better engineering

If illumination and geometry repeat part to part, lock the exposure and stop. A running loop hides a failing lamp and varies images between parts that should look alike. For dimensional work, check that brightness changes do not move your edges: contour position jitter under automatic exposure is a separate acceptance test from hitting the mean target.

Four conditions demand an explicit response rather than more loop gain:

  • Saturation: the mean reads healthy while highlights clip; the 1%-over-4000-counts alarm catches it.
  • Flicker: on 50 Hz mains the light ripple repeats every 10 ms, so hold exposure to whole multiples of 10 ms or the loop chases bands.
  • Low signal: below the floor, stop correcting and raise an alarm; an exposure jump from noise buys nothing.
  • Failed writes: flag them and stop inspecting — an unwritten limit is an uncontrolled image.

Commissioning checklist

  • Step the light down and up and record settling and overshoot; a loop damped at 0.5 shows none.
  • Run at the production frame rate with logging and processing both on.
  • Test at t_min and at the blur clamp, not only at the target brightness.
  • Move real parts through the reference patch and count false corrections.
  • Archive pixel format, black offset, target, clamps and deadband with the recipe, so any later change traces to a number.

When the station changes — new part speed, new light, new lens — re-run the blur arithmetic first. That ceiling decides everything else in the loop.

Questions

Does raw output mean the camera cannot run automatic exposure?

No. Pixel format and exposure automation are independent: a camera that conforms to GigE Vision or USB3 Vision publishes ExposureAuto — Off, Once or Continuous — through GenICam whether its output is raw Bayer or fully processed, because the camera's loop sits ahead of the pixel formatter. Set ExposureAuto to Off; from then on your software owns the loop and writes ExposureTime in microseconds itself.

What damping and limits should the first version use?

Start with a damping exponent of 0.5 and clamp the per-update ratio between 0.5 and 2.0. The clamp bounds every step to 1.41x up or 0.71x down, so one polluted measurement cannot slam the sensor, and a factor-of-two brightness error closes to within 5% in four updates. Add a plus-or-minus 5% deadband around the target to stop constant writes on a stable scene.

Why did my controller stop raising exposure at 2.5 ms?

Because the blur limit bound first. At a part speed of 40 mm/s, an image scale of 0.10 mm/pixel and a 1-pixel blur allowance, the ceiling is 1 x 0.10 / 40 = 2.5 ms, so the requested 2.83 ms was clamped. With corrected signal reaching only 500 counts against a target of 800, exposure is finished: add about 60% more light or open the aperture.

Should the loop measure the whole frame or one region?

One fixed region of midtones that every part presents. A detector-following box makes the measurement chase scene content instead of illumination, and the loop then hunts. On 12-bit data, keep a separate saturation alarm — more than 1% of region pixels above 4000 of 4095 counts — because a healthy mean can coexist with clipped highlights that carry the defect information.

How do I know which exposure produced a measurement?

A write cannot interrupt the frame being integrated, so it applies at the next frame boundary; with one frame integrating and one in flight, your statistic reflects the exposure set two frames earlier — 67 ms at 30 fps. Prefer frames stamped through chunk data (GenICam names the stamp ChunkExposureTime), allow one outstanding command, and never stack corrections from stale frames.

Is gain a substitute when exposure hits its ceiling?

No. Gain amplifies signal already collected; it gathers no additional photons, so contrast in the noise gets worse. Reach the target with light or aperture first — the worked example needed 1.6x more light to hit 800 corrected counts inside a 2.5 ms exposure. If gain is unavoidable, re-run the defect-detection test at the elevated setting before release.

Corrections

  • Rewritten for usefulness: specific values with their basis, the named standards, a worked example, no hedging.