CV Filter Points By Distance
Keep the ticks in the band, not the noise
- points
- points
- count
You have a pile of 2D points - corners, keypoint locations, centroids, matched features - and you know where they should be: in a ring around the dial centre, within forty pixels of an anchor, not in the corners of the frame. This node is the geometric version of that knowledge. Keep everything whose distance from an origin falls inside [min_dist, max_dist].
How it works
np.hypot of the offset from (origin_x, origin_y), compared inclusively against both bounds. That's the whole implementation, and it's the right amount of implementation for the job.
The subtlety is the shape of the region, and it's the reason this node exists instead of a bounding-box crop: an annulus is not a rectangle. A corner detector run over a whole image will always find something in the background; restricting to a band around a known feature is how you turn "features in the image" into "features of this object" without a mask.
Inputs and outputs
points- anNx1x2point array, orNone. None or zero points is valid:count = 0, no error.origin_x,origin_y- the reference point in pixels. A dial centre, an anchor, a ray start. Defaults are(0, 0), which means "distance from the top-left corner" if you don't set them - a common accidental filter.min_dist(default 0) - keep points at least this far out. Inclusive.max_dist(default 1000000) - keep points at most this far out. Inclusive. The default is effectively "no upper bound", which is the opposite convention from the point-cloud version of this filter, wheremax_distance = 0disables the bound. Worth a second look when you're hopping between the two.
Outputs: points and count. Order is preserved, and there's no mask output - if you need to know which points survived, you'll want a different node, or a labelled point set to begin with.
The workflows it unlocks
- Dial and gauge reading.
CV Enclosing Circlegives you the dial's centre and radius; this node keeps the tick-mark features in a band just inside the rim, andCV Points To Polarthen converts them to angles. That's a needle reading without any model. - Anchored matching. After a match, points far from any plausible location are almost certainly false matches. An annulus around a known landmark costs nothing and removes a whole class of error before RANSAC sees it.
- Eyeballing a feature set. Set the band generously, draw the points, look. Seeing what's inside versus outside teaches you what your detector is actually doing better than any statistic will.
The gotcha that costs people an afternoon
min_dist = 0 and max_dist = 1000000 are the defaults, which means if you forget to set the origin, nothing visibly changes and you assume the node is a no-op. It isn't - it's been filtering from the origin in the corner the whole time, and your points were being kept because everything is within a million pixels of anything. Then you set a proper max_dist and the output empties, and it looks like the node broke. It didn't. Set the origin first.
Second: the bounds are in pixels of whatever coordinate space your points are in. If your points came from a resized image, they're in resized coordinates, and a band tuned on the full-size image is now in the wrong units. Check with CV Array Size or the image's dimensions before you tune numbers.
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 built on the V3 node API (older builds register none of this pack). Only dependency: opencv-contrib-python-headless~=5.0.0.93. This node is numpy-only, so the pack's contrib-wheel footgun - installing plain opencv-python over the contrib build silently empties the shared cv2 submodules - doesn't affect it, though it will remove the pack's Contrib-category nodes. tools/repair_opencv_contrib.py --check / --apply exists for that.
The pack is a fork of geroldmeisinger's opencv-comfyui, rewritten by bmad4ever on the modern API, with a loud disclaimer that it's LLM-assisted and not production-ready. A distance threshold is about as auditable as code gets; use it without ceremony.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| points | NPARRAY | Nx1x2 point array (or None). | |
| origin_x | FLOAT | 0.00-1000000–1000000 | X pixel coordinate of the reference origin (e.g. the dial center, anchor, or ray start). |
| origin_y | FLOAT | 0.00-1000000–1000000 | Y pixel coordinate of the reference origin (e.g. the dial center, anchor, or ray start). |
| min_dist | FLOAT | 0.000–1000000 | Keep points at least this far (pixels) from the origin. |
| max_dist | FLOAT | 1000000.000–1000000 | Keep points at most this far (pixels) from the origin. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| points | NPARRAY | — |
| count | INT | — |