You have a working detection system: YOLOv5 finds the objects, OpenCV handles the image work, and your own tracker keeps identities frame to frame — all on a host CPU, with inference eating most of the frame budget. The fix is not a rewrite. Cut the pipeline at one call: send the frame to an NPU accelerator, read back its raw output tensor, and keep everything else — decoding, tracking, drawing, signaling — running exactly as it does today. This guide shows where to cut, how to pull frames out of a pipeline callback, what changes in your post-processing, and a frame budget you can check against your own camera before ordering anything.
Where to cut: one call, five stages kept
Your loop has six stages:
- capture a frame from the area scan camera
- letterbox and normalize it to the network input size
- run inference
- decode: threshold the scores, then non-maximum suppression (NMS)
- associate boxes into persistent tracks
- draw overlays, log results, signal the PLC
The accelerator takes stage 3, and some runtimes fold stage 2 in as well. Stages 4 through 6 stay on the host, because the accelerator returns a raw tensor, not finished detections. The standard YOLOv5 export emits a tensor of shape [batch_size, number_of_anchors, 4 + 1 + number_of_classes]: four box values, one objectness score and one class score per row. That is exactly what your existing threshold-plus-NMS code consumes. Swap the call inside stage 3, wire its output into stage 4, and the rest of the loop does not change.
At the default 640 × 640 input, the detection head works on grids of 8, 16 and 32 pixel strides with three anchors each: (80 × 80 + 40 × 40 + 20 × 20) × 3 = 25,200 prediction rows. A single-class barcode model therefore returns 25,200 × 6 values, about 0.6 MB in float32. The tensor is small; the risk is a per-row Python decode loop, so keep thresholding and NMS vectorized in NumPy.
What actually changes in your post-processing
Three things move when inference leaves the CPU.
Coordinates come back in network space. Boxes are expressed in the letterboxed input frame, 640 × 640 pixels here. Convert each box back to full-image pixels with the letterbox scale and padding you logged at pre-processing time — store scale and pad per frame, not globally, or a zooming camera view will drift.
Scores shift slightly. The accelerator's compiler quantizes weights and activations, typically to 8-bit integers. Boxes land within a pixel or two of the float model; scores move more. Keep your pipeline, re-tune the thresholds. The same drift appears whenever a model crosses runtimes — the OpenCV ONNX preprocessing mismatch fix walks through one real case.
Layout is the compiler's decision. Read the converter report for the input layout it expects (NCHW or NHWC) and the output layout it returns, and build the letterbox and decode around exactly that. A fixed input size is normal: your letterbox must emit that size for every frame.
Getting a frame into OpenCV inside a pipeline callback
Framework-driven pipelines invert control: you no longer call the camera in your loop; the pipeline calls you. A callback shaped like app_callback(pad, info, user_data) is typical — the pipeline hands in event objects, and the frame travels attached to one of them, not as a NumPy array. Two habits make this safe:
- Unwrap the buffer yourself. Pull the buffer's pixel data into a NumPy array. Wrapping the buffer without a copy keeps the overhead near zero; the cv::Mat zero-copy pattern works for any buffer you can address as rows × columns × bytes.
- Put your state in user_data. That parameter is your channel: attach the tracker, the frame queue, configuration and a logger at set-up, and the callback reaches them on every call.
Then leave fast. Do the minimum in the callback — unwrap, queue, return — and let a worker thread run tracking and drawing. A callback that blocks stalls the pipeline, and the damage shows up downstream as a frame that never arrives within its timeout or a streaming callback that stops being called. If the host CPU sits near 100 % while frames still queue, replace the spin loop with an event-driven wait — the busy-waiting fix shows the pattern.
Worked example: frame budget for a 4000 × 3000 camera at 30 fps
Take a concrete capture side: the LS-CMR-0166, a 12 MP USB3 camera documented at 4000 × 3000 resolution and 30 fps, with a 1/1.7" rolling-shutter IMX226 sensor.
- Sensor format
- 1/1.7"
- Interface
- USB3
- Resolution
- 12 MP
- Shutter
- Rolling
Your application adds three inputs of its own: 8-bit mono pixels out of the camera (1 byte per pixel — a natural choice for barcode work), a 640 × 640 three-channel network input (the export setting from the first section), and a usable USB3 throughput of 400-500 MB/s out of the link's 5 Gbit/s. That last number is the one estimate in this example; it is the practical range for host controllers.
The rules, in words: the frame budget is the wall-clock time one frame may consume, and the link load is frame bytes times frame rate.
Budget: 1000 / 30 fps = 33.3 ms per frame. Capture, inference, decode, tracking and signaling must all fit inside 33.3 ms, or the camera produces frames faster than you consume them.
Mono stream: 4000 × 3000 px × 1 B = 12,000,000 B = 12 MB per frame, and 12 MB × 30 fps = 360 MB/s = 2.88 Gbit/s — inside the 5 Gbit/s link with headroom. At a practical 500 MB/s the link carries 500 / 12 ≈ 41 fps of this stream, so the link, not the accelerator, is the ceiling.
Full 24-bit color at the same rate: 4000 × 3000 px × 3 B = 36 MB per frame, and 36 MB × 30 fps = 1080 MB/s = 8.6 Gbit/s — above the link's raw 5 Gbit/s. A full-rate 12 MP color stream cannot cross USB3 at all, whatever inference hardware sits behind it.
Network input: 640 × 640 px × 3 B = 1,228,800 B ≈ 1.2 MB — about a tenth of one mono frame and 29× smaller than the color frame.
Decision: request 8-bit mono at 30 fps, letterbox on the host down to the 1.2 MB network input, and hold 30 fps only while inference plus decode plus tracking stays under 33.3 ms. If the sum exceeds the budget, cut input size or frame rate — not the tracking that keeps your identities stable.
What still runs on the host, and which host
The host half is real work: camera control, letterbox, decode, tracking, overlay rendering and the I/O that tells a PLC or robot what happened. The accelerator module rides in that host, so pick the host by stream count and decode load, then add the accelerator to it.
All three controllers below offer at least three camera-side LAN ports and four or more built-in light channels, so capture, lighting and the host loop live in one box.
| Specification | MV-VC2140-128G40-1T | MV-VC3301H-128G66 | MV-VC5525-256G70-NN |
|---|---|---|---|
| CPU | Intel® Processor N97 | Intel® Core i3-8100 | Intel® Core i5-12400 |
| Memory | 8 GB | 8 GB | 32 GB |
| Storage | 128 GB SSD + 1 TB HDD | 128G SSD | 256 GB SSD |
| GPU | Intel® UHD Graphics | Intel® HD Graphics 630 | Intel® UHD Graphics 730 |
| Operating system | Unactivated 64-bit Windows 10 LTSC 21H2 | Windows 10 64-bit, Not activated | Unactivated Windows 10 |
| Encryption method | Not support | VM6200Pro license key | – |
| Video output | HDMI interface × 1, VGA interface × 1; Supports independent display outputs at the same time; Max. resolution 1920 × 1080 @ 60 Hz | HDMI interface × 1, VGA interface × 1; Supports independent display output at the same time; Max. resolution 1920 × 1080 @ 60 Hz | HDMI interface × 1, VGA interface × 1; Supports independent display output at the same time; Max. resolution 1920 × 1080 @ 60 Hz |
| Digital I/O | Opto-isolated input × 8; Opto-isolated output × 8 | 8 digital isolated inputs supported; 16 opto-isolated outputs supported (NPN/PNP switchable) | Opto-isolated input × 8; Opto-isolated output × 12 ( supports NPN/PNP switching), digital-isolated low-side driver output × 8; Non-isolated bi-directional differential I/O × 4 |
| Light interface | Light interface with 24 VDC constant voltage × 4; (Brightness is adjustable. Max. current 3 A for single light interface, and total max. power consumption 80 W. Supports light mode switching between strobe and solid via external control) | 4-channel 24 V constant-voltage light drive, Max. power per channel: 72 W, Max. power (4 channels): 150 W | 24 VDC constant voltage × 8; (Max. power consumption of single channel is 72 W, total max. power consumption 190 W) |
| Network interface | GigE interface × 4 | 6× Gigabit Ethernet ports | GigE interface × 7, including interface for PoE × 4 |
| USB Interface | USB 2.0 × 2; USB 3.0 × 2 | USB 2.0 × 4; USB 3.0 × 4; Built-in USB 2.0 × 1 | USB 2.0 × 4, USB 3.0 × 4, built-in USB 2.0 × 1 |
| Serial Port | RS-232 × 1, supports switching to RS-485 or RS-422 | 2× full-function RS-232, RS-485/RS-422 switchable | RS-232 × 2 by default, supports switching to RS-485 and RS-422 |
| Price | $1,679.00 | $2,419.00 | $4,479.00 · quoted |
All 3 share: Certification CE, KC. Values as printed in each product's data sheet; open a part for its drawing and full specifications.
Decision by case. One or two cameras and a lean model: the Intel N-series unit with 8 GB of memory is enough host. A GigE camera fleet on one box: the Core i5 unit — its PoE camera ports power the cameras over the same cable and 32 GB of memory absorbs multi-stream decode queues. If you want a vision runtime preinstalled and a PCIe slot for the accelerator card, the Core i3 unit ships with a VM6200Pro license and PCIe expansion, without PoE. If the whole task collapses to detect-and-decide at the sensor, compare smart cameras: the inspection runs on the device with no host PC. The vision controllers range spans both extremes.
Setting up the accelerator's runtime on that box is a one-time job; a step-by-step NPU runtime walkthrough, from install to a working pipeline, covers the sequence on a small inference host.
Migration checks, in order
Run these before the production line sees the change:
- Compile and read the report. Note the fixed input size, the expected layout (NCHW or NHWC) and the quantization; make your letterbox emit exactly that size.
- Parity test on recorded frames. Push 200 saved frames through the float model and the accelerator; compare box overlap and scores. Expect near-identical boxes with slightly shifted scores.
- Re-tune thresholds from step 2's score distributions before any pass/fail logic goes live.
- Time stages with wall clock, not CPU time — the deadline is per frame, and the wall-clock versus CPU-time distinction decides whether you meet it.
- Soak at full rate. Run 30 minutes at the target frame rate and watch for dropped frames and queue growth; if acquisition outruns processing, the frame-rate gap fix lists the levers.
- Check tracker cadence. Feed detections at the rhythm your tracker was tuned for; faster inference only helps if the tracker consumes boxes at the rate it expects.
When an NPU is not the answer
Commit to the accelerator only when the numbers say so. At a few frames per second, a modern CPU host may already meet its budget with room to spare — CPU inference for inspection covers that path, and the CPU-or-GPU setup check gives the sizing questions. If several streams must run at full rate, a GPU-class host scales past what an edge NPU delivers. And if profiling shows the bottleneck is capture, pre-processing or drawing rather than inference, no accelerator helps — measure per stage first.
Next step: size it with your own numbers
Work the budget with your camera's figures — frame rate, pixels, bytes per pixel — then match hardware: the camera finder filters capture sides by resolution and interface, and the configurator assembles camera, host and lighting as one system. If the split between host and accelerator is unclear, contact us with your frame rate and network input size, and we will work the budget with you. The systems topic page collects the surrounding guides on buffers, callbacks and pipeline timing.
Questions
Do I need to retrain my YOLOv5 model to run on an NPU?
No. Export the trained weights and compile them with the accelerator vendor's converter, which quantizes the network — typically to 8-bit integers, sometimes using a small calibration image set. Accuracy usually survives the trip; what changes is the score distribution, so re-run validation frames and re-tune confidence thresholds. If the converter reports a layer it cannot map, adjust export options or swap that one layer rather than rebuilding the model.
Why do detections land in the wrong place after the move?
Coordinate-space handling, almost every time. The accelerator returns boxes in the letterboxed input space; multiply by the pre-processing scale and remove the padding before comparing with full-image pixels, and store scale and pad per frame rather than globally. The second suspect is layout: reading an NCHW tensor as NHWC scrambles values into plausible-looking nonsense. Both errors fail a parity test on recorded frames immediately.
Can my custom tracker keep running unchanged?
Yes, if it consumes boxes, scores and timestamps. Keep two things healthy: feed detections at the cadence the tracker was tuned for — a faster inference loop does not help logic that assumed a slower rhythm — and confirm assignment thresholds still hold, because quantized models place box edges within a pixel or two of the float model. Validate against recorded sequences before the line sees the change.
How do I get pixel data into NumPy from a pipeline callback?
The callback receives pipeline objects, not arrays: in a signature like app_callback(pad, info, user_data), the buffer rides on one of the event parameters and user_data carries your own state. Unwrap the buffer into a NumPy array — zero-copy where the memory layout allows — push it to a queue, and return. Keep a worker thread for tracking and drawing.
When is a plain CPU host still the right choice?
When the budget already closes. Divide 1000 by your target frame rate; if measured inference plus decode plus tracking comes in well under that on your existing machine, acceleration buys nothing. Small models at a few frames per second are the classic case. An NPU earns its slot when the CPU misses the budget at the input size you need; a GPU-class host earns it when several streams must run at full rate.
