A Python application can process fewer images than a camera acquires, even when both report a plausible frame rate. Waiting for synchronized streams, aligning depth, converting arrays and printing progress can all change the measured loop speed. This troubleshooting workflow helps you separate those effects, establish a capture-only baseline and identify the stage responsible for a slowdown. The key is to measure acquisition, delivery and processing as different quantities.
Identify which frame rate you are measuring
Start by giving each measurement a precise name. A configured rate is a request, not evidence that the application receives or processes that many unique images.
| Measurement | What it describes | What it does not establish |
|---|---|---|
| Configured stream rate | The requested or negotiated stream profile | Sustained capture or delivery |
| Camera timestamp rate | Spacing between timestamped frame events, according to the documented timestamp definition | Host processing speed |
| Host delivery rate | Unique frames returned to your application over host elapsed time | Sensor acquisition rate when frames are lost or buffered |
| Processing throughput | Completed processing operations per elapsed time | Unique images acquired |
| Display refresh rate | How often the interface updates | Capture or processing throughput |
A loop counter measures whatever work the loop completes. If the loop waits for a paired depth-and-color bundle, it measures bundle delivery rather than either stream independently.
For the underlying timestamp arithmetic, use the frame-rate measurement guide. The procedure below adds a controlled Python test that separates measurement overhead from processing work.
Establish a controlled capture-only baseline
Change one variable at a time. Otherwise, a faster result may come from changing resolution, stream count or exposure rather than removing processing overhead.
- Record the active stream profiles after acquisition starts. Include dimensions, pixel format, requested rate and negotiated rate where available.
- Keep the scene and illumination steady. Allow startup exposure adjustments and stream initialization to settle before collecting measurements.
- Remove alignment, image conversion, inference, display, recording and per-frame console output.
- Collect each stream independently where the documented interface permits it. Record frame identifiers, camera timestamps and host arrival times.
- Sample for an illustrative interval of 10 seconds, then repeat the test 3 times. Extend the interval if the readings vary substantially.
Use the same exposure settings throughout the comparison. An exposure requirement that exceeds the frame period can constrain acquisition, depending on the camera's operating mode.
If the expected profile is unavailable, follow advertised stream-mode verification before interpreting performance measurements. For color-stream limitations, the pixel-format and exposure guide addresses those constraints separately.
Measure delivery with a monotonic host clock
Use time.perf_counter() for host elapsed intervals. It is a monotonic performance clock, unlike a wall clock that can change during synchronization or administrative adjustment.
The following helper is SDK-independent. Supply a read_one() adapter using the manufacturer's documented acquisition interface. It must wait for a frame from one chosen stream and return its identifier and camera timestamp, normalized to seconds. Return None for an unavailable timestamp; do not substitute a host timestamp.
from time import perf_counter
def sample_delivery(read_one, seconds=10.0):
host_times = []
device_times = []
frame_ids = []
started = perf_counter()
while perf_counter() - started < seconds:
frame_id, device_seconds = read_one()
host_times.append(perf_counter())
device_times.append(device_seconds)
frame_ids.append(frame_id)
return host_times, device_times, frame_ids
def interval_rate(timestamps):
if len(timestamps) < 2:
return None
if any(t is None for t in timestamps):
return None
intervals = [
later - earlier
for earlier, later in zip(timestamps, timestamps[1:])
]
if any(interval <= 0 for interval in intervals):
return None
return (len(timestamps) - 1) / (
timestamps[-1] - timestamps[0]
)
The adapter should avoid processing image pixels. Record any acquisition timeout separately rather than silently treating it as a frame. Configure a documented finite wait timeout so a stalled stream cannot block the test indefinitely.
Check identifiers before accepting the calculated rate. Repeated frames can produce a high callback or loop rate without providing new image information. The helper deliberately collects evidence; it does not automatically remove duplicates or infer their cause.
Compute the host result from host_times. Compute the camera-timestamp result only after confirming the timestamp definition and checking the recorded identifiers. Print the summary after sampling, not inside the capture loop.
Validate timestamp and metadata meaning
A timestamp is useful only when its event, units and clock domain are known. It might represent exposure timing, another device event or host receipt. Read the manufacturer's definition rather than assuming every timestamp describes exposure start.
Here, N is the number of unique frame observations within one continuous timestamp sequence. There are N - 1 intervals between those observations. Using N with first-to-last elapsed time biases short samples upward.
Check these conditions before comparing results:
- Units: convert milliseconds, microseconds or clock ticks using the documented scale.
- Continuity: split the sample if timestamps reset, wrap or change domains.
- Uniqueness: identify repeated images using documented counters or identifiers where available.
- Gaps: interpret identifier jumps using the documented counter behavior, including wraparound.
- Stream identity: keep color and depth records separate unless you are deliberately measuring synchronized bundles.
A metadata field described as an actual frame rate may help, but its averaging window and meaning remain vendor-defined. It may describe sensor timing rather than unique frames reaching your application. Check that the field is supported on the current stream and frame before reading it.
Do not subtract a camera timestamp from a host clock reading unless the clocks have a documented common timebase or a measured mapping. For identifying which settings belong to each image, use the per-frame exposure and gain metadata checks.
Profile alignment and conversion separately
After establishing the baseline, restore one processing stage per run. Start with stream pairing, then alignment, array access, explicit copies, processing, display and storage.
Measure immediately before and after each operation with the same host clock. Store durations and summarize them after the run. Report the mean, median and a high percentile, such as the 95th percentile, because occasional stalls can overflow a queue even when average processing appears fast enough.
| Stage | What to time | Common interpretation trap |
|---|---|---|
| Frame wait | Blocking acquisition call | Waiting can mean the consumer is keeping up, not that acquisition is inefficient |
| Stream pairing | Synchronization or bundle assembly | A missing partner can delay an otherwise available stream |
| Alignment | Geometric registration | This is processing work, not a direct sensor-rate measurement |
| Array access | Buffer wrapping or conversion | A view may defer costs that appear in later operations |
| Explicit copy | Copy into application-owned memory | Ownership safety has a measurable memory-transfer cost |
| Processing | Inspection or inference | Asynchronous work may finish after the timed call returns |
| Output | Display, file writes and logging | Output stalls can dominate an otherwise fast pipeline |
Array conversion is not necessarily a full image copy. Verify the interface's buffer lifetime and ownership rules before retaining a view across subsequent acquisitions. The Python image-conversion checks cover validation before downstream processing.
For asynchronous accelerator work, use the processing framework's documented completion timing. A short submission time is not the same as a short execution time; follow the slow image-processing diagnostic workflow when that distinction matters.
Work through a capture-versus-processing example
Consider a hypothetical capture-only test with 601 unique observations spanning 10 seconds between the first and last camera timestamps:
Now restore alignment and conversion. Suppose the application receives 401 unique observations spanning 10 seconds:
A separate timing log shows the following illustrative mean costs in a serial pipeline:
| Operation | Mean duration |
|---|---|
| Alignment | 18 ms |
| Array access and required copying | 4 ms |
| Other application work | 3 ms |
| Total non-wait work | 25 ms |
The estimated serial processing capacity is 1000 / 25 = 40 frames/s. That agrees with the loaded delivery measurement and makes processing overhead a strong suspect.
This comparison establishes a processing-dependent delivery change. It does not yet prove that the sensor continued acquiring at the original rate during the loaded run. Check camera metadata, identifier gaps and queue behavior during that run before deciding where frames disappeared.
Match each symptom to a test and fix
Work from the earliest stage that differs from the capture-only baseline.
| Symptom | Next test | Appropriate response |
|---|---|---|
| Capture-only delivery is already low | Verify active profiles, exposure and transport diagnostics | Correct acquisition settings or transport limits before optimizing processing |
| Independent streams are fast, paired bundles are slow | Measure each stream without synchronization | Review pairing rules, timestamps and permitted synchronization tolerance |
| Alignment causes the slowdown | Disable alignment while keeping acquisition unchanged | Align only necessary frames or use a suitable processing path |
| Conversion causes the slowdown | Compare buffer access with explicit copying | Remove unnecessary copies without violating buffer ownership |
| Frame identifiers skip after processing is restored | Inspect queue policy and processing durations | Choose explicit latest-frame or lossless buffering behavior |
| Results become progressively older | Measure queue age with a valid timebase | Bound the queue and address the sustained processing deficit |
| Slowdown appears only with output enabled | Disable display, recording and logging individually | Reduce output frequency or move output away from acquisition |
A latest-frame policy can reduce latency by discarding intermediate frames. It does not increase acquisition rate or provide lossless recording. Conversely, a larger queue can absorb short bursts but cannot solve a sustained processing deficit.
When multiple workers are involved, preserve frame identifiers through the pipeline. The asynchronous result-to-frame matching guide explains why completion order alone is insufficient. If even the minimal capture test remains slow, continue with the camera frame-rate bottleneck workflow.
Check hardware fit without assuming SDK behavior
Do not replace a camera solely because a processing loop reports a low number. First establish whether the requirement concerns acquisition, processed output, latency or lossless storage.
For conventional image capture, compare area scan cameras using the required interface and stream specifications. The LS-CMR-0166 is a catalog example of a USB3 area scan camera, not a claim about achievable throughput in your application.
- Sensor format
- 1/1.7"
- Interface
- USB3
- Resolution
- 12 MP
- Shutter
- Rolling
For depth acquisition, compare 3D sensors by sensing method and documented output behavior. The MV-DT01WMU is identified in the catalog as a ToF sensing camera. Do not assume its metadata or acquisition interface matches another device.
- Type
- Depth / ToF camera
Use the camera finder to shortlist hardware after defining the measurement requirement. Confirm timestamp support, frame identifiers, queue behavior and supported stream combinations through manufacturer documentation before ordering.
Repeat the test and preserve the evidence
Keep a compact test record so another run can reproduce the result:
- Active stream profiles and exposure settings, captured after startup.
- SDK and firmware revisions obtained through authorized manufacturer channels.
- Warm-up duration, sample duration and unique observation count.
- Timestamp units, event definitions and clock domains.
- Duplicate identifiers, identifier gaps and acquisition timeouts.
- Capture-only and loaded delivery rates measured separately.
- Per-stage timing summaries and the selected queue policy.
Repeat the baseline after each proposed fix. Accept the change only when the required throughput and latency both improve without unacceptable frame loss.
Bring that record to application support when the cause remains unclear. If the evidence identifies a hardware requirement rather than processing overhead, start the camera configuration workflow with the required stream profiles and measured processing budget.
Questions
Is counting frames with a Python timer inaccurate?
An elapsed-time counter is valid for the work being counted. If each iteration aligns images, converts buffers and updates a display, it measures completed loop throughput. To measure delivery, remove those stages, count unique frames and use a monotonic host clock over a steady interval.
Should I use camera timestamps or host arrival times?
Use camera timestamps to examine documented device timing and a monotonic host clock to examine application delivery. They answer different questions. Confirm timestamp units, event definitions and continuity, and do not calculate latency by subtracting clocks that lack a documented common timebase.
Does actual frame-rate metadata prove that no frames were dropped?
No. The field may describe sensor timing or an average over a vendor-defined window. Transport losses, queue replacement or a slow consumer can still reduce unique frames delivered to the application. Compare metadata with frame identifiers and host delivery measurements.
Why does depth alignment reduce my measured Python frame rate?
Alignment transforms data between coordinate systems and consumes processing resources. A loop that aligns every frame includes that cost in its throughput measurement. Compare capture-only and alignment-enabled runs with unchanged stream settings, then time the alignment operation independently.
Will adding threads restore the requested frame rate?
Additional threads can separate acquisition from output or processing when the SDK and buffer ownership rules permit it. They cannot remove a sustained processing deficit automatically. Define bounded queues, retain frame identifiers and decide explicitly whether dropping intermediate frames is acceptable.

