Your threshold does its job, the seam line appears, and so do a dozen short stubs hanging off it. The usual fix — erode a few passes — trims the stubs and the line ends at the same rate, which is exactly wrong when the line feeds a tracker. This guide gives you the decision instead of a kernel-size guessing game: pick one of three pruning methods by how the spurs attach, run the longest-path extraction that keeps every pixel of the line, and budget the pixel cost when you stay with endpoint thinning. A worked example with a 640-px seam line shows the arithmetic, and the verification steps tell you within one run whether the prune removed spur or line.
Why erosion removes your line with the spurs
Erosion keeps a pixel only when the structuring element fits fully inside the foreground. The tip of a spur has no room for any 3×3 element, so it dies on the first pass. The end of your line is a tip too, and it dies on the first pass as well. Custom kernels, hit-or-miss endpoint patterns, alternating erode and dilate — every one of these charges the line ends for the spurs they remove.
The bill is predictable, which makes it budgetable:
Two consequences follow. Clearing the spurs completely takes at least one pass per pixel of the longest spur, so spur length sets the pass count. And the loss lands only at the two ends, which is why the erosion shortcut survives whenever nobody measures where the line starts and stops. Tracking usually measures exactly that.
Pick the method by how the spurs attach
One question decides the method: are the spurs part of the line's connected component, or separate blobs? Threshold, label the components, count the pixels in each — the same pixel counting that measures blob area in a binary image. If every spur is its own component, you never touch morphology.
| Method | Removes attached spurs | Line pixels lost | Passes over the image | Use when |
|---|---|---|---|---|
| Keep the largest component | Detached specks only | 0 | 1 | spur pixels never touch the line |
| Endpoint thinning, k passes | Spurs up to k px | 2k px | k | the line ends are not measured |
| Longest-path extraction | Any length | 0 | 2-3 | tracking uses the true endpoints |
Attached spurs force a choice between paying pixels (thinning) and paying a path search (extraction). Detached spurs cost one labeling pass. Real laser-stripe and seam images mix the two: glare throws off detached specks while flare keeps a stub welded to the line, so run the component filter first and the path extraction second.
What you need
A thresholded binary image with the line one to three pixels wide; thicker ribbons get skeletonized first, with polarity fixed before you start (skeletonizing line patterns covers the background-white failure). Three primitives cover every method here: connected-component labeling with pixel counts, a hit-or-miss kernel pair that strips skeleton endpoints, and a path search over the 8-connected neighbors of the line's component. A smart camera or a vision controller runs all three in a few lines of code; no GPU and no frame grabber needed.
Extract the longest line, step by step
- Clean before you threshold. Spurs are born in the gray image — flare, glare, a soft second reflection. Reject saturated pixels or shorten the exposure so the longest spurs never form; on a laser stripe, extract the profile peak instead of thresholding its skirt, as in laser line extraction cleanup. One setup note: shutter or block the projector before you lean into the ROI — a live beam at eye level is the classic stray-reflection injury.
- Label components and drop the specks. One labeling pass gives every component a pixel count. Components under a floor set from the smallest real feature — 20 px works for a 640×480 ROI — are glare fragments. If the line's count survives alone, stop; most detached-spur images end here.
- Test what is still attached. Scan the surviving component for endpoints: a foreground pixel with exactly one 8-connected foreground neighbor is an endpoint, three or more mark a branch point. A clean open line has exactly 2 endpoints. Extra endpoints mean attached spurs — keep going.
- Skeletonize if the line is thicker than 2 px. Path search over a 1-px curve is unambiguous; over a 3-px ribbon it is not. One thinning pass brings the line to its center curve.
- Search the endpoint-to-endpoint paths. Walk the component as a graph with 8-connected steps, scoring orthogonal moves as 1 px and diagonal moves as √2. When the skeleton is a tree (no self-crossings), two breadth-first sweeps find the longest path without testing every pair; with loops, test the few endpoint pairs directly. Keep the longest.
- Keep the path, rebuild the width only if a downstream tool needs area. Copy the path to a new image, dilate one 3×3 pass, and AND with the original binary. The line returns at full width and its old ends, with at most a 1-px stub at each former junction. For tracking, skip the rebuild: the 1-px path is the centerline the tracker wants.
- Seed with the previous frame. When several real curves compete, gate the search: among the endpoint pairs, keep the path whose midpoint lies closest to the previous frame's line, gated by a few pixels plus the expected per-frame motion. This pairs naturally with the search-region logic in line tracking inspection.
Worked example: a 640-px seam line with 12-px glare spurs
Take a typical case and price both routes. Inputs, example values sized to a seam: ROI 640 × 480 px; the thresholded line crosses the full ROI, so N = 640 px; the longest attached spur is s = 12 px of flare; calibration 0.05 mm/px at working distance; skeleton 8-connected. Substitute your own ROI, spur length and calibration — the arithmetic is the same.
Endpoint thinning route. A 12-px spur vanishes after 12 passes; add a safety pass, k = 13. The line loses 1 px per end per pass: 2 × 13 = 26 px, leaving 640 − 26 = 614 px. Each end now sits 13 px inside its true position: 13 px × 0.05 mm/px = 0.65 mm of end bias. Thirteen passes each sweep up to 640 × 480 = 307,200 pixels.
Longest-path route. Labeling sweeps the same 307,200 pixels once. The endpoint scan and the two breadth-first sweeps touch only the line's component; the rebuild adds one dilation. Three passes total, spurs gone whatever their length, and the line keeps all 640 px: end bias 0 px.
Decision. If the seam endpoints are measured to better than ±0.65 mm per end, endpoint thinning fails its budget on this setup — run the path extraction, three passes and nothing lost. If ±0.65 mm at the ends sits comfortably inside tolerance and the processor is tight, thirteen thinning passes ship with less code. Price both ways against your own numbers before you pick.
How to verify the prune worked
- Endpoint count. Run the one-neighbor scan on the output. An open line must show exactly 2 endpoints; more means a spur or fragment survived; zero means the line itself died — check polarity before celebrating.
- Length accounting. Compare the kept path's pixel length against the un-pruned component's longest endpoint-to-endpoint span. Extraction should land within a pixel or two; thinning within 2k. A bigger gap means thinning ate a junction or the line fragmented.
- Replay recorded frames. Write the thresholded image, the component map and the kept path from your code each cycle — the setup in saving a thresholded binary image shows the pitfalls — then replay 100 frames through the whole chain. The fitted line's residuals should match the un-pruned fit within the tracker's own jitter; growth means spur pixels still leak into the fit.
Common mistakes that eat the line
- Keeping the largest component and hoping. An attached spur is part of that component; its pixel count never drops. The component filter removes detached specks only.
-
Running a grayscale curve tool on the binary image. Curve-extract functions such as
imaq extract curvesfollow gray-level ridges and bring a tuning burden that a labeling pass already answered. - Skeletonizing a line that is already 1 px wide. Thinning a 1-px curve can snap it into fragments at junctions and hand the path search several short pieces. Skeletonize only at 2 px and wider.
- Scoring paths in 4-connected steps. Diagonal moves then score as nothing, and a straight diagonal line ranks below a crooked orthogonal one. Score 8-connected with √2 diagonals.
Run it where the image lives
Pruning is a per-frame, integer-pixel job; it belongs where the image already is. On the device, a smart camera with an open platform runs labeling, path search and the tracker beside acquisition. On a multi-camera line, a vision controller runs one copy of the code for every camera and drives the lights from its own outputs. Browse the smart cameras and the camera finder to match resolution to the ROI, or the vision controllers when one code base serves the whole line.
- Interface
- USB 2.0
- Resolution
- 6-10 MP
- Sensor format
- 1"
- Shutter
- Global
The LS-SMC-0017 is an X86-platform smart camera: an 8.8 MP global-shutter sensor on a 1" format with a C-mount, so a custom labeling and path-search pass runs next to the acquisition instead of in a separate PC.
- LAN ports
- 3-4 LAN ports
- USB 3 ports
- 2 USB 3 ports
- Light outputs
- 4 built-in channels
- Memory
- 8 GB
For the multi-camera line, the MV-VC2140-128G40-1T vision controller pairs an Intel N97 with 8 GB of memory, 3-4 LAN ports, 2 USB 3 ports and 4 built-in light channels: cameras in, results out, lights strobed, one box.
Ship checklist
- Threshold polarity fixed, line one to three pixels wide before any pruning runs.
- Speck floor set from the smallest real feature — 20 px in the worked example — applied by component pixel count.
- Endpoint scan in place: exactly one 8-connected neighbor is an endpoint, three or more a branch point.
- Either 2k px of end loss budgeted (thinning) or the path search implemented (extraction), decided from the end-bias arithmetic, not habit.
- Output shows exactly 2 endpoints on every replayed frame.
- Kept-path length within a pixel or two (extraction) or 2k px (thinning) of the un-pruned span.
- Intermediate binary images written for the first week on the line.
If you want a second pair of eyes on a real frame before the line ships, send the thresholded image and ROI size through the contact page. More image-level fixes of this kind sit on the systems topic page.
Questions
Does an opening (erosion then dilation) remove attached spurs from a thin line?
No, not safely. Opening keeps a pixel only where the structuring element fits entirely inside the foreground. A 1-px line offers no 3×3 element any footing, so an opening large enough to reach 12-px spurs deletes the whole line, and one small enough to spare the line never touches the spurs. Opening cleans thick blobs happily and fails on line tracking, where the structure you need is barely one or two pixels wide.
How many endpoint-removal passes do I need for a 15-px spur?
At least 15, and 16 with a safety pass: each pass consumes the spur's current tip, so a spur of s px needs s passes to vanish. The line pays 2 px per pass, so 16 passes cost 32 px — 16 from each end. Even a mid-line spur charges the same 32 px of overall span, which is the arithmetic to run before you pick thinning over path extraction.
Can I prune spurs on a smart camera without a PC in the loop?
Yes, if the camera runs an open platform: an X86-platform smart camera executes labeling, path search and tracking beside acquisition, so nothing leaves the device. If you would rather keep cameras simple and code central, a vision controller beside the line runs one copy for every camera and drives the lights from its own outputs. Choose by whether each camera should own its pass or one box should own the whole line.
What if the ROI holds two real lines and the tracker keeps the wrong one?
Longest-path extraction always scores the longer curve, so a scratch longer than the seam wins. Seed the search instead: keep the previous frame's fitted line, and among all endpoint pairs keep the path whose midpoint sits closest to that seed, gated by a few pixels plus the per-frame motion you expect. Seeding also settles the flicker case, where a spurious long curve appears in some frames and vanishes in others.
Should I prune spurs before or after fitting the tracking model?
Before. Spur pixels are outliers with long lever arms on a line fit: a 12-px spur at a right angle drags a least-squares fit toward itself, and an iterative or RANSAC-style fit can settle on the spur outright. Prune first, fit the kept path, then compare residuals against the pre-prune fit on the same frame — if they grew, the prune removed line, not spur.

