You have a camera, a programming environment with no vision toolkit, and a part whose edge position you need as a number on the screen. A single bright-dark edge is one of the few vision tasks that genuinely needs no library: a grayscale array, a histogram, one threshold and a few dozen lines of plain code deliver a repeatable edge position. The catch is contrast, and contrast is a lighting question, not a software question.
This walkthrough gives the contrast number to hit, the threshold rule that follows from it, the pixel-to-millimeter arithmetic with a worked example, and the camera settings that quietly wreck the measurement when left on automatic. By the end you can tell whether your existing camera is enough, or whether the budget belongs in a light.
Can you detect an edge with no vision library at all?
Yes: if the dark and bright sides of your edge differ by about 20% in mean intensity, a midpoint threshold on the raw grayscale array finds the edge to a fraction of a pixel — no vision package, toolkit or licence involved.
An edge here is a step in intensity: dark pixels on one side, bright pixels on the other, and a transition a few pixels wide between them. Your code does three things — turn each frame into a 2-D array of gray values, pick one threshold, and record where every row crosses it. All three are plain loops over integers. What deserves care is everything around the array: how frames arrive over the cable and what the numbers mean is covered in digital camera image acquisition for machine vision, and the sampling floor under any edge measurement is treated in estimating edge position accuracy from image sampling.
How do the pixels get into your array?
A USB3 camera into an ordinary PC is the simplest path: the camera SDK, or any GenICam-compliant capture tool, hands you each frame as a 2-D integer array, 8 or 12 bits deep, and a USB3 cable spans the few metres a bench station needs.
Pick the interface by distance and camera count, not by features. USB3 keeps a bench station to a single cable. GigE suits longer runs and multi-camera racks; standard GigE reaches about 100 m on copper. A Power-over-Ethernet card such as the MV-GE2002P — a dual-port PoE NIC — carries power on the data cable, so the camera needs no separate supply. Camera Link and CoaXPress only earn their frame grabbers at high frame rates or with long line-scan sensors.
- Interface
- GigE
- Ports
- 2 ports
On the camera itself, start with a mono sensor and buy pixels: thresholding barely cares about anything else. Browse area scan cameras and filter by mount, interface and shutter in the camera finder. The shortlist below spans the three common starting points.
| Specification | LS-CMR-0166 | LS-CMR-0053 | MV-CS200-10UM |
|---|---|---|---|
| Sensor model | Sony IMX226 | IMX 226 | Sony® IMX183 |
| Resolution | 4000 × 3000 (12MP) | 4000 px x 3000 px | 5472 × 3648 |
| Max. frame rate | 30 fps | 9 fps | 19.2 fps @ 5472 × 3648 Mono 8 |
| Pixel size | 1.85 μm | 1.85 μm x 1.85 μm | 2.4 μm × 2.4 μm |
| Sensor size | 1/1.7" | 1/1.7" | 1" |
| Shutter | rolling shutter | Rolling Shutter | – |
| Data interface | USB 3.0 | GigE | USB3.0, compatible with USB2.0 |
| Pixel bit depth | 10-bit | 8 / 10 / 12 bits | – |
| Sensor type | – | CMOS | CMOS, rolling shutter |
| Dynamic range | – | 60.0 dB | 65.5 dB |
| SNR | – | >38.5 dB | 41.5 dB |
| Digital I/O | – | 1 input (opto-isolated), 1 output (opto-isolated) | 6-pin P7 connector provides power and I/O, including opto-isolated input × 1 (Line 0), opto-isolated output × 1(Line 1), bi-directional non-isolated I/O × 1 (Line 2) |
| Price | $560.00 | $560.00 | $759.00 |
Values as printed in each product's data sheet; open a part for its drawing and full specifications.
The LS-CMR-0166 is the bench model the worked example below uses. The LS-CMR-0053 is the same sensor behind GigE at 9 fps, for longer cable runs. The MV-CS200-10UM is a 20 MP USB3 camera on a 1-inch sensor when the field must be wider or finer.
How far apart must the dark and bright peaks be?
Commit to two numbers: the bright mean must sit at least 20% above the dark mean, and the gap between the two means must exceed six times the larger standard deviation — on an 8-bit camera, means near 40 and 210 with spreads near 7 pass both with room to spare.
Build the histogram from the pixels you will actually threshold, not from the whole frame:
The 20% figure is the floor for even lighting. The 6σ rule is what actually protects the measurement: each peak spreads roughly ±3σ, so a gap wider than 6σ leaves an empty valley, and a threshold set anywhere in that valley classifies essentially every pixel correctly. When the light is uneven, σ grows, the two skirts meet in the middle, and 20% separation alone no longer saves you — the crossing gains a band of ambiguous pixels and your edge position gains noise. Widen the gap or shrink σ; both are lighting moves.
Which threshold value, and how do you find the edge with it?
Set the threshold midway between the two peak means, T = (μ_dark + μ_bright) / 2, then walk every row of the region of interest and record the column where the gray values cross it; the mean of those crossings is the edge position.
The full procedure, in order:
- Lock the light and the camera settings (two sections below) and capture 20 frames of the stationary part. Averaging them divides random noise by √20 ≈ 4.5.
- Crop to the region of interest: the edge plus about 50 pixels on each side.
- Build the histogram — 256 bins for 8-bit data — and read μ_dark, μ_bright and σ for each peak.
- Check both rules above. Fail either and you have a lighting problem, not a code problem.
- Set T = (μ_dark + μ_bright) / 2. Otsu's method lands within a few gray levels of the same value and automates the choice when the valley is deep.
- For each row, find the first column pair that straddles T, then interpolate on the gray values for sub-pixel position:
- Average x_edge over all rows and multiply by the millimeters-per-pixel scale. The standard deviation of the crossings is your live quality number — alarm when it doubles.
- Keep a binary render of the frame for commissioning only; a live binary image preview for defect inspection makes a bad threshold visible in seconds.
In plain code, the inner loop is four lines:
for y in roi_rows:
x = first column where gray[y][x] > T
xe = x − 1 + (T − gray[y][x−1]) / (gray[y][x] − gray[y][x−1])
edge_mm = mean(xe) × mm_per_pixel
A shorter variant replaces the per-row loop with one projection: sum image rows to build a stable intensity profile, then take the steepest point of that profile as the edge. Same data, same threshold idea, one curve instead of a thousand crossings.
How many pixels do you need across the field?
Plan on a tenth of a pixel of crossing precision and at least ten pixels across the smallest dimension you check: with 4,000 columns spanning a 60 mm field that is 15 µm per pixel and about 1.5 µm of single-shot edge precision in the image.
Worked example — a machined stop on a stage, field of view 60 mm:
- Camera: 12 MP USB3, a 4000 × 3000 array on a 1/1.7-inch sensor. That format is about 7.5 mm wide.
- Scale: 60 mm ÷ 4000 px = 15 µm per pixel.
- Magnification: m = 7.5 ÷ 60 = 0.125.
- Lens: at 200 mm working distance, f = 200 × 0.125 ÷ 1.125 ≈ 22 mm, so a 25 mm C-mount lens is the stock choice; it sits at WD = 25 × 1.125 ÷ 0.125 = 225 mm and still covers the field.
- Aperture and exposure: f/5.6 and 4 ms under a bar light put the bright peak at 214 and the dark side at 42.
Now the histogram test from the previous section: μ_dark = 42, μ_bright = 214, σ ≈ 7 on both peaks. Separation = (214 − 42) ÷ 214 = 80%, and the gap of 172 counts is about 24σ — an empty valley. T = (42 + 214) ÷ 2 = 128.
Precision stacks up like this. One row's crossing jitters by roughly ±0.5 px, which is ±7.5 µm at this scale. Averaging 600 rows divides the random part by √600 ≈ 24, down to about 0.3 µm; residual lens distortion, thermal drift and LED aging leave a well-built station at 1-2 µm repeatability. Absolute accuracy is a different budget: uncorrected distortion in a C-mount lens runs on the order of 0.1-0.2% of the field — 60 to 120 µm here — so calibrate the scale once with a gauge block or a ruled target before trusting millimeters.
What if the histogram has only one peak?
Change the geometry before you touch the code: for an opaque part, a diffused backlight behind it gives near-total separation, and for a metal edge that cannot be lifted out, a bar light grazing the surface at 10-20° turns the edge into a bright rim on a dark field.
A backlight is the strongest contrast available: the part reads black against a saturated field and the threshold problem nearly disappears. When the part must stay in place, angle does the work: a bar light about as long as the edge, fitted with its diffuser and set 10-20° off the surface, is the standard dark-field set-up. The LS-BAR-1276, a 372 × 28 mm diffused white bar light on 24 V, is a typical bench size. Sizes, colors and wavelengths are in the lighting finder.
- Size
- 200-400 mm
- Type
- Diffused
- Voltage
- 24 V
Color is the next lever: which LED colour gives the best contrast in machine vision matches red, blue, green, infrared or UV to the part's finish. Shiny surfaces get a polarizer pair — cross polarization to reduce surface glare — and stubborn metal fixtures get contrast paint selection for metal fixture edges as the honest last resort. If the part moves, strobe instead of lengthening the exposure; triggering strobe lighting from digital outputs shows the wiring pattern.
Which camera settings must be locked before you measure?
Lock exposure, set gain to zero and switch every automatic function off: auto exposure alone can move a thresholded edge by several pixels between frames, because it re-weights the histogram as the scene and the room lighting change.
Auto gain is quieter and worse: it changes the camera's own scale factor, so μ_dark and μ_bright move together and T follows, dragging your number with it while the part never moves. How auto functions choose which pixels count is covered in auto exposure and auto gain on color cameras. Turn gamma and any lookup table off as well — gamma reshapes the intensity ramp asymmetrically and shifts the midpoint crossing.
Choose the fixed exposure so the bright peak lands between 200 and 235 on the 8-bit scale, keeping saturation a safe distance away. If the dark side then sits too low, recover it in software: brighten a grayscale image without losing highlight detail lifts the valley without clipping the peaks. Under mains-driven room lighting, keep exposure a whole multiple of 10 ms; the diagnosis lives in camera flicker under artificial lighting. When the camera offers 12-bit output, take it — saving 12-bit camera images without losing intensity data keeps the valley structure intact.
What should you check before ordering anything?
Check seven things, all quick to test and costly to skip: two histogram peaks with both separation rules met, ten pixels per checked dimension, a lens that fits the working distance, the right interface, a mono sensor, a strobe channel if anything moves, and locked camera settings.
- Histogram: two peaks, separation ≥ 20%, gap > 6σ. Everything downstream inherits this.
- Pixels: at least ten across the smallest checked dimension; the calculator above turns that into a camera.
- Lens: focal length from f = WD × m / (1 + m) at your real working distance, and a mount the sensor's image circle fits.
- Interface: USB3 for a bench, GigE with PoE beyond a few metres.
- Sensor: mono, global shutter if the part moves and you will not strobe.
- Trigger: a strobe output or controller channel whenever the part is moving.
- Settings: exposure fixed, gain 0, gamma off — written into the station's start-up file.
When the list is clean, build the station from the configurator's camera step, or send the histogram and the field-of-view number through the contact page and we will pick the parts with you.
Questions
Does the part have to be stationary during capture?
For a thresholded edge, yes — or strobed. A rolling shutter reads the sensor line by line over tens of milliseconds, so a part moving even 1 mm/s skews the edge measurably between top and bottom of the frame. A strobe pulse of 100 µs or shorter freezes the geometry on any shutter type. With a global shutter and continuous light, keep exposure short enough that motion during exposure stays under a tenth of a pixel.
Can a color camera do this job?
Yes, though mono is the right tool. A color camera filters every pixel to one color band, then demosaicing interpolates the missing channels, which softens a clean step edge by about a pixel. If you already own the color camera, convert to gray with a plain channel average, or threshold the raw green plane if your SDK exposes it, and skip the demosaic step entirely.
Should the histogram use 8-bit or 12-bit data?
Take 12-bit when contrast is marginal; 8-bit is plenty when the peaks are far apart. Twelve bits put 4,096 levels where eight put 256, so the valley between the peaks and the sub-pixel interpolation both get finer, and the threshold arithmetic is identical either way. Store 12-bit frames losslessly — one JPEG round trip collapses the valley and the precision with it.
How often should the threshold be recalculated?
Every frame. One histogram pass over the region of interest costs a millisecond or two, so let the peak means — and therefore the threshold — track the light as LEDs age and room conditions change. Keep the alarm tied to the rules: if separation drops below 20% or the crossing scatter doubles, stop trusting the number and fix the lighting.
Can the same routine measure a width between two edges?
Yes. Find the left and right crossing on each row, take the width in pixels as the right crossing minus the left, and multiply by the same scale. Common-mode motion — part drift, thermal shift of the camera — moves both crossings equally and cancels in the subtraction, so widths are usually steadier than absolute positions. The scale itself still needs one calibration against a known gauge.

