cv2.cornerHarris
Cv2.cornerHarris hands you a score map, not a list of corners
- src
- nparray
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.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| src | NPARRAY,IMAGE,MASK | Input 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. | |
| blockSize | INT | 0-2147483648–2147483647 | Neighborhood size (see the details on #cornerEigenValsAndVecs ). |
| ksize | INT | 0-2147483648–2147483647 | Aperture parameter for the Sobel operator. |
| k | FLOAT | 0.0000-1e+38–1e+38 | Harris detector free parameter. See the formula above. |
| borderTypeopt | COMBO | BORDER_DEFAULT | Pixel extrapolation method. See #BorderTypes. #BORDER_WRAP is not supported. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| nparray | NPARRAY | — |