Nodes/ComfyUI CV/CV Components Touching Border
ComfyUI Node

CV Components Touching Border

The sky is whatever runs off the edge of the frame

By bmad4ever·Created 4 months ago·Updated 14 days ago· 1
CV Components Touching Border
  • image
  • mask
  • area
  • count
  • found
◄borderany edge►
◄modekeep touching►
◄connectivity8►
◄margin0►

What this is for

"Which blob is the foreground" is a question people try to answer with size or with a probe point, and both answers are wrong often enough to be a running joke. Keep the largest component and on a washed-out driving frame you get the road, 71,344 pixels of it, not the sky you wanted. Put one probe point in the sky and a lamp post cuts that sky into 18 separate components - your probe caught one of them.

Touching the frame border is a different predicate, and for background it's the right one: sky, a table surface, water, a wall - anything unbounded runs off the edge of the image by definition. Invert it and you have the standard "drop detections the frame clipped" filter, which is what you want when a boat half out of shot is never going to be classified correctly anyway.

How it works

It runs cv2.connectedComponentsWithStats on the binarized input with your connectivity setting, then examines the label strips along the chosen edges. Any label appearing in those strips is "touching". keep touching keeps exactly those; remove touching keeps everything else. Both are one operation on the same label set, which is why they're a mode instead of two nodes.

The border dropdown is wider than just "any edge": top, bottom, left, right, top or bottom, left or right. That matters for panning shots and for frames where the ground plane, not the sky, is the thing you're isolating.

margin is the setting with the best tooltip in the pack, and it's worth reading rather than rediscovering: it's how many pixels in from the edge still count as the border. Zero sounds right and often isn't, because whatever the frame edge already carries becomes part of your mask. cv2.remap with BORDER_CONSTANT leaves a dark seam one row deep; any blur or derivative after it smears that seam a few rows further, so a mask built from gradients may only start answering the question at row 4. Not a morphology problem, either - OpenCV pads erosion with +inf, so an opening doesn't shrink a region at the image border.

Inputs and outputs

  • image - binary image or MASK; non-zero is foreground. It comes back in the format you put in.
  • border and mode - as above. any edge + keep touching is the sky-mask case; any edge + remove touching is the clipped-detection filter.
  • connectivity (advanced, default 8) - 8 also connects diagonals. On noisy masks this is the difference between one sky and a hundred islands.
  • margin (advanced, default 0) - see above.

Outputs: mask (your kept components, same format as the input), area (total kept pixels - handy as a sanity threshold, and as a "how much sky is in this frame" metric), count (how many components were kept) and found. Nothing surviving is found=false with an empty mask, not an error, so a frame with no sky doesn't blow up a batch.

Install

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
# restart ComfyUI

Manager users: ComfyUI CV (bmad4ever). Python ≥ 3.12 and a V3-node-API ComfyUI; opencv-contrib-python-headless~=5.0.0.93 comes from the pack's requirements. No models - it's connected components on a mask you already produced.

Common issues

  • Nothing is kept, but the region clearly touches the edge. Raise margin. The classic case is a mask derived from a remapped or gradient-based image where the outer rows are artefact, not signal.
  • The sky came back as 20 pieces. Connectivity 4 on a noisy mask, usually. Set it to 8, or clean the mask before this node rather than after.
  • You kept the road instead of the sky. Both touch the border. Restrict border to top, or use area as a filter downstream - the road touching the bottom edge is why any edge is not always the right answer.
  • Contrib nodes missing from the pack. A non-contrib OpenCV wheel emptied the contrib submodules in the shared site-packages/cv2; tools/repair_opencv_contrib.py --check / --apply.
Categoryimage/CV/contours

Inputs (5)

NameTypeDefaultDescription
imageCOMFY_MATCHTYPE_V3Binary image (or MASK); non-zero = foreground. The mask comes back in 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.
borderCOMBOany edgeWhich image edge a component must reach to count.
modeCOMBOkeep touching'keep touching' isolates the background that runs off the frame; 'remove touching' drops blobs the frame cut off and keeps the fully-visible ones.
connectivityoptCOMBO88 also connects diagonal pixels; 4 only direct neighbours.
marginoptINT00–64How many pixels in from the edge still count as 'the border'. It has to cover whatever artefact the frame edge already carries, or a region that visually reaches the edge is missed entirely: cv2.remap with BORDER_CONSTANT leaves a dark seam one row deep, and any blur/derivative applied after it smears that seam a few rows further, so a mask derived from gradients may only start answering at row 4. Morphology is NOT the problem - OpenCV pads erosion with +inf, so an opening does not shrink a region at the image border.

Outputs (4)

NameTypeDescription
maskCOMFY_MATCHTYPE_V30/255 mask of the kept components, in the image input's format.
areaINTTotal pixel area kept.
countINTHow many components were kept.
foundBOOLEANFalse when nothing was kept (empty mask out).