cv2.spatialGradient
Both derivatives in one call, and why your preview is black
- src
- dx
- dy
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 ComfyUIIMAGEorMASKdirectly, or anNPARRAY. 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. AMASKstays 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.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| src | NPARRAY,IMAGE,MASK | input 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. | |
| ksizeopt | INT | 3-2147483648–2147483647 | size of Sobel kernel. It must be 3. Preset to the OpenCV default (3). |
| borderTypeopt | COMBO | BORDER_DEFAULT | pixel extrapolation method, see #BorderTypes. Only #BORDER_DEFAULT=#BORDER_REFLECT_101 and #BORDER_REPLICATE are supported. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| dx | NPARRAY | — |
| dy | NPARRAY | — |