Put a camera on a press, a bench or a packing line and someone downstream wants the picture now, on a screen across the room. Wire the obvious grab-and-send loop and you get the classic three failures: the remote display blinks, the image tears, or the whole thing stammers along at a couple of frames per second. All three trace back to two mistakes — sending a buffer the grab loop still owns, and pacing the loop with fixed waits instead of the camera's frame period. This guide builds the pair of programs that fix both: copy each complete frame, wrap the byte stream in a length header, send on frame arrival, and size the stream to the link. The worked numbers use a 30 fps, 12 MB-per-frame camera, so every figure scales to your sensor.
What the symptoms tell you
Diagnose before you edit code. The three symptoms have three distinct causes:
| Symptom | What you see on the far screen | Root cause |
|---|---|---|
| Blink | The image flashes or falls back to an empty panel between updates | The receiver times out between frames and repaints nothing |
| Tear | Horizontal shear: the top of one frame above the bottom of the next | The sender transmits while the acquisition library rewrites the same buffer |
| Stutter | A steady slideshow, or smooth-for-a-moment-then-jump | A fixed wait, typically 500 ms, throttles the loop regardless of the camera |
Blink and stutter are pacing bugs. Tearing is an ownership bug. The fixes are independent, so apply all three: one copy, one header, one clock.
What you need
A sender PC with the camera and its SDK, a receiver PC on the same switched network, and Python with the socket, struct and numpy modules on both ends — the logic translates directly to your SDK's own client libraries. Any GigE or USB3 area scan camera works; what drives the numbers is pixels per frame, bytes per pixel and frame rate, so note those three before you start. The camera finder filters by all three, and the configurator's camera step specs the rest of the chain.
For tuning the stream, a bench camera with a known load saves guesswork — this USB3 model delivers 4000 × 3000 at 30 fps:
- Sensor format
- 1/1.7"
- Interface
- USB3
- Resolution
- 12 MP
- Shutter
- Rolling
Keep the sender PC near the camera: passive USB3 cable runs top out around 3 m, and the USB-C cable timeouts on a USB3 camera guide explains what a marginal cable does to a stream. Past that distance, run the camera over GigE — Step 4 covers the arithmetic.
Step 1: Copy each complete frame before you send it
The acquisition library delivers every frame into a buffer it owns and reuses. Send that buffer and the socket write races the next exposure: the receiver stitches the top of an old frame to the bottom of a new one. So the first rule is ownership. In the grab callback, or right after a blocking read, deep-copy the pixels into memory you own and hand the copy to the sender — never queue the original. This is the same copy-before-use rule that stops torn frames in a camera callback, and it costs about a millisecond for a 12 MB frame on a modern PC.
Drop incomplete frames. A short or flagged frame from a camera reconfiguration or a cable glitch is worse than a late one — discard it and grab a fresh one. And release whatever per-frame object your SDK hands back once the copy exists; an unreleased per-frame reference is a leak that surfaces as sluggish swaps after hours on the line.
# Sender. grab() and frame.complete stand in for your SDK's per-frame read
# and its completeness flag; socket and struct are the Python standard library.
import socket, struct
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("192.168.1.60", 5000)) # receiver address and port you chose
while True:
frame = grab() # blocks until the next frame arrives
if frame is None or not frame.complete:
continue # drop short or flagged frames
payload = frame.pixels.copy() # a copy you own; tobytes() freezes it
sock.sendall(struct.pack("<I", payload.nbytes) + payload.tobytes())
Because the loop sends one frame before it grabs the next, frames leave the socket strictly in order and never overlap. The hardware trigger grab loop in Python guide shows the acquisition half of this pattern in full.
Step 2: Frame the byte stream with a length header
TCP is a byte stream, not a message queue: one recv() can return half a frame, two frames or four bytes. The receiver must learn where each frame ends, so prefix every frame with its length as a fixed 4-byte unsigned integer, then read exactly that many bytes.
# Receiver helper: return only when exactly n bytes have arrived.
def recv_exact(sock, n):
buf = bytearray()
while len(buf) < n:
chunk = sock.recv(min(65536, n - len(buf)))
if not chunk:
raise ConnectionError("sender closed the stream")
buf.extend(chunk)
return bytes(buf)
(length,) = struct.unpack("<I", recv_exact(sock, 4))
payload = recv_exact(sock, length)
Keep the header even when every frame is the same size — fixed ROI, mono 8-bit. The day the ROI changes or you move to 12-bit, a self-describing stream keeps working. The send images over a TCP socket in Python guide frames single images the same way; live streaming adds only the pacing and sizing rules below.
Step 3: Pace to the frame period, not to a wait
The camera sets the tempo. At 30 fps a frame lands every 1000 / 30 = 33 ms; at 60 fps every 1000 / 60 = 16.7 ms. Build the timing from that number:
- Send on arrival. Block on the grab, as Step 1 does — frame arrival is your clock, your flow control and your pacing in one, with no timer to drift.
- If your SDK forces a polling loop, sleep until the next frame boundary and never stack a fixed wait on top. A 500 ms wait on a 30 fps camera turns the stream into a 1000 / 500 = 2 fps slideshow — the slow-display failure in its purest form.
- Set the receiver's socket timeout above one frame period — three to four periods, about 100-150 ms at 30 fps — or leave it blocking. A timeout at or below 33 ms fires between frames, and the empty read is what gets repainted as a blank panel: the blink.
- If the link cannot carry the full rate (Step 4), drop frames at the sender: overwrite your staging copy and send the newest, never queue.
Fix the rate upstream too. Timed acquisition — the camera's fixed-frame-rate mode — gives the stream a steady period instead of a rate that wanders with exposure; the timed acquisition guide sets frame rate and bit depth deliberately.
Step 4: Size the stream to the link
TCP accepts bytes faster than any network can drain them, and the send buffer swells until latency climbs by seconds. Compute the load before you deploy:
Gigabit moves 1000 Mbit/s of line rate, which is 125 MB/s; the 80% target leaves headroom for Ethernet and TCP overhead plus anything else sharing the switch. Worked example — a 12 MP sensor sent as mono 8-bit:
- Bytes per frame: 4000 × 3000 pixels × 1 B = 12,000,000 B = 12 MB.
- Full frame at 30 fps: 12 MB × 30 = 360 MB/s = 2880 Mbit/s. That is 288% of the budget. TCP will take it, buffer it, and the far screen will run seconds behind the press.
- Crop to 2000 × 1500: 3 MB × 30 = 90 MB/s = 720 Mbit/s, 72% of budget. Smooth and current.
- Keep the full frame and compress: industrial JPEG commonly lands between 5:1 and 20:1 on real scenes — encode on the sender, measure your own ratio, and check 360 ÷ ratio against the 100 MB/s usable budget before you trust it.
| Choice | Bytes per frame | Rate at 30 fps | Share of gigabit budget |
|---|---|---|---|
| 4000 × 3000 mono8, full | 12 MB | 360 MB/s | 288% — fails |
| 2000 × 1500 mono8 ROI | 3 MB | 90 MB/s | 72% — passes |
| 1000 × 1000 mono8 ROI | 1 MB | 30 MB/s | 24% — three streams fit |
| Full frame capped at 8 fps | 12 MB | 96 MB/s | 77% — visible stutter |
Decision order: crop or bin first, compress second, cut the frame rate last. A rate cut shows as jumped motion, and a link near saturation adds latency even when the average fits.
For distance, move the camera to GigE: copper gigabit reaches 100 m on a proper camera cable, and the 5GBASE-T bandwidth and distance guide covers the faster multi-gigabit options. A 15 m RJ45 camera data cable with a locking stud holds the plug through vibration:
- Type
- Camera data cables
- Length
- 15 m
And when the screen hangs across the floor, the GigE version of the same 12 MP IMX226 sensor streams the full frame at 9 fps:
- Sensor format
- 1/1.7"
- Lens mount
- C-mount
- Interface
- GigE
- Resolution
- 12 MP
That spec is the pipe speaking: 12 MB per frame × 9 fps = 108 MB/s = 864 Mbit/s, which is 86% of the gigabit line. A full 12 MP frame fits copper gigabit only at single-digit frame rates; anything faster needs a crop, a codec or a faster link.
Step 5: Draw once per frame on the receiver
The display thread has one job: take the newest complete frame, convert once, draw once. Two rules keep the picture steady.
- Never clear the panel on a timeout. If no frame arrives inside the timeout, keep showing the last good image. Nearly every blink is an empty read repainted as background; a steady last frame plus a logged stall is the correct behavior.
- Keep the mailbox one frame deep. Display and processing run at different rates, and when processing lags you drop the older frame instead of queueing it — the acquisition faster than processing guide works through the same rate mismatch.
Serving several screens or processes changes the plumbing, not the rules: the server holds one staging copy per frame and fans it out to each connected client, and a slow or dead client is dropped without touching the stream.
When several consumers and several cameras share one box, run the server on a machine built for it. A vision controller with 5+ LAN ports takes a camera or a client per port, PoE camera ports power GigE cameras over the same cable, and this one adds 8 built-in light channels and 32 GB of memory:
- LAN ports
- 5+ LAN ports
- USB 3 ports
- 3-4 USB 3 ports
- PoE
- PoE camera ports
- Light outputs
- 8 built-in channels
Verify the stream end to end
Five checks, about fifteen minutes:
- Delivered rate: count complete frames at the receiver for 60 s and divide by 60. Around 1800 frames means the 33 ms pacing held; around 120 means a 500 ms wait is still hiding somewhere.
- Sequence integrity: with the 4-byte sequence number from Step 3, a gap counts your dropped frames, and a stall longer than 10 periods — about 330 ms at 30 fps — means the stream is dead.
- Motion test: point at a moving belt or spin a textured part. Any shear means a buffer went out mid-overwrite — back to Step 1.
- Kill test: stop the sender. The receiver must hold the last frame, log the stall and start a reconnect with backoff — never crash, never clear.
- Latency: wave a hand in front of the lens and watch the echo on the far screen. On a link inside budget it should sit well under 100 ms; a delay that grows over minutes means saturation, and the sender must drop frames per Step 3.
Common mistakes
- Sending the SDK's frame object instead of a copy — tearing on every motion.
- Two timers stacked: a wait in the send loop plus a short socket timeout on the receiver — blink and stutter together.
- Queueing frames on a slow link — latency grows without bound; drop at the sender instead.
- Dropping the length header because frames are "all the same size" — the stream misaligns the day the ROI changes.
- Converting image to array and back on both ends — wasted CPU and extra copies; send the bytes or the encoded image you already hold.
- Leaking per-frame references — memory creeps up over a shift until swapping slows everything.
- Tuning over Wi-Fi — jitter destroys the 33 ms budget; measure on the switched LAN you will run.
To spec the whole chain — camera, cable, controller — start at the configurator's camera step or reach us through the contact page.
Questions
Can one gigabit link carry several camera streams at once?
Divide, don't guess. The usable budget is about 100 MB/s — 80% of gigabit's 125 MB/s line rate. A 1000 × 1000 mono 8-bit stream at 30 fps costs 30 MB/s, so three such cameras take 90 MB/s and fit with margin; a fourth reaches 120 MB/s and both stutter. Color doubles the per-frame bytes and 12-bit doubles them again, so recompute before adding a camera.
How do I tell a dead stream from a still scene on the far screen?
Use the 4-byte sequence number in each frame as a heartbeat. A live sender increments it even when the scene is frozen, so if the counter stalls for more than about 10 frame periods — 330 ms at 30 fps — the stream is dead: grey out the display, log the stall and reconnect with backoff. Stillness and death stay distinguishable.
TCP or UDP for a machine-vision video stream?
On a dedicated switched LAN, TCP: it guarantees every byte, and the sender controls rate by dropping frames. UDP avoids head-of-line blocking on shared networks and suits multi-gigabit camera backbones, but you own ordering, loss handling and framing. Start with TCP plus a sender-side frame budget; move to UDP only when measured latency or loss on your network demands it.
How do I know frames are tearing, not just dropping?
Check the sequence number against the image. A counter that skips means frames were dropped on the way — expected when the sender throttles to the link. A counter that is correct but the image looks sheared or mixed means the sender transmitted a buffer the grab loop overwrote mid-copy; fix ownership at the sender, not the network. A spinning textured part makes shear obvious.
How many frames should the receiver buffer?
One. Hold the newest complete frame in a mailbox and let the display thread take it; drop anything older. Every queued frame adds one frame period of latency, so five frames waiting at 30 fps is 5 × 33 = 167 ms of lag behind the line — and the backlog only grows while processing trails acquisition.



