You enabled a depth stream at 60 FPS and the frame timestamps arrive 33 or 69 ms apart. The camera is not faulty and your loop is not dropping frames: the depth exposure no longer fits inside the frame period, so the sensor stepped down to the next supported rate without telling you. At 60 FPS a frame lasts 16.7 ms, which is the hard ceiling on depth exposure. This guide gives you that ceiling for every common rate, a ten-second timestamp test that names the cause, the exposure decision for moving versus dark parts, and the host-side checks for the cases exposure does not explain.
The ceiling: exposure must fit the frame period
A depth frame is an IR image the sensor integrates and then reads out. Integration cannot run into the next frame, so the exposure you set — or that a range, motion-range or accuracy preset sets for you — must fit inside the frame period with a small readout margin. Ask for more and the firmware does not clip the exposure; it drops to the next lower supported rate and keeps the integration you asked for. That is why a stream enabled at 60 FPS starts delivering frames 33 or 69 ms apart.
The readout margin is small: the illustrative log below runs about 2 ms long at 15 FPS and under 1 ms at 60 FPS. Planning on 2 ms of margin gives the working numbers:
| Rate you enable | Frame period | Exposure that fits | Timestamp spacing to expect |
|---|---|---|---|
| 60 FPS | 16.7 ms | 15 ms or less | 16-18 ms |
| 30 FPS | 33.3 ms | 31 ms or less | 33-35 ms |
| 25 FPS | 40.0 ms | 38 ms or less | 40-42 ms |
| 15 FPS | 66.7 ms | 65 ms or less | 67-70 ms |
Every camera that integrates light obeys this ceiling, not just depth sensors. The area-scan version is why your GigE camera frame rate drops at longer exposure times; the ToF-specific trade-off between integration and range is covered in ToF camera exposure time and frame rate trade-off explained.
Read the timestamps: a ten-second test
Print the per-frame depth timestamp inside your capture loop and subtract consecutive values. Pseudocode — substitute your SDK's own calls:
# illustrative pseudocode, not a specific SDK
enable_depth(width=640, height=480, fps=60) # request 60 FPS
while True:
frames = wait_for_frames() # the SDK's blocking call
print(depth_timestamp_ms(frames)) # one number per frame
A coded-light camera with its range preset raised high (long IR exposure) prints something like this:
0 69 138 207 277 346 415 485 554 623
The spacing sits near 69 ms. 1000 ÷ 69 = 14.5, so the camera is on its 15 FPS mode: the preset's integration fits neither 16.7 ms nor 33.3 ms, but it fits 66.7 ms with about 2 ms to spare.
Set the preset to minimum and the log tightens:
0 33 66 99 133 166
1000 ÷ 33 = 30. Exposure now fits a 33.3 ms period but not a 16.7 ms one. Shorten it once more:
0 16 33 49 66 83
1000 ÷ 16.7 = 60. Full rate. Three outcomes, three causes: the rate follows the preset (exposure-limited), the rate stays low at minimum exposure (camera- or host-limited), or the rate never matched what you requested.
The likely causes, in order, and each test
- Depth exposure longer than the frame period. The most common cause, and it usually appears right after someone raises a range, motion-range or accuracy preset, because those presets map to IR exposure. Test: set the preset or exposure to minimum and re-read the timestamps. Fix: hold exposure at or below the table value, or enable the lower rate explicitly so the spacing you get is the spacing you planned for.
- A second stream on the same link. Depth plus IR or color on one USB 3 connection shares bandwidth, and both rates fall. Test: enable depth only and re-measure. If the rate recovers, work through testing depth cameras one stream at a time and fixing USB bandwidth stalls before adding streams back.
- The host path. A shared USB controller, a hub, or a real-time (RT) preempt kernel trading USB completion latency for deterministic servo threads. A camera that must be replugged after a capture session crashed is pointing at this layer. Test: give the camera its own controller, budget the USB bandwidth first, and boot the stock kernel once to compare if you run an RT build.
- Your own processing. A point-cloud filter that runs per frame caps the loop while the sensor runs fine. Test: print the timestamp before any processing executes. The fix is in point-cloud filter slows depth camera FPS: fix the CPU load.
Why long exposure buys range and costs frames
A structured-light camera correlates a projected pattern against what the sensor sees, and every returning photon feeds that correlation. Dark, distant and steeply angled surfaces return the least light, which is exactly where holes appear. Doubling exposure doubles the collected light: moving from 16.7 ms to 33.3 ms is a 2× gain, worth as much as one stop of illumination. That is why a range preset wants long integration, and why it cannot coexist with 60 FPS.
Time-of-flight cameras obey the same ceiling on the IR frames that feed their timing measurement; a ToF sensing camera such as the MV-DT01WMU drops its rate by exactly this arithmetic when its exposure exceeds the period.
- Type
- Depth / ToF camera
Raising gain is not a substitute: gain multiplies signal and noise together, and it does not create photons.
Choosing exposure when the part moves
Motion smears the pattern by the distance the surface travels during integration:
At 250 mm/s — a slow robot arm or conveyor — a 33.3 ms exposure smears 250 × 0.0333 = 8.3 mm of pattern, a 16.7 ms exposure smears 250 × 0.0167 = 4.2 mm, and an 8 ms exposure smears 250 × 0.008 = 2.0 mm. A millimetre-scale projected pattern stops correlating well before 8 mm of smear, so set the tolerance first and divide: a 2 mm smear budget at 250 mm/s gives 2 ÷ 250 = 0.008 s = 8 ms of exposure, which fits 60 FPS with room to spare.
Decide by case:
- Part moving, surfaces return well: keep exposure under the smear budget and hold 60 FPS. At 250 mm/s and a 2 mm budget that is 8 ms.
- Part static or slow, surfaces dark or far: use the longest exposure that fits the rate you need. 31 ms at 30 FPS collects twice the light of 15 ms at 60 FPS.
- Part moving and surfaces dark: no preset solves this. If the surface needs 33 ms of integration, the part moves 8 mm during it; at 15 ms you get holes instead. The fix is optical — more projector power, a shorter distance, a slower part — not a setting. Holes have other causes too: close-range depth dropout on fuzzy surfaces and thin parts covers the geometry ones.
When the exposure answer is wrong
- Minimum exposure still gives 33 ms spacing. The minimum is a firmware floor, not zero, and bright IR ambient raises it — IR depth image noise in bright light: the autoexposure floor shows where that floor comes from. If the floor already fits the period, move to the host checks.
- The rate differs between your code and the camera's viewer. Run the same stream in the vendor's viewer; capture time slower in the API than in the viewer separates camera limits from pipeline limits.
- You compensated by averaging frames. Averaging recovers noise performance, never frame rate — long exposure on a fixed frame rate camera: averaging multiple frames shows the arithmetic.
- Auto exposure quietly lengthens integration. Cap the maximum exposure at the table value, the way capping the maximum auto exposure time to stop motion blur describes for area-scan cameras.
Checklist before you blame the camera
- Print depth timestamps for 100 frames and divide 1000 by the median spacing.
- Convert the spacing to a rate and compare against the supported list — commonly 60, 30, 25 and 15 FPS.
- Read the current depth exposure and every preset that maps to it, and check it against the table.
- Compute smear at your part speed: speed × exposure.
- Disable every stream except depth and re-measure.
- Confirm the rate in the camera's own viewer application.
- Put the camera on its own USB controller; on an RT kernel, test once on the stock build.
Hardware that holds the rate, and the next step
With the exposure budget fixed by physics, the cameras that hold 60 FPS on dark or distant parts are the ones with more return signal: a higher-power projector inside its safety class, larger pixels, or a shorter working distance. In 3D sensors, filter by type — a projected structured-light camera such as the MV-DPS200P-02 gives you this pattern-versus-exposure trade-off directly, and laser profilers meet the same integration ceiling per profile. Depth streams that ride on IR sensors share the frame-period arithmetic with the infrared cameras range.
- Type
- Stereo / structured light
Take your working distance, part speed and surface reflectance into the camera finder, or start from the configurator with those three numbers:
The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.
Bring a timestamp log to our contact page and we will read it with you. More depth-camera set-up articles are collected under the 3D machine vision topic.
Questions
What timestamp spacing corresponds to each frame rate?
Divide 1000 by the spacing in milliseconds: 16-17 ms is 60 FPS, 20 ms is 50 FPS, 33-34 ms is 30 FPS, 40 ms is 25 FPS, 67-70 ms is 15 FPS. A spacing that matches no supported rate, or that drifts frame to frame, points at the host side - USB bandwidth, kernel scheduling or your processing loop - rather than the exposure ceiling, which always lands exactly on a supported rate.
Why do holes appear in the depth image when I shorten the exposure?
Each pixel needs a minimum amount of returned infrared light before the pattern correlation or timing measurement passes. Halving the exposure halves the photons, so dark, distant and steeply angled surfaces drop below the threshold first and become holes. Buy the light back with more projector power inside its safety class, a shorter working distance, or a lower frame rate whose longer exposure doubles the collection.
Can I force 60 FPS with a long exposure by disabling the limit?
No. Integration cannot overlap the next frame's readout, so a 16.7 ms period physically cannot hold more than about 15 ms of exposure, and no SDK flag changes that. The real choices are a shorter exposure, a higher-power projector inside its safety class, gain (which amplifies noise equally), or accepting 30 FPS, where 31 ms of integration fits.
Is a low frame rate always caused by exposure?
No. If minimum exposure still gives 33 ms spacing, work down the host checks: disable every stream except depth, move the camera to its own USB controller, boot a non-real-time kernel once, and print timestamps before your processing runs. A camera that must be unplugged and replugged after a session crashed almost always has a host-side cause, not an exposure one.
Does a higher resolution or accuracy preset lower the frame rate too?
It can, through two paths. A larger frame takes longer to read out, which shortens the margin left for exposure at the same rate, and high-accuracy presets often lengthen integration internally. If timestamps hold the same spacing after you cut exposure to minimum, drop the resolution one step and compare: the readout path, not the exposure, is the cap.

