cv2.goodFeaturesToTrackWithQuality
Cv2.goodFeaturesToTrackWithQuality
- image
- mask
- corners
- cornersQuality
Same corner detector as cv2.goodFeaturesToTrack, one difference that changes what the node is for: it gives you the score for each corner instead of just the coordinate. "Find corners" becomes "rank corners", and ranking is how you decide which points deserve your trust when the frame is noisy, half-occluded, or has one huge high-contrast region hogging all the attention.
Reach for it when the pipeline downstream is going to make a decision. Plain corners are fine if you are drawing dots on a debug overlay. If you are picking the points that seed a homography, tracking a window across frames, or rejecting detections, you want the response value on the socket so you can gate on it. This is the same instinct the community applies everywhere else in ComfyUI - filters and thresholds in the plumbing layer, before anything expensive runs (see comfyui-node-plumbing.md).
How it works
Identical math to the plain detector: per-pixel gradient structure over a blockSize neighbourhood, scored by the minimal eigenvalue (or the Harris response), thresholded against qualityLevel × best_score, then picked strongest-first with minDistance enforcing spacing. OpenCV 5 also takes gradientSize, which sizes the derivative operator used to build that response.
What is different is the return: two arrays in lockstep. corners is the (x, y) coordinates, cornersQuality the matching quality measure, in the same order. So cornersQuality[0] is the best corner's score, and sorting one sorts the other.
Being OpenCV's own WithQuality function, the second array is the raw response, not a normalized 0–1 number - it scales with image contrast and size. Do not hard-code a threshold like "keep scores above 50" across two different images; normalize against the first element (the best score) instead, which is exactly what qualityLevel already did internally.
Inputs you actually set
image- single-channel 8-bit or 32-bit, and a colour IMAGE link gets converted to grayscale for you. Frame 0 of a batch only - this function is not in the pack's per-frame loop list, so if you need per-frame corners, split the batch first.maxCorners- 0 = return everything. Two arrays of everything is a lot of wire.qualityLevel- 0.01 keeps everything within 1% of the best corner's score; raise it to 0.1 when you only want the strong ones.minDistance- minimum pixel gap between returned corners. Tighten for dense tracking, loosen for sparse.mask- a required socket here, and a genuinely useful one: white = search here. Pointing it at a coarse region mask ("the car, not the sky") is the cheapest way to make corners behave.blockSize- derives the score from ablockSize × blockSizeneighbourhood, default 3. Bigger is smoother and slower.gradientSize- default 3, the aperture of the gradient operator.useHarrisDetector/k- Harris scoring instead of Shi-Tomasi;kdefault 0.04.
Outputs: corners and cornersQuality, both NPARRAY. corners plugs into CV Draw Points, cv2.cornerSubPix, or cv2.calcOpticalFlowPyrLK as prevPts. cornersQuality plugs into cv2.sortIdx or CV Take By Index if you want the top ten points by strength, into CV Draw Points as per-point labels, or into Preview CV Array to eyeball the distribution.
Install
ComfyUI Manager → search comfyui_cv (bmad4ever), or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Python ≥ 3.12, a recent ComfyUI on the V3 node API, and pip install "opencv-contrib-python-headless~=5.0.0.93" - the pack's only required dependency, pinned because it curates behavior against that build. Nothing to download, no model checks.
When it goes wrong
- Both sockets are always connected in your head, but only one is wired. Because the arrays are parallel, reversing them (quality into the draw node, corners into the sort) silently produces nonsense rather than an error - coordinates as colors, scores as points.
Inspect CV Dataon each is the two-second check. - Do not compare quality values between images. They are undefined units; a brighter or busier frame produces larger responses across the board. Normalize or rank, never absolute-threshold across a batch.
- Empty results are legal. A soft, low-contrast frame can produce zero corners. Watch for the nothing-found case if a downstream node assumes points exist.
- Nothing in the pack validates which variant you want. Both corner nodes share the display name
cv2.goodFeaturesToTrack, so in the node search you will see two nearly identical entries and one is distinguished by(2/2). This one is the version with the quality array; the plain one is the version you want when you do not need scores. - Development note worth carrying: the README states the pack was written with heavy LLM assistance and that some workflows were test-overfitted rather than verified generally. Read the nodes you depend on.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| image | NPARRAY,IMAGE,MASK | - - - 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. | |
| maxCorners | INT | 0-2147483648–2147483647 | - - - |
| qualityLevel | FLOAT | 0.0000-1e+38–1e+38 | - - - |
| minDistance | FLOAT | 0.0000-1e+38–1e+38 | - - - |
| mask | NPARRAY,IMAGE,MASK | - - - 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. | |
| blockSizeopt | INT | 3-2147483648–2147483647 | - - - Preset to the OpenCV default (3). |
| gradientSizeopt | INT | 3-2147483648–2147483647 | - - - Preset to the OpenCV default (3). |
| useHarrisDetectoropt | BOOLEAN | false | - - - Preset to the OpenCV default (False). |
| kopt | FLOAT | 0.0400-1e+38–1e+38 | - - - Preset to the OpenCV default (0.04). |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| corners | NPARRAY | — |
| cornersQuality | NPARRAY | — |