cv2.matchShapes
Shape similarity that ignores size, rotation and position — on purpose
- contour1
- contour2
- float
cv2.matchShapes scores how alike two shapes are, and it's much older and much simpler than anything else you're running. It takes two contours and returns one float - lower means more similar, zero means identical. That inversion catches people out; it isn't a confidence score, it's a distance.
The interesting property is what it deliberately throws away. Because it compares Hu moment invariants rather than pixels, the comparison is invariant to translation, rotation and scale. A tiny circle and a huge ellipse-ish blob are "closer" to each other than either is to a long thin stripe. That's either exactly what you want - "is this the same kind of object, wherever it is and however big it turned out?" - or hopelessly wrong for your task, because a small "O" on a page and a dinner plate score as near-identical.
How it works
It converts each contour's Hu moments into log-scaled invariants and compares them. The method dropdown picks the comparison: CONTOURS_MATCH_I1 sums the differences of the reciprocals of the log-moments, CONTOURS_MATCH_I2 sums the raw log-moment differences, and CONTOURS_MATCH_I3 sums those differences normalised by the first contour's moments. I1 is the default and the most commonly used. parameter is the method-specific parameter - OpenCV documents it as not supported, so leave it at 0 and don't spend a weekend on it.
Because it's moment-based, it needs actual points, not a picture. Both contour1 and contour2 are NPARRAY-only - "a data array (points / matrix), NOT an image". The usual route into that socket: CV Find Contours → a select or filter node → CV Contour To Points, whose dtype matters (the pack's tooltips steer you toward float32 point arrays for the geometry functions). The output is a single float.
What you'd wire it into
A roundness/classifier gate. Find contours, turn each into points, compare against a reference contour you keep as a literal point set, and branch on the result. It's the kind of check people reach for when they want a cheap "is this the same family of blob as last time" test without a detector or a model. It also survives as a debugging tool: if your contour extraction is drifting between runs, the distance number moves before your eyes do.
Installing the pack
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. Python ≥ 3.12, a V3-API ComfyUI build, one pinned contrib OpenCV wheel. No models. cv2.matchShapes is core OpenCV, though the contour helpers you'll be wiring around it are the pack's own curated nodes.
Where people get wrong
Passing contours into the wrong socket shape. matchShapes wants a point array, so an NPARRAY of points. Wire an IMAGE in and you'll be comparing pixels as if they were coordinates.
Comparing a shape to itself after a resize and expecting zero. It should be near-zero, since the metric is scale-invariant - if it isn't, your contour ordering or point dtype changed, not the shape.
Reading the number as a percentage. It isn't bounded at 1 and has no natural threshold; collect a few known-similar and known-different pairs and pick the cutoff from those. The mask/contour machinery in this ecosystem normally travels as SEGS (see the detector→detailer loop in the KB), and those contours are not what this node takes - you convert first.
Empty or degenerate contours. A one-pixel contour has near-zero moments and the normalisation turns into garbage. Filter the contours for area before you compare them; the pack's contour filter nodes are the way to do that without a Python node.
The standard pack-wide trap: if another node pack installed a non-contrib OpenCV wheel over the contrib one, contrib-backed nodes silently disappear from the menu - all four opencv-* wheels share a single site-packages/cv2. Check with python tools/repair_opencv_contrib.py --check, fix with --apply. And keep in mind the author's own warning that this codebase was written with heavy LLM assistance and isn't production-grade without review - for a moment comparison that's low risk, but it's worth knowing whose math you're trusting.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| contour1 | NPARRAY | - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here. | |
| contour2 | NPARRAY | - - - A data array (points / matrix), NOT an image - only an NPARRAY link is accepted here. | |
| method | COMBO | CONTOURS_MATCH_I1 | - - - |
| parameter | FLOAT | 0.0000-1e+38–1e+38 | - - - |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| float | FLOAT | — |