Nodes/ComfyUI CV/CV Shape Distance
ComfyUI Node

CV Shape Distance

Matching shapes when 'similar' isn't a moment

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
CV Shape Distance
  • contours_a
  • contours_b
  • distance
◄methodShape Context (deformation-aware)►
◄sample_points100►
◄index_a0►
◄index_b0►
◄angular_bins12►
◄radial_bins4►
◄hausdorff_normL2 (euclidean)►
◄rank_proportion0.60►
◄rotation_invariantfalse►

cv2.matchShapes compares two contours using Hu moments - seven numbers per shape. It's instant, it's rotation- and scale-invariant, and it's blind to almost everything else. When it can't tell your two shapes apart, you need a distance that knows where the points are.

CV Shape Distance wraps two of OpenCV's shape distances: Shape Context (cv2.ShapeContextDistanceExtractor) and Hausdorff (cv2.HausdorffDistanceExtractor). Both live in OpenCV's shape module as classes, which is exactly why no raw wrapper in the pack can reach them - the generator parses top-level functions only. Curated node, no alternative.

Smaller means more similar. Zero means identical. That's the whole output convention, and the number is only meaningful against a threshold you establish yourself.

Which method, and why the choice matters

Shape Context builds a log-polar histogram around every sample point and solves the correspondence between the two shapes. That's what makes it tolerant of bending and of scale - the author's description calls it "the strong matcher and much slower" and that's the honest trade. The sample_points count drives the cost hard, so start at the default 100 and only raise it if the discrimination isn't there.

Hausdorff is the worst-case nearest-point distance: cheap, purely geometric, and brutally sensitive to a single stray point. The author cites a measurement for this, which is worth more than most node docs: a 40° rotation costs 1.93 with rotation_invariant off and 0.010 with it on. On a 20° turn, 0.028 off versus 0.009 on. That's the entire argument for the toggle in two numbers.

And the honest limit, straight from the tooltip: it degrades past about 160°, and it is not a mirror test - a reflected shape lands among the large-rotation scores, not in a separate bucket. Hu-based matching is no better there, since Hu is reflection-blind in everything but its 7th component. If mirroring is your discrimination problem, use the mirror_mode policy on CV Filter Contours By Shape instead. That's a different node doing the thing this one can't.

The inputs that matter

  • contours_a, contours_b - whole contour sets, as CV Find Contours emits them. You pick which member with the optional index_a / index_b (default 0, which is the largest, since Find Contours sorts by area).
  • method - the two-way choice above. Defaults to Shape Context.
  • sample_points - INT, 4 to 1000, default 100. Both contours are resampled to this many points before comparison, which is why contours of different lengths are comparable at all.
  • rotation_invariant - Shape Context only, off by default. Off is deliberate: it "keeps saved graphs' numbers stable", which is a real consideration when you've tuned a threshold.
  • Then the second-order knobs: angular_bins and radial_bins for the Shape Context histogram, hausdorff_norm and rank_proportion for Hausdorff. rank_proportion is the useful one - 0.6 uses the 60th-percentile nearest-point distance instead of the true maximum, which is precisely what you want when one noisy pixel would otherwise dominate. Set it to 1.0 for classic Hausdorff.

One output: distance, FLOAT. Unitless for Shape Context, pixels for Hausdorff - and that difference is not cosmetic. A pixel-unit distance only makes sense between shapes that are already aligned and at the same scale. Feed it into a comparison or math node to turn it into a yes/no, then into a switch for the branch.

Install

ComfyUI Manager → ComfyUI CV → install → restart, or:

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"

Python ≥ 3.12 and a recent ComfyUI on the V3 node API. No model files. Shape Context ships in OpenCV's shape module, part of the standard wheel - contrib isn't needed for this node specifically, keep the contrib build anyway.

Common issues

  • Distance is huge and meaningless. You're comparing a pixel-unit Hausdorff distance between shapes at different scales. Normalize, or switch to Shape Context, which doesn't care.
  • Distance is ~0 for shapes that shouldn't match. Too few sample_points, so the resampling washed out the difference. Raise it and pay the time.
  • Graph got slow after you raised sample_points. Shape Context's cost climbs quickly with point count. It's the strongest matcher here and it charges accordingly.
  • You were hoping it would detect mirrors. It won't, and neither will Hu moments. CV Filter Contours By Shape's mirror_mode is the tool.
  • Contrib nodes vanished after a pip install. Plain OpenCV wheel over the contrib one - same shared site-packages/cv2. tools/repair_opencv_contrib.py --check, then --apply.
Categoryimage/CV/contours

Inputs (11)

NameTypeDefaultDescription
contours_aCV_CONTOURSFirst shape. A whole contour LIST is accepted; the contour selected by 'index_a' is used.
contours_bCV_CONTOURSSecond shape, compared against the first.
methodCOMBOShape Context (deformation-aware)Shape Context handles deformation and returns a unitless matching cost; Hausdorff returns a distance in PIXELS, so it only makes sense on shapes that are already aligned and at the same scale.
sample_pointsINT1004–1000Both contours are resampled to this many points before comparing, which is what makes contours of different lengths comparable. More points = finer and slower (Shape Context cost grows quickly).
index_aoptINT00–10000Which contour of 'contours_a' to use (0 = the largest, since 'CV Find Contours' sorts by area).
index_boptINT00–10000Which contour of 'contours_b' to use.
angular_binsoptINT124–64Shape Context only: angular bins of the log-polar histogram.
radial_binsoptINT42–32Shape Context only: radial bins of the log-polar histogram.
hausdorff_normoptCOMBOL2 (euclidean)Hausdorff only: the point-to-point norm used.
rank_proportionoptFLOAT0.600–1Hausdorff only: use this RANK instead of the true maximum (0.6 = the 60th-percentile nearest-point distance), which makes it robust to a few outliers. 1.0 is the classic worst-case Hausdorff distance.
rotation_invariantoptBOOLEANfalseShape Context only: measure each log-polar histogram against the local tangent instead of a fixed axis, which is what actually makes it rotation tolerant (measured on a 40-degree turn: 1.930 off, 0.010 on; on a 20-degree turn 0.028 off, 0.009 on). It DEGRADES past about 160 degrees, and it is not a mirror test - a reflected shape lands among the large-rotation scores. Off by default so saved graphs keep their numbers.

Outputs (1)

NameTypeDescription
distanceFLOATShape dissimilarity - 0 means identical. Unitless for Shape Context, PIXELS for Hausdorff.