cv2.ximgproc.thinning
Turn a fat blob mask into a one-pixel skeleton
- src
- result
Skeletonisation is one of those operations that sounds academic and turns out to be the practical answer to a lot of questions: how thick is this stroke, how many branches does this shape have, where's the centre line of this road/lane/cable/letter, which way do these clock hands point. Thinning erodes a binary blob down to a single-pixel-wide line that runs through its middle, preserving connectivity. Everything you can then measure - length, junctions, endpoints, orientation - is trivial on a skeleton and painful on a mask.
Feeding it is the part people get wrong, so start there.
Inputs
- src - a single-channel 8-bit binary image, foreground at 255. The tooltip is unambiguous: "the foreground is eroded to a 1-pixel-wide skeleton. Threshold first." In practice, hand it a MASK, or a binary NPARRAY from a threshold node - this is one of the wrappers that doesn't grayscale a colour IMAGE for you, and the function insists on one channel. The output echoes the input's format, so a MASK in gives you a MASK back, ready for whatever consumes masks downstream.
- thinningType (
THINNING_ZHANGSUEN) - which algorithm. Zhang–Suen is the classic fully-parallel one; Guo–Hall tends to produce fewer spurious side branches, which matters a lot when you're counting junctions or tracing a path. If your skeleton comes back hairy, switch algorithms before you blame your threshold.
What it's actually for
- Width/profile measurement. Skeleton length plus blob area gives you an average stroke thickness; the pack's region-properties node reports skeleton length among its many columns.
- Topology. A skeleton is where "how many loose ends does this shape have" becomes answerable - the same node exposes skeleton loose ends as a feature.
- Path extraction. Lane markings, wires, cursive text, hand-drawn strokes: once you have a skeleton you have a curve you can sample, filter, or feed into the pack's contour machinery.
- Shape descriptors that don't care about weight. Heavy and light versions of the same glyph thin to similar skeletons.
The pack also ships a curated CV Skeletonize node that wraps this function with a morphological fallback, and it's the better default if you don't specifically want the raw function's behaviour - you get a working skeleton even when the fast path isn't available, and a dropdown naming the algorithms.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Restart ComfyUI, or install ComfyUI CV from ComfyUI Manager. Python ≥3.12, a recent ComfyUI on the V3 node API, opencv-contrib-python-headless~=5.0.0.93 - no models, nothing else to fetch. thinning is a contrib function, so a plain opencv-python wheel installed over the contrib one makes it disappear from the menu; tools/repair_opencv_contrib.py --check names that, --apply repairs it.
Common issues
Build errors like (-215) … CV_8UC1. Your input isn't single-channel binary - usually a colour IMAGE that went straight in. Mask → CV Array or an explicit threshold in front of it fixes it.
Skeleton is fragmented into dashes. The blob was already broken by the threshold. A closing operation (morphologyEx, MORPH_CLOSE) before thinning connects the pieces; thinning cannot invent a bridge.
Hairy skeleton with stub branches. Classic thinning artefact: small boundary bumps in the mask become short spurs. Try Guo–Hall, smooth the mask before thinning, or prune the output by removing short branches.
Nothing appears for one frame in a batch. The node takes frame 0 of an IMAGE/MASK batch when you wire one directly. For a per-frame effect, loop the frames through the pack's batch nodes (Image Batch → CV Batch, the array nodes, CV Batch → Image Batch) instead of hoping it multiplies itself.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| src | COMFY_MATCHTYPE_V3 | Source 8-bit single-channel image, containing binary blobs, with blobs having 255 pixel values. The image output(s) echo this input's format. 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. | |
| thinningTypeopt | COMBO | THINNING_ZHANGSUEN | Value that defines which thinning algorithm should be used. See cv::ximgproc::ThinningTypes |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| result | COMFY_MATCHTYPE_V3 | Echoes the 'src' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY. |