You can see the live image in the camera's own app, but the same picture refuses to appear on your PC: the image URL times out, returns an error, or shows the frame from an hour ago. The cause is rarely the network. Most networked vision cameras split the job across two addresses: one request triggers a new capture, a second request returns the image. This guide gives the exact two-call sequence, curl and Python code for the bench, the arithmetic that sets your snapshot rate, and the fixes for empty or stale frames.
Why the image URL alone shows nothing
A camera with a web interface keeps two jobs on two addresses. The capture endpoint tells the sensor to expose and read out one new frame. The image endpoint hands over the frame the camera currently holds. One integrated vision camera documents the pair as /captureimage and /current.jpg on port 4242; many other devices follow the same shape with their own paths.
Call the capture address and the answer is not a picture. It is a few tens of bytes of JSON, {"status":"succeeded"}, saying that a new frame now exists. Call the image address, /current.jpg?annotations=off, and the camera returns that frame as JPEG bytes. Parameter names vary a little by firmware: this pair uses annotations, another line uses type=color. The meaning is the same either way — annotations=on burns the inspection overlay into the picture, off returns the clean image.
So a browser pointed straight at the image URL asks for a frame that may not exist. Right after power-up there is nothing to send, and the request fails. Once any capture has run — the camera's own program, its app or one of your earlier calls — the same URL returns that frame, however old it is. The fix is not network troubleshooting. It is calling capture first, every time.
What you need
- The camera's IP address and the port its web service listens on, reachable from the PC that will run the loop. A passing ping is the quick check.
- The endpoint paths and port for your model, from the camera maker's interface reference. Values from a different model or firmware line are the most common reason a request that should work returns an error page.
- A client: a browser for a one-off, curl for the bench, any language with an HTTP library for a production loop.
- Login credentials, if the interface asks for them.
The pattern fits any camera that serves its own web page on the network — in practice the vision sensors and smart cameras in the smart cameras & vision sensors category, which run the inspection on the device and answer over Ethernet. Three current examples of the class:
- Interface
- Fast Ethernet
- Resolution
- 5 MP
- Interface
- Fast Ethernet
- Resolution
- Up to 3 MP
- Interface
- Fast Ethernet
- Resolution
- Up to 3 MP
All three expose Fast Ethernet and an M12 lens mount, with resolution from 1.6 to 5 MP.
Capture, then fetch: the sequence
- Check reachability. Ping the camera from the PC that will pull the images. If the camera's own controller or hand-held sees it and your PC does not, you are on a different subnet or VLAN; fix routing first, because no URL crosses a missing route.
-
Trigger the capture. Request
http://192.0.2.50:4242/captureimage— an example address and port, so substitute your camera's. The body that comes back is status text, not an image. -
Read the status before fetching.
{"status":"succeeded"}means a fresh frame exists in the camera. Anything else means there is nothing new to download yet, so fix the capture step, not the fetch. -
Fetch the JPEG. Request
http://192.0.2.50:4242/current.jpg?annotations=off. Pick the value you actually want —onfor the overlay with results,offfor the clean image — and type it literally. Never paste the<on|off>placeholder from the reference into the address. -
Save and sanity-check.
curl -o snap.jpgwrites the bytes to disk;file snap.jpgshould report JPEG image data at roughly 100-300 KB for a megapixel sensor. - Repeat the pair for every frame. One capture buys exactly one new frame, so a polling loop is capture, read status, fetch, wait, repeat. If one event needs several frames, multi image capture per trigger: get an image set from one shot shows the trigger-then-set pattern.
- Prove freshness once. Move a target in the scene between two cycles and compare the two files by size or hash. Identical bytes mean you fetched a stale or cached image; the causes are in the mistake list below.
The same two calls from a browser, curl or Python
In a browser
Paste the capture URL, read the status line, then paste the image URL and the picture renders. Keep the port in both addresses: without it the browser knocks on port 80 and reports the camera as unreachable. Browsers also cache the image URL, so for a second look use a hard reload, or add a changing value as an extra parameter after one test confirms the camera tolerates one.
With curl
# trigger one new frame - the answer is a few tens of bytes of status, not the image
curl -s "http://192.0.2.50:4242/captureimage"
# {"status":"succeeded"}
# download the frame that capture just produced
curl -s -o snap.jpg "http://192.0.2.50:4242/current.jpg?annotations=off"
# prove the file is a JPEG and not a saved error page
file snap.jpg
# snap.jpg: JPEG image data, JFIF standard 1.01, resolution...
In Python
import time
import urllib.request
BASE = "http://192.0.2.50:4242" # str, example camera address and port
def capture_and_fetch() -> bytes:
# 1. trigger one new frame; the body is status text, not an image
status: bytes = urllib.request.urlopen(BASE + "/captureimage", timeout=5).read()
if b"succeeded" not in status:
raise RuntimeError(f"capture did not succeed: {status!r}")
# 2. download the frame the capture just produced
return urllib.request.urlopen(BASE + "/current.jpg?annotations=off", timeout=5).read()
a: bytes = capture_and_fetch() # JPEG bytes, roughly 100-300 KB at 1.6 MP
time.sleep(1.0)
b: bytes = capture_and_fetch()
assert a != b, "identical frames - the loop returned a stale or cached image"
The assert is the test: two captures a second apart, with something moving in the scene, must differ byte for byte.
Reading the two responses
The capture call answers with JSON text and the image call answers with JPEG bytes; mixing them up wastes the first hour on the bench.
- Capture answers with a few tens of bytes of JSON. Saved as
snap.jpgit will not open in any viewer, because it never was an image. - A fresh image answers with JPEG bytes starting
FF D8, roughly 100-300 KB for a 1.6 MP scene at typical quality. - HTML or a JSON error body from the image call means a wrong path or parameter: the camera is replying, just not with a picture.
- Byte-identical JPEGs across cycles mean the capture call is missing from the loop, or a cache sits in the way.
When a snapshot matters, its capture time matters too. The file time on your PC only says when you saved it, so use the camera's own stamp when the status or header carries one. On the Linux USB side, v4l2 buffer timestamp: know when a frame was really captured solves the same freshness question at the driver level.
How long one snapshot takes
Plan the rate from three numbers: frame size, link speed and sensor time.
Worked example, 1.6 MP mono on Fast Ethernet: 1,600,000 pixels × 1 byte = 1.6 MB raw. At the planned 10:1 compression the JPEG is about 160 KB. The link moves 100 Mbit/s ÷ 8 = 12.5 MB/s, so the file transfers in 160,000 ÷ 12,500,000 = 12.8 ms.
The sensor needs its own time. The LS-SMC-0063 is a 1.6 MP smart camera rated at 60 fps. At 60 fps one frame costs 1/60 s ≈ 16.7 ms of sensor time.
- Interface
- GigE
- Resolution
- Up to 3 MP
Add the example numbers: 16.7 ms capture + 12.8 ms transfer + about 5 ms for two HTTP round trips ≈ 35 ms per cycle, a ceiling near 28 snapshots per second. Script pauses and retries typically halve the sustained rate, so plan a polled loop at 5-10 per second and move to the streaming protocol or SDK past roughly 20. On Gigabit Ethernet the transfer falls to 160,000 ÷ 125,000,000 ≈ 1.3 ms and stops mattering; capture time dominates. If a higher-resolution camera instead makes PC-side capture sluggish, full resolution image capture is slow or stalls: size the data first sizes that pipeline the same way.
Common mistakes and fixes
-
The placeholder pasted literally.
?annotations=<on|off>asks for a mode no camera has. Write?annotations=offor?annotations=on. - The capture call skipped. The first fetch after power-up has no frame to return, and later fetches serve the same picture forever. Capture before every fetch.
- The port left out. A URL without a port knocks on port 80; when the web service listens on 4242, the browser reports that it cannot connect. Put the port in every address, in scripts too.
-
https assumed. Many device interfaces serve plain http only.
https://against an http-only port fails with a protocol or connection error. -
The browser cache trusted. The browser can re-serve the previous
/current.jpgwithout asking the camera. Scripts use curl; browser checks use a hard reload or a changing extra parameter. - The fetch pipelined before the status. A fetch fired while the capture is still running returns the previous frame. Read the status, then fetch.
- The network assumed flat. The camera may answer its controller while ignoring your PC across a subnet, VLAN or firewall. Ping from the loop PC first.
When the camera does not speak HTTP
A traditional area scan camera — a GigE Vision or USB3 Vision model — has no snapshot URL at all. It streams frames over its own protocol through a GenICam-based SDK, and a browser cannot pull a single pixel from it. That is a deliberate trade of convenience for sustained frame rate, not a fault. The two-call logic still maps: a software trigger plays the part of /captureimage, and the frame grab in your capture loop plays /current.jpg. Most reliable camera interface: GigE, USB3, Camera Link or CoaXPress compares those interfaces, camera parameter set and get in a capture loop safely shows the loop that replaces the polling script, and get the first image on an embedded vision prototype is the companion read for a first frame from a new area scan camera. To shortlist models by interface, resolution and shutter before you order, use the camera finder.
For live view rather than single snapshots, repeated capture-and-fetch carries a few images per second; past that, look for a real stream. Grab images from an IP camera over an HTTP snapshot URL and remote IP camera snapshot URL for a one-button live view cover the one-shot and live-view ends of that scale. More fixes from the same bench sit on the systems topic page.
Before you script it: the short checklist
- Reachability from the loop PC. The machine that runs the script needs the route and the passing ping, not just your laptop.
- Port and paths for your exact model. Paths differ between models and firmware lines; note them from the interface reference, per camera.
- A capture call in front of every fetch, status read. One habit removes both the empty-first-frame and the stale-frame failure modes.
-
Placeholders replaced with real values.
annotations=offin the file, never the literal<on|off>token from the reference. - One freshness proof recorded. Move a target, run two cycles, keep both hashes in the project notes; when the line argues about stale images later, the test already exists.
- A rate budget from the sizing math. File size against link speed with 2-3× margin, so the cycle time is a decision, not a surprise.
- A named fallback above the HTTP rate. The streaming protocol or SDK path, chosen before deployment, not after the first missed cycle.
If you are assembling the station rather than only scripting it, the configurator below starts at the camera step and carries lens, light and trigger choices through with it. For help pinning down a specific camera's endpoint list, the contact page reaches the applications team.
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.
Questions
What do I type for <on|off> in the snapshot URL?
The angle brackets are documentation placeholders, not part of the address. Replace the whole token with one value: annotations=off returns the clean image, annotations=on burns the inspection overlay into it. A URL that literally contains the placeholder asks for an annotation mode no camera has, and most firmware answers with an error page instead of a JPEG.
Can I call these HTTP endpoints from any language?
Yes. The interface is plain HTTP, so curl, a browser, Python, C# or any language with an HTTP client works with no SDK installed on the PC. You need the camera vendor's library only when you switch to a streaming protocol such as GigE Vision, or when you need continuous frames, very low latency, or camera settings the web interface does not expose.
How many snapshots per second can I pull over HTTP?
For a 1.6 MP mono camera the cycle ceiling works out near 25-30 per second on Fast Ethernet (16.7 ms capture plus 13 ms transfer plus overhead), and script latency usually halves it. Plan a polled loop at 5-10 per second and watch the status replies for queueing. Past roughly 20 per second, or for continuous video, switch to the streaming protocol or SDK.
Does the capture call work while the camera's own inspection program runs?
On the camera behind these examples, yes: the capture request answered with a succeeded status while the inspection program was running, and the next fetch returned that fresh frame. Other devices may queue the request behind the running job or refuse it while busy. One test settles it on any model: trigger, fetch, then compare against the previous file by size or hash.


