CV Find Contours
The fork in the road for every vision pipeline
- image
- contours
- count
Everything in this pack's contour category starts here. cv2.findContours takes a binary image - an edge map, a threshold, a segmentation mask - and returns the outlines of whatever is lit up. If you're doing classical CV in ComfyUI, this is the node where pixels become shapes you can measure, filter, circle, compare and count.
How it works
The input is normalised first: colour becomes grayscale, and any non-zero pixel counts as foreground. So a mask preview, a Canny edge map, and a thresholded photo all go in without conversion.
Then the two structural choices:
mode-RETR_EXTERNAL(the default) returns only outermost contours, which is what you want when each blob is an object and holes are irrelevant.RETR_LISTreturns all contours with no hierarchy, which is what you want when holes matter or when overlapping contours are the point. The pack notes thatRETR_LISTuses OpenCV's TRUCO parallel engine (4.14+), so it's also the faster path for dense scenes on a modern build.method-CHAIN_APPROX_SIMPLEcompresses axis-aligned runs to their endpoints, so a rectangle is four points.CHAIN_APPROX_NONEkeeps every boundary pixel, which is what you want if you're measuring arcs, fitting curves, or doing anything where the exact outline matters. The default is the compact one, and it's the right default.
Contours come back sorted by area, largest first, with specks below min_area dropped. That sorting is not nothing: it means downstream "select the largest" and "rank by size" operations are index operations, not sorts.
Outputs are contours (a CV_CONTOURS socket - the pack's contour data type, not an image) and count. Zero contours is a valid result with count = 0, not an error. That contract, repeated across the pack's detection nodes, is what makes it possible to build a graph that survives an empty frame - and you should branch on it whenever a pipeline runs on video.
What comes next
The natural follow-ups, all in this pack: CV Filter Contours (keep by area, circularity, aspect ratio, extent, solidity), CV Filter Contours By Shape (Hu-moment matching against a reference), CV Enclosing Circle, CV Shape Moments (centroid, orientation, Hu invariants, chirality), CV Select Contour (pick one by rank or position), plus the drawing node for a quick look. There are also bridges that convert contours to plain point arrays when you want to do arithmetic rather than shape work.
A warning that travels with every contour pipeline: an open curve has near-zero area. If your edge map broke your object's outline into arcs, area-based selection and area-based filtering will both let you down - that's not a hunch, it's the reason CV Enclosing Circle defaults to selecting by perimeter and not area. When in doubt, measure by perimeter or arc length.
Tuning
min_area is a float with a step of 10 and a huge range, and it's the single most effective declutter: a Canny edge map of any real photograph produces hundreds of one-pixel contours from sensor noise. Start at something like 50–200px² for a 1024-wide image and go up until count looks like the number of objects you can actually see. Then, if you still have too many, filter by shape after finding contours rather than trying to pre-filter the binary image into submission - you'll get much more explainable results.
Install
# ComfyUI Manager → search "ComfyUI CV" → install → restart
# or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
cd comfyui_cv && pip install -r requirements.txt
Python ≥ 3.12 and a recent ComfyUI - this pack is written against the V3 node API, so on an older build none of its nodes register and there's nothing to debug from inside the node. The one dependency is opencv-contrib-python-headless~=5.0.0.93; this node is a core cv2 function, so it doesn't need contrib, but the pack's Contrib-category nodes do. Installing plain opencv-python over the contrib wheel is the classic breakage - all four OpenCV distributions share one site-packages/cv2, and the contrib submodules empty out silently. tools/repair_opencv_contrib.py --check, then --apply, is the pack's own repair path.
Gotchas
count = 999.min_areatoo low, or you fed a gradient image instead of a binary one.- Contours inside contours. You probably want
RETR_EXTERNAL, or you want the hierarchy and should be onRETR_LISTdeliberately. - Jagged outlines messing up your shape metrics.
CHAIN_APPROX_NONEplus a blur upstream; the compact approximation cuts corners on curves. - You fed it a photo. Non-zero-everywhere means one contour that traces the frame. Threshold or edge-detect first -
CV Fill Holesis a common companion when a mask has gaps.
bmad4ever's pack is a fork of geroldmeisinger's opencv-comfyui, rewritten on the modern node API by an author who also ships small utility nodes elsewhere in ComfyUI. He describes it as LLM-assisted and not production-grade. This node is a curated wrapper around one of the most-used functions in OpenCV, and the curation - the grayscale conversion, the non-zero rule, the area sort, the min_area filter, the empty-safe contract - is exactly the fiddly part people get wrong when they write it themselves.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| image | NPARRAY,IMAGE,MASK | Binary image (edges or MASK); non-zero = foreground. 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. | |
| mode | COMBO | RETR_EXTERNAL | RETR_EXTERNAL: only outermost contours (usual choice). RETR_LIST: all, ungrouped; uses the TRUCO parallel engine (OpenCV 4.14+). |
| method | COMBO | CHAIN_APPROX_SIMPLE | CHAIN_APPROX_SIMPLE compresses straight segments to their endpoints; CHAIN_APPROX_NONE keeps every boundary pixel. |
| min_area | FLOAT | 00–1000000000 | Drop contours whose area (px²) is below this - filters noise specks. 0 keeps everything. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| contours | CV_CONTOURS | — |
| count | INT | — |