Nodes/ComfyUI CV/CV Detect Line Segments (LSD)
ComfyUI Node

CV Detect Line Segments (LSD)

Straight segments with subpixel endpoints and no threshold to tune

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
CV Detect Line Segments (LSD)
  • image
  • segments
  • widths
  • lengths
  • nfa
  • count
◄refinestandard►
◄scale0.80►
◄sigma_scale0.60►
◄quant2.0►
◄angle_tolerance22.5►
◄density_threshold0.70►
◄min_length0►
◄log_eps0.0►
◄bins1024►

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; advanced adds the NFA (number of false alarms) validation and fills the nfa output; none is the raw fastest pass. Start at standard.
  • 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 as sigma = 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_length to something sane for your resolution (20–50 px on a 1024-wide image) and raise scale's effect by lowering scale to 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.
  • nfa is all zeros. You're not in advanced mode. That output doesn't exist otherwise, by design rather than as a bug.
  • Missed faint edges. Raise scale to 1.0 and lower density_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.

Categoryimage/CV/features

Inputs (10)

NameTypeDefaultDescription
imageNPARRAY,IMAGEImage 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.
refineCOMBOstandardRefinement 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.
scaleFLOAT0.800.1–2Image 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_scaleFLOAT0.600.1–2Gaussian blur sigma used for the downscale, expressed as sigma = sigma_scale / scale.
quantFLOAT2.00–20Bound on the gradient quantisation error; raise it on noisy or heavily compressed images.
angle_toleranceFLOAT22.51–90Gradient angle tolerance in DEGREES for pixels to join the same line-support region.
density_thresholdFLOAT0.700–1Minimum density of aligned pixels inside a segment's enclosing rectangle; lower accepts sparser segments.
min_lengthoptFLOAT00–10000Post-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_epsoptFLOAT0.0-10–10Detection threshold for the 'advanced' NFA test: -log10(NFA) > log_eps. Only used when refine is 'advanced'.
binsoptINT10241–8192Number of bins in the gradient-modulus pseudo-ordering.

Outputs (5)

NameTypeDescription
segmentsNPARRAYNx4 float32 (x1, y1, x2, y2) subpixel endpoints - feed 'CV Draw Segments' directly.
widthsNPARRAY(N,) float32 measured width of each segment in pixels.
lengthsNPARRAY(N,) float32 segment length in pixels - use it with a comparison node to keep only the long structural edges.
nfaNPARRAY(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.
countINTHow many segments survived - branch on it with if/else for the nothing-found case.