CV Detect Line Segments (LSD)
Straight segments with subpixel endpoints and no threshold to tune
- image
- segments
- widths
- lengths
- nfa
- count
CV Detect Line Segments (LSD) finds straight edge segments in one pass, without a threshold to tune, and returns them as data: subpixel endpoints and a measured width per segment.
That "no threshold" claim is the interesting part. Hough-family detectors work like an election - edge pixels vote, and you choose how many votes a line needs. LSD works on the gradient field directly, growing regions of pixels that share a gradient direction, so each output segment is a real, bounded piece of edge rather than the peak of an accumulator. Two practical consequences: there's no accumulator resolution to set, and two collinear edges stay two segments instead of being united into one optimistic line.
Compared to the node next door, CV Detect Lines (Hough): Hough gives you a picture with lines drawn on it, LSD gives you the numbers. Output is the same Nx4 (x1, y1, x2, y2) layout HoughLinesP produces, so CV Draw Segments plugs straight in when you do want the picture.
The inputs that matter
Required: image - and feed the original image, not an edge map. LSD computes its own gradients; handing it Canny output is the single most common mistake with this node.
Then:
refine-standard(default) splits arcs and refines endpoints;advancedadds the NFA (number of false alarms) validation and fills thenfaoutput;noneis the raw fastest pass. Start atstandard.scale(0.8) - the image is downscaled by this factor before detection. Below 1.0 suppresses noise and speeds things up at the cost of short segments. 1.0 finds more and smaller stuff.sigma_scale(0.6) - the blur sigma used for that downscale, expressed assigma = sigma_scale / scale.quant(2) - bound on gradient quantisation error. Raise it on noisy or heavily JPEG'd images.angle_tolerance(22.5°) - how far two neighbouring gradient directions can differ and still join the same region.density_threshold(0.7) - minimum density of aligned pixels inside a segment's bounding rectangle.
Optional: min_length (0 = keep everything), log_eps (only used when refine is advanced), and bins (1024).
min_length deserves a note: LSD has no length knob of its own, and short segments are what makes a result unusable - thousands of three-pixel specks. This is a post-filter the node adds, and it's the one you'll actually use.
Outputs
segments (Nx4 float32, subpixel), widths (measured width in pixels - real data, not a stroke), lengths (feeds a comparison node so you can keep only the structural edges), nfa (per-segment -log10 false-alarm count; all zeros unless refine is advanced, which is the only mode that computes it), and count.
Finding nothing is a valid result - empty arrays, count = 0, no error. Branch on it.
Install
From comfyui_cv (bmad4ever/comfyui_cv). ComfyUI Manager → search "ComfyUI CV", or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"
Restart ComfyUI afterwards. Python ≥ 3.12 and a recent V3-API ComfyUI. LSD is a create* factory class rather than a plain function, which is exactly why it needs a hand-written node - the pack's ~470 auto-generated wrappers only cover top-level functions, and the README is upfront that class-based APIs are absent unless a curated node bridges them. Keep the contrib wheel alive; a plain opencv-python over the top empties the contrib submodules and takes those nodes with it (tools/repair_opencv_contrib.py --check to test, --apply to fix).
Common issues
- You fed it an edge map. Feed the original. This is worth repeating.
- Thousands of tiny segments. Set
min_lengthto something sane for your resolution (20–50 px on a 1024-wide image) and raisescale's effect by loweringscaleto 0.5. - Segments break at every corner of a textured object. That's LSD being honest. Merge them downstream by grouping on angle, or accept it - a mesh of short segments is still useful for structure.
nfais all zeros. You're not inadvancedmode. That output doesn't exist otherwise, by design rather than as a bug.- Missed faint edges. Raise
scaleto 1.0 and lowerdensity_threshold. Both cost time.
If you want lines from a known kind of image - documents, facades, black-and-white drawings - Hough is often tidier because the vote threshold gives you a single lever for "how much do I trust this". LSD shines on natural images with real gradient structure, where the absence of a magic threshold is a feature and not a gap.
Inputs (10)
| Name | Type | Default | Description |
|---|---|---|---|
| image | NPARRAY,IMAGE | Image to search (converted to grayscale internally); feed the ORIGINAL image, not an edge map. Accepts a ComfyUI IMAGE/MASK directly (frame 0 of a batch) or an NPARRAY. Arithmetic ops (add, multiply, etc.) process the full IMAGE batch when both inputs have the same batch size. | |
| refine | COMBO | standard | Refinement level. 'standard' splits arcs and refines endpoints; 'advanced' additionally validates each segment with the NFA (number of false alarms) test and fills the 'nfa' output; 'none' is the raw, fastest pass. |
| scale | FLOAT | 0.800.1–2 | Image is downscaled by this factor before detection. Below 1.0 suppresses noise and speeds things up; 1.0 keeps full resolution and finds more short segments. |
| sigma_scale | FLOAT | 0.600.1–2 | Gaussian blur sigma used for the downscale, expressed as sigma = sigma_scale / scale. |
| quant | FLOAT | 2.00–20 | Bound on the gradient quantisation error; raise it on noisy or heavily compressed images. |
| angle_tolerance | FLOAT | 22.51–90 | Gradient angle tolerance in DEGREES for pixels to join the same line-support region. |
| density_threshold | FLOAT | 0.700–1 | Minimum density of aligned pixels inside a segment's enclosing rectangle; lower accepts sparser segments. |
| min_lengthopt | FLOAT | 00–10000 | Post-filter: drop segments shorter than this many pixels. 0 keeps every segment. LSD has no length knob of its own, and short segments are what clutters a result. |
| log_epsopt | FLOAT | 0.0-10–10 | Detection threshold for the 'advanced' NFA test: -log10(NFA) > log_eps. Only used when refine is 'advanced'. |
| binsopt | INT | 10241–8192 | Number of bins in the gradient-modulus pseudo-ordering. |
Outputs (5)
| Name | Type | Description |
|---|---|---|
| segments | NPARRAY | Nx4 float32 (x1, y1, x2, y2) subpixel endpoints - feed 'CV Draw Segments' directly. |
| widths | NPARRAY | (N,) float32 measured width of each segment in pixels. |
| lengths | NPARRAY | (N,) float32 segment length in pixels - use it with a comparison node to keep only the long structural edges. |
| nfa | NPARRAY | (N,) float32 -log10(number of false alarms) per segment (higher = more certain). All zeros unless refine is 'advanced', which is the only mode that computes it. |
| count | INT | How many segments survived - branch on it with if/else for the nothing-found case. |