You have a straight-edge finder locking onto two moving edges, and three questions follow: what the reported 280° angle is measured from, how to run the same processing over a recorded video file at its native frame rate, and how to turn pixel coordinates into a correction the controller can act on. This walkthrough answers all three with numbers: the angle convention and its signed conversion, a per-frame loop paced at 30 fps with the wait computed from the frame rate, and a worked example that turns a pixel offset into millimetres. You finish with a loop proven on an AVI recording before it ever sees a live camera, plus the checks that catch the failures that fool everyone: angle wrap-around and unpaced loops.
Read the angle: counterclockwise from the horizontal axis
The rake-style straight-edge finder fits a line through the intensity transition and reports its angle counterclockwise from the horizontal image axis, over a full 0–360° range. A reported 280° is a valid result: 360 − 280 = 80, so the edge runs 80° clockwise from horizontal, which is 10° off vertical (90 − 80 = 10). A tool with a ±90° range reports the same edge as −80°.
Your controller wants a signed tilt, not a bearing, so convert before output:
280° becomes −80°, and readings move smoothly through the vertical: 269° → −91°, 271° → −89°.
| Reported | Direction | Signed value |
|---|---|---|
| 10° | 10° counterclockwise from horizontal | +10° |
| 90° | vertical | +90° |
| 180° | horizontal, same line as 0° | treat as 0° |
| 270° | vertical, opposite search direction | −90° |
| 280° | 80° clockwise from horizontal | −80° |
| 350° | 10° clockwise from horizontal | −10° |
A line is symmetric under 180°, so 10° and 190° are the same edge found in opposite directions. Relative angles survive the conversion because both readings shift by the same 360°: edges at 280° and 265° differ by 15° before and after.
Pace the AVI loop at the file's frame rate
The recording is your test bench: identical frames on every run, so any glitch you see belongs to your loop, not the scene. Read one frame per loop iteration using the AVI-read example that ships with the development environment, and pace the loop to the file's frame rate.
- Open the AVI and read the frame rate and frame count properties from the file. At 30 fps, a 60 s clip holds 30 × 60 = 1,800 frames; if the count disagrees, the file is not what you think. If no usable rate comes back, divide frame count by clip duration; reading the frame rate reliably covers the failure modes.
- Each iteration, make exactly one read call into the image buffer and one timed wait. Two reads in one iteration duplicates motion; none freezes the loop.
- Run the two straight-edge finders on their search regions.
- Convert any angle over 180° to its signed value.
- Subtract the image center from each edge midpoint, as the next section defines.
- Measure the processing time of steps 2–5 and wait the remainder of the frame period.
- Log the frame index, both edge coordinates and both angles every iteration. This log is the acceptance test.
At 30 fps the period is 1000 / 30 = 33.3 ms. If your measured processing is 21 ms, that leaves 33.3 − 21 = 12.3 ms, so wait 12 ms. The decision point is plain: once processing meets 33.3 ms, the rate is gone. Cut the search region, or process every second frame at 1000 / 15 = 66.7 ms and account for the halved update rate when tuning the controller.
The jolting display and the occasional half-drawn frame have two known causes: the loop running unpaced, and the display update sharing the loop with processing. Pace the loop as above, and move the display or the processing into a parallel loop — grab and process in parallel shows the pattern. Keep one wait call per frame at the acquisition boundary, and the same skeleton carries straight into live edge detection from a video stream when the recording graduates to a camera.
Report positions against pixel (0,0)
Edge coordinates come back in pixels measured from (0,0) at the top-left of the image: column x grows to the right, row y grows downward. That origin sits on the sensor corner and moves with the camera, so for centering, build the error against the image center:
On a 4000 × 3000 image the center is (2000, 1500). Positive error_x means right of center; positive error_y means below it. Image y grows downward while most motion controllers count y upward — flip the sign once, at the output, and write the convention where the controls engineer will read it.
Subpixel coordinates carry a half-pixel convention — the subpixel coordinate convention guide explains why 2000.5 is the boundary between pixels 2000 and 2001 — so keep finder and consumer on the same convention. When the part itself can translate, anchor the frame to a physical feature instead: setting a coordinate system on a circle center walks through that registration.
Worked example: a 6.4 mm correction at 30 fps
Take a camera that outputs 4000 × 3000 pixels — the LS-CMR-0166 is one such 30 fps sensor. Put a 120 mm × 90 mm field of view on it. The 4:3 field matches the 4:3 frame, so both axes scale alike:
- Scale: 120 mm / 4000 px = 0.030 mm/px, and 90 mm / 3000 px = 0.030 mm/px confirms it.
- Edge midpoint reported at (2213, 1495). Error: x = 2213 − 2000 = 213 px, y = 1495 − 1500 = −5 px.
- In engineering units: 213 × 0.030 = 6.4 mm right, and −5 × 0.030 = −0.15 mm.
- Controller command: −6.4 mm on x, +0.15 mm on y after the axis-sign flip.
For the lens: a 1/1.7"-class sensor has an active width near 7.4 mm (the sensor's data sheet gives the exact figure), so magnification is m = 7.4 / 120 = 0.062. At a 300 mm working distance, focal length is f = 300 × 0.062 / (1 + 0.062) = 17.5 mm; the nearest stock C-mount choice, 16 mm, places that field at WD = 16 × 1.062 / 0.062 = 274 mm. Sensor pixel pitch: 7.4 mm / 4000 px = 1.85 µm.
Motion budget: an edge crossing at 150 px/s moves 150 / 30 = 5.0 px per frame. To hold blur at or under 0.5 px, exposure must be 0.5 / 150 = 3.3 ms or shorter; a 0.5 ms strobe pulse leaves 150 × 0.0005 = 0.075 px — frozen. Aperture trades directly against this: closing from f/5.6 to f/11 costs two stops, and 3.3 × 2 × 2 = 13.2 ms of needed exposure no longer meets the 3.3 ms limit. Stay near f/5.6–f/8 on a flat part at a fixed distance and buy light rather than exposure time.
Choose the camera for the live version
The recording hides two camera properties: shutter type and transport latency.
Rolling shutter samples the frame line by line, so a moving edge appears at different times top to bottom and the measured angle gains a false tilt: skew = arctan(edge_speed × readout_time / frame_height). At 5.0 px per frame across a 3000-row image with a near-full-frame readout, arctan(5 / 3000) = 0.10° — ignore it. At 5,000 px/s with a 20 ms readout, the smear is 5000 × 0.020 = 100 px over 3000 rows, and arctan(100 / 3000) = 1.9° of false tilt. Global shutter removes the term entirely.
The MV-CU020-90GC is a 2.5 MP GigE camera with a 1/1.7" global-shutter sensor. The MV-CH120-11UC is a 12 MP USB3 camera with a 1.1" global-shutter sensor. The LS-CMR-0166 is a 12 MP USB3 camera with a 1/1.7" rolling shutter that outputs 4000 × 3000 pixels at 30 fps.
Side by side:
| Specification | MV-CU020-90GC | LS-CMR-0166 | MV-CH120-11UC |
|---|---|---|---|
| Sensor type | CMOS, global shutter | – | CMOS, global shutter |
| Sensor model | GMAX4002 | Sony IMX226 | Sony® IMX304 |
| Pixel size | 4 μm × 4 μm | 1.85 μm | 3.45 μm × 3.45 μm |
| Sensor size | 1/1.7" | 1/1.7" | 1.1" |
| Resolution | 2048 × 1200 | 4000 × 3000 (12MP) | 4096 × 3000 |
| Max. frame rate | 49 fps @ 2048 × 1200 Bayer GB 8 | 30 fps | 30.3 fps @ 4096 × 3000 Bayer RG 8 |
| Dynamic range | 66.9 dB | – | 72.2 dB |
| SNR | 40.8 dB | – | 40.2 dB |
| Gain | 0 dB to 11.1 dB | – | 0 dB to 24 dB |
| Exposure time | 4 μs to 10 sec | – | UltraShort exposure mode: 1 μs to 14 μs; Standard exposure mode: 15 μs to 10 sec |
| Exposure mode | Off/Once/Continuous exposure mode | – | Off/Once/Continuous exposure mode |
| Pixel format | Bayer GB 8/10/10Packed/12/12Packed | – | Mono 8/10/12; Bayer RG 8/10/10Packed/12/12Packed; YUV422Packed, YUV422_YUYV_Packed; RGB 8, BGR 8 |
| Price | $309.00 | $560.00 | $2,339.00 · quoted |
Values as printed in each product's data sheet; open a part for its drawing and full specifications.
Decision by case: edges under about 10 px per frame — any of the three holds its angle, so pick on resolution and cable length. Fast edges — global shutter, the 2.5 MP or the 12 MP global model. Long cable runs on the plant floor — GigE. The full range sits in area scan cameras, the camera finder filters by shutter and interface, and the camera configurator step folds in lens and light.
When the loop belongs on the device itself, a smart camera runs the edge tools internally and streams coordinates:
- Interface
- GigE
- Resolution
- 6-10 MP
- Sensor format
- 1/2.4"
- Shutter
- Rolling
The MV-SC5060XM-08M-WBN is a 6 MP GigE smart camera with an 8 mm M12-mount lens and a rolling-shutter 1/2.4" sensor. The same angle reference and centering arithmetic apply inside its edge tools; the pacing loop disappears because the device reports on every acquired frame. Browse smart cameras and vision sensors for the range.
Checks before you trust the numbers
- Throughput: 300 ± 10 processed frames in a 10 s log at 30 fps, counted from logged timestamps. Frame rate measurement from timestamped frames shows the method; the wall clock does not.
- Static repeatability: clamp a part and log 300 frames. A strong, well-lit edge repeats within about 0.1–0.3 px; swings of several pixels point at lighting, focus or the search region, not the finder.
- Scale: lay a ruled target in the field; both axes must return 0.030 mm/px in this set-up.
- Headroom: keep at least three frames' worth of motion as margin on each side of the search region — 3 × 5.0 = 15 px here.
- Wrap: after the signed conversion, a frame-to-frame jump above 180° is a wrap; add or subtract 360° before differencing.
- Exposure lock: auto exposure shifts edge contrast and subpixel position mid-run. Lock it, or read the true value per frame as reading exposure time from frame metadata describes.
When the usual answer is wrong
- The angle flips near horizontal: 359.4° one frame, 0.6° the next. The raw difference says 358.8° of rotation when the real motion is 1.2° (360 − 358.8 = 1.2). Unwrap the sequence before any differencing or gain calculation.
- A fixed 33 ms wait breaks the math the moment processing varies. With 21 ms processing plus a fixed 33 ms wait, the loop runs at 54 ms → 18.5 fps, not 30. Recompute the wait from measured processing time every iteration.
- Reading the AVI flat-out proves the computer, not the control loop. Real-time proof needs the file played at its own rate, with the log to show it.
- An edge leaving its search region makes the finder latch onto background texture, and the error steps to a plausible but wrong value. Gate the output on an angle window around nominal and a minimum edge contrast.
- A rolling shutter on a fast edge reports a tilt that is not there — 1.9° in the case above. Nothing in the calibration removes it; the shutter type does.
Short checklist
- Frame rate and frame count read from the file: 1,800 frames expected for a 60 s clip at 30 fps.
- One read call and one timed wait per iteration; wait recomputed as 1000 / fps − processing time.
- Angles over 180° converted to signed; wrap jumps unwrapped before differencing.
- Error referenced to image center; axis-sign convention written at the controller input.
- Scale calibrated with a ruled target on both axes.
- Exposure locked; 0.5 px blur budget checked against edge speed.
- Angle window and contrast gate active on every edge reading.
- Log of frame index, coordinates and angles kept as the acceptance record.
Questions
What is a straight-edge angle of 280 degrees referenced from?
It is measured counterclockwise from the horizontal image axis, over a full 0–360° range. A 280° line is the same edge tilted 80° clockwise from horizontal (360 − 280 = 80), which is 10° off vertical. For a controller, output angle − 360 whenever the reading exceeds 180°, giving −80° here; the converted value then moves smoothly through vertical edges instead of jumping.
How do I keep a video processing loop at 30 frames per second?
Compute the frame period and wait only the remainder: 1000 ms / 30 fps = 33.3 ms, minus the processing time you measured that iteration. At 21 ms of processing, wait 12 ms. Once processing reaches 33.3 ms the rate is gone: shrink the search region or process every second frame at 66.7 ms. Recompute the wait every iteration; a fixed wait drifts as processing time varies.
Why does the displayed video jolt and show half-drawn frames during processing?
Two causes: the loop runs unpaced, and the display update shares the loop with image processing, so a frame gets drawn while the processing pass is still writing it. Pace the loop with the computed wait and move the display or the processing into a parallel loop; the acquisition side then always hands over complete frames at a steady cadence.
How do I convert the edge's pixel position to millimetres for the control loop?
Divide the field of view by the pixel count on the same axis: 120 mm across 4000 pixels gives 0.030 mm/px. An offset of 213 px from center is then 213 × 0.030 = 6.4 mm. Calibrate both axes with a ruled target; the two scales match only when the field's aspect ratio equals the sensor's, so a mismatched lens field breaks the y axis.
Do I need a global shutter camera to track a moving edge?
Only when the edge moves fast. A rolling shutter smears the edge by speed × readout time: at 5 px per frame on a 3000-row image the false tilt is 0.10°, but 5,000 px/s with a 20 ms readout gives 1.9°. If the tilt feeds the controller, treat about 0.5° as the limit — with a full-frame readout that lands near 800 px/s of edge speed; above it, choose global shutter.
Can I build the pipeline in code instead of the scripting assistant?
Yes. Every function the assistant script chains together exists as a callable function in the same library, so rebuild it call by call: open the file, read one frame, run the two edge finders, output coordinates and angles. The assistant's code export only saves you the wiring. The recorded clip doubles as the regression test that proves both versions return the same numbers.
