Nodes/ComfyUI CV/cv2.spatialGradient
ComfyUI Node

cv2.spatialGradient

Both derivatives in one call, and why your preview is black

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.spatialGradient
  • src
  • dx
  • dy
◄ksize3►
◄borderTypeBORDER_DEFAULT►

cv2.spatialGradient gives you the raw gradient field of an image - how much brightness changes left-to-right (dx) and top-to-bottom (dy) at every pixel. That's the input to every edge, flow and structure decision downstream. Most nodes in this space hand you a decision instead: Canny thresholds and thins until it's binary, Laplacian hands you zero-crossings. If you want the numbers before anyone decided anything, this is the cheapest way to get both derivatives in one 3x3 pass.

This node is one of roughly 470 auto-generated raw cv2.* wrappers in ComfyUI CV (bmad4ever/comfyui_cv). Raw means exactly that: it maps the OpenCV function's parameters onto sockets and does no interpretation for you. The author of the pack is blunt about the trade-off - the whole set is LLM-generated, uncurated, and not something to put in production without reading the source yourself.

What it actually does

OpenCV's spatialGradient is a fixed 3x3 derivative filter applied in both directions. It also has a habit that surprises people: the outputs are signed. Gradient values go negative wherever brightness drops, so the results leave the node as NPARRAY (raw ndarray) sockets, not as images - the pack only echoes a socket type back out for functions it knows preserve image nature, and this isn't one of them.

Two more bounds are baked into the node's own tooltips: ksize "must be 3", and borderType accepts only BORDER_DEFAULT (BORDER_REFLECT_101) or BORDER_REPLICATE. Set anything else and OpenCV raises at run time rather than ignoring you.

Inputs and outputs that matter

  • src - takes a ComfyUI IMAGE or MASK directly, or an NPARRAY. Link an IMAGE and the wrapper converts it to 8-bit BGR, then auto-grayscales it, because this cv2 function only accepts single-channel input. A MASK stays single-channel and is used as-is.
  • ksize - leave it at 3. The widget accepts other integers; cv2 won't.
  • borderType - only the two supported values.

Outputs are dx and dy, both NPARRAY. They are not previewable images. Preview Image refuses them; use the pack's Preview CV Array or Inspect CV Data, or convert with CV Cast Array and CV Array → Image.

A batched IMAGE goes through frame by frame and the results come back stacked, so a 12-frame batch works in one call.

The workflow you probably want

The pack's own edges-and-gradients playground (06_edges_gradients_playground.json) is the honest reference: it runs the same pipeline through Sobel and then repeats it through cv2.spatialGradient, feeding the two fields into cv2.cartToPolar. Magnitude gets normalized for display (otherwise it's a dark smear), and direction goes into CV Flow To Color - hue = angle, brightness = magnitude, flat areas black because nothing points anywhere. That's the classic structure: gradients are the data, magnitude and angle are what you look at.

Square the magnitude, blur it, and you have the raw material of a structure tensor or a local orientation map.

Installing it

Any node in this pack needs the pack. ComfyUI Manager, search ComfyUI CV, install, restart - or by hand:

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

You need Python ≥ 3.12 and a ComfyUI new enough to have the V3 node API, or the nodes never appear. It must be a contrib OpenCV wheel: all four OpenCV distributions share one site-packages/cv2, so anything that installs plain opencv-python over it empties the contrib submodules. tools/repair_opencv_contrib.py --check then --apply diagnoses and fixes that. Behaviour is pinned to 5.0.0.93.

Where people get burned

The black-preview one is almost universal: dx is signed and mostly near zero, so it reads as a black rectangle. Preview abs(dx) or the magnitude instead. Second: wiring dx straight into anything expecting an IMAGE fails type-checking - it's an ndarray socket, and it stays one. Third: ksize: 5 looks harmless in the widget and throws in cv2. Set it to 3 and stop.

This pack is new and obscure - a corpus search turns up essentially no community discussion of it - so there's no crowd-sourced error catalogue to fall back on. The OpenCV 5.0 reference for spatialGradient is the authoritative source for edge cases, and there's no promised support for the pack itself.

Categoryimage/CV/low-level/cv2 S

Inputs (3)

NameTypeDefaultDescription
srcNPARRAY,IMAGE,MASKinput 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.
ksizeoptINT3-2147483648–2147483647size of Sobel kernel. It must be 3. Preset to the OpenCV default (3).
borderTypeoptCOMBOBORDER_DEFAULTpixel extrapolation method, see #BorderTypes. Only #BORDER_DEFAULT=#BORDER_REFLECT_101 and #BORDER_REPLICATE are supported.

Outputs (2)

NameTypeDescription
dxNPARRAY—
dyNPARRAY—