Nodes/ComfyUI CV/cv2.cornerHarris
ComfyUI Node

cv2.cornerHarris

Cv2.cornerHarris hands you a score map, not a list of corners

By bmad4ever·Created 4 months ago·Updated 14 days ago· 1
cv2.cornerHarris
  • src
  • nparray
◄blockSize0►
◄ksize0►
◄k0.0000►
◄borderTypeBORDER_DEFAULT►

Here's the thing that surprises people about cv2.cornerHarris: it doesn't find corners. It computes, for every single pixel, how corner-like that pixel's neighborhood is, and returns the scores as an image-shaped array. The CV node in the ComfyUI CV pack is a direct wrapper, category image/CV/low-level/cv2 C, and it behaves exactly like the cv2 function - which means your next node is not a "draw corners" node. It's a threshold.

This is one of ~470 auto-generated cv2.* wrappers from bmad4ever's pack, a fork of geroldmeisinger's opencv-comfyui now rewritten on ComfyUI's V3 node API. The author's own README warns the low-level wrappers are uncurated and that you should expect to handle conversions yourself - this node is a textbook example of that promise being kept.

How it works

Over a neighborhood of blockSize pixels around each position, cv2 builds the second-moment matrix of the image gradients - the structure tensor - and scores it with the Harris measure:

R = det(M) − k · trace(M)²

A flat region has near-zero gradients, so R ≈ 0. An edge has one large eigenvalue and one near-zero one, and R goes negative. A true corner has both eigenvalues large and R peaks positive. That's the whole detector, and it's why the output is a float32 single-channel map of the same size as the input: one number per pixel.

Because the score comes from gradient products, the raw values are small and image-dependent. OpenCV's own tutorial thresholds at 1% of the map's maximum - that fraction, not an absolute number, is the reason fixed thresholds don't transfer between images.

The inputs and outputs that matter

src is the image. If you wire a normal 3-channel IMAGE into it, the pack converts to grayscale for you - cornerHarris is in the author's table of functions that only accept single-channel input, and the conversion is automatic rather than something you have to remember. An NPARRAY and a MASK both work too.

blockSize is the neighborhood you average over - 2 is the common starting point, 5–7 gives thicker, more stable responses on noisy photos. ksize is the Sobel aperture used for the gradients (3 is standard, higher is smoother). k is the free parameter in the formula: 0.04 is the near-universal default, and lowering it (0.02) makes the detector more eager, raising it prunes. borderType is optional and rarely touched.

The output is named nparray, not image, and that's not a typo to work around - it's an NPARRAY holding a raw score map. Preview CV Array is the node to look at it with (it has a normalize mode, which you need because the raw values are tiny); CV Array Statistic or cv2.minMaxLoc gets you the maximum when you want to threshold at a fraction of it. From there cv2.threshold → CV Array → Mask turns the response into something a ComfyUI mask input will take.

If you actually wanted corner coordinates

Use a different node. cv2.goodFeaturesToTrack in this same pack is the one that returns an array of corner points, and it has a useHarrisDetector toggle plus a k widget - same Harris maths, but it non-max-suppresses and returns the top maxCorners points instead of a map. The curated CV Detect Corners node goes further: FAST or GFTT (Shi-Tomasi/Harris) with proper KEYPOINTS that carry a response and chain into CV Draw Keypoints, CV Compute Descriptors and the matchers.

Reach for cv2.cornerHarris when you want to see or measure the corner response - weighting a region by how much structure it has, comparing two images' texture, building your own detector - and for the point-returning version when you want to track or match.

Installing it

Manager → search for the pack title ComfyUI CV → install → restart. Manual route:

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
pip install "opencv-contrib-python-headless~=5.0.0.93"

Python ≥ 3.12, a recent ComfyUI with the V3 node API, and the contrib wheel - a non-contrib opencv-python installed over it empties the contrib submodules and takes nodes with it (tools/repair_opencv_contrib.py --check diagnoses).

Common issues and troubleshooting

The output looks black. It is black, near enough: the response values for a normal photo sit at a tiny fraction of the 0–1 range the preview expects. Use the normalize mode on Preview CV Array, or threshold first.

"Why is my threshold not working on a different image?" Because R scales with contrast and gradient magnitude. Compute the max and use 0.01–0.05 × max, or cv2.normalize the map first so the threshold means something fixed.

You wired the map into Save Image and got a type error. NPARRAY and IMAGE are different sockets. CV Array → Image (and its reverse) are the bridges when you genuinely need one.

Categoryimage/CV/low-level/cv2 C

Inputs (5)

NameTypeDefaultDescription
srcNPARRAY,IMAGE,MASKInput single-channel 8-bit or floating-point image. 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.
blockSizeINT0-2147483648–2147483647Neighborhood size (see the details on #cornerEigenValsAndVecs ).
ksizeINT0-2147483648–2147483647Aperture parameter for the Sobel operator.
kFLOAT0.0000-1e+38–1e+38Harris detector free parameter. See the formula above.
borderTypeoptCOMBOBORDER_DEFAULTPixel extrapolation method. See #BorderTypes. #BORDER_WRAP is not supported.

Outputs (1)

NameTypeDescription
nparrayNPARRAY—