System designHow-to

Capture a camera image via HTTP CGI from your PC

Capture a camera image via HTTP CGI: call the capture endpoint, then fetch the JPEG. Curl and Python examples, timing math and fixes for empty or stale frames.

LumaSpec Vision10 min read Share on LinkedIn

On this page 10 sections
  1. 01Why the image URL alone shows nothing
  2. 02What you need
  3. 03Capture, then fetch: the sequence
  4. 04The same two calls from a browser, curl or Python
    1. In a browser
    2. With curl
    3. In Python
  5. 05Reading the two responses
  6. 06How long one snapshot takes
  7. 07Common mistakes and fixes
  8. 08When the camera does not speak HTTP
  9. 09Before you script it: the short checklist
  10. 10Questions

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:

Smart cameras & vision sensorsHikrobot MV-SC3050XC-16M-ATN-H 5 MP color vision sensorMV-SC3050XC-16M-ATN-H
Interface
Fast Ethernet
Resolution
5 MP
$1,809.00View part
Smart cameras & vision sensorsHikrobot MV-SCB007EC-12S-WBN SCB vision sensor, 1.6 MP, Color, 12 mm lens, Manual focusMV-SCB007EC-12S-WBN
Interface
Fast Ethernet
Resolution
Up to 3 MP
$1,989.00 · quotedView and quote
Smart cameras & vision sensorsHikrobot HK-TD032-12-WBN 1.6 MP mono vision sensorHK-TD032-12-WBN
Interface
Fast Ethernet
Resolution
Up to 3 MP
$1,699.00View part

All three expose Fast Ethernet and an M12 lens mount, with resolution from 1.6 to 5 MP.

Capture, then fetch: the sequence

  1. 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.
  2. 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.
  3. 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.
  4. Fetch the JPEG. Request http://192.0.2.50:4242/current.jpg?annotations=off. Pick the value you actually want — on for the overlay with results, off for the clean image — and type it literally. Never paste the <on|off> placeholder from the reference into the address.
  5. Save and sanity-check. curl -o snap.jpg writes the bytes to disk; file snap.jpg should report JPEG image data at roughly 100-300 KB for a megapixel sensor.
  6. 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.
  7. 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.jpg it 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.

raw bytes = pixels × bytes per pixel (mono 8 bit: 1 byte) jpeg bytes ≈ raw bytes / 10 (5:1-20:1 typical; plan at 10:1) seconds = jpeg bytes / (link Mbit/s × 125000)

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.

Smart cameras & vision sensors1.6 MP smart camera, 60 fps, 16 mm lens, white light (polarized, transparent light) (LS-SMC-0063)LS-SMC-0063
Interface
GigE
Resolution
Up to 3 MP
$1,110.00View part

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=off or ?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.jpg without 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=off in 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.

Build the whole station

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.

Start with the camera

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.