Nodes/ComfyUI CV/cv2.Sobel
ComfyUI Node

cv2.Sobel

Finite Differences Done Right (and the ddepth Setting Everyone Gets Wrong)

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.Sobel
  • src
  • nparray
◄ddepthsame as input►
◄dx0►
◄dy0►
◄ksize3►
◄scale1.0000►
◄delta0.0000►
◄borderTypeBORDER_DEFAULT►

Sobel is the "hello world" of image gradients: convolve with a small weighted difference kernel and you have the derivative of the image. It's been in every vision textbook since the sixties, it's absurdly cheap, and it's still the right first step for edge orientation, structure tensors, flow fields and any pipeline where you need to measure how brightness changes rather than merely look at it.

This is the raw cv2.Sobel wrapper from ComfyUI CV (bmad4ever/comfyui_cv), and it's a good example of what the pack's ~470 auto-generated nodes are for: OpenCV's primitives, exactly as documented, available as nodes so you can build the geometry and measurement half of a workflow without leaving ComfyUI.

How it works

The derivative of a discrete signal is a difference, and OpenCV computes it with a separable kernel: a smoothing kernel along one axis times a difference kernel along the other. ksize chooses the smoothing width - allowed values are 1, 3, 5 and 7. ksize=1 means no smoothing at all (a bare [-1, 0, 1] gradient), which is noisier and also more honest about what's in the data. ksize=3 is the default [1, 2, 1] smoothing, and 5/7 are progressively blurrier.

dx and dy are the derivative orders: dx=1, dy=0 for the horizontal derivative, dx=0, dy=1 for the vertical, dx=2 for a second derivative. They're INT fields with no sanity clamp, so nonsense orders are on you - OpenCV will reject what it can't build.

The inputs that matter

  • src - IMAGE, MASK or NPARRAY. Batch-friendly: hand it a whole IMAGE batch and every frame gets processed.
  • ddepth - the output depth, and the field that decides whether this node works. The default is same as input, and on a uint8 image a derivative truncates: negatives clamp to zero, so a signed gradient becomes a one-sided half-gradient that still looks plausible on screen. Set CV_32F.
  • dx / dy - as above.
  • ksize (advanced) - 1, 3, 5 or 7. The default of 3 matches OpenCV.
  • scale / delta / borderType (advanced) - a multiplier, an additive offset, and the edge-extrapolation mode (BORDER_WRAP isn't supported).

One output: nparray. A gradient changes depth, so the socket can't echo an IMAGE the way blur nodes do - that's expected, not a bug.

Wiring it up

The standard chain is two Sobels plus cv2.cartToPolar, which turns the pair into magnitude and angle. From there: Preview CV Array in normalize mode to see the magnitude (raw gradient values look black until you stretch them), CV Flow To Color for hue-coded direction, CV Draw Flow Grid for arrows, CV Array Statistic or Inspect CV Data when you want numbers rather than pictures. The pack ships this whole arrangement as the CV Sobel 2D subgraph, and workflows/06_edges_gradients_playground.json walks through it alongside Laplacian, Canny and cv2.spatialGradient - if you're learning the node, open that workflow rather than hand-building it.

One expectation to set: a Sobel magnitude map is not great ControlNet conditioning. Dedicated preprocessors (Canny, lineart, softedge/HED) are trained to produce maps the models respond to; a first-derivative magnitude is noisy and edge-thickness dependent. Use Sobel when you want the actual numbers.

Install

Manager → search ComfyUI CV → install, or:

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

Restart ComfyUI. Python ≥ 3.12 and a recent ComfyUI on the V3 node API. No model files, no downloads.

Where people get burned

ddepth. Said twice because it's the mistake: if your Sobel output only catches bright-to-dark transitions, you left it on same as input with a uint8 source.

Reading gradient values as an image. Core's PreviewImage expects 0–1 data; a signed float32 gradient is not that. Convert deliberately with CV Array → Image, or use Preview CV Array, which is built for exactly this.

Forgetting scale exists. A 3×3 Sobel over an 8-bit image reaches ±1020, so if you do want an 8-bit output, scale is the knob for keeping it in range - that's what it's in the signature for.

Contrib wheel collisions. The four OpenCV distributions share a single site-packages/cv2; installing a non-contrib wheel over the contrib build leaves contrib-derived nodes missing from the menu with no error in the log. tools/repair_opencv_contrib.py --check detects it, --apply fixes it.

Categoryimage/CV/low-level/cv2 S

Inputs (8)

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.
ddepthCOMBOsame as inputoutput image depth, see "combinations"; in the case of 8-bit input images it will result in truncated derivatives.
dxINT0-2147483648–2147483647order of the derivative x.
dyINT0-2147483648–2147483647order of the derivative y.
ksizeoptINT3-2147483648–2147483647size of the extended Sobel kernel; it must be 1, 3, 5, or 7. Preset to the OpenCV default (3).
scaleoptFLOAT1.0000-1e+38–1e+38optional scale factor for the computed derivative values; by default, no scaling is applied (see #getDerivKernels for details). Preset to the OpenCV default (1.0).
deltaoptFLOAT0.0000-1e+38–1e+38optional delta value that is added to the results prior to storing them in dst. Preset to the OpenCV default (0.0).
borderTypeoptCOMBOBORDER_DEFAULTpixel extrapolation method, see #BorderTypes. #BORDER_WRAP is not supported.

Outputs (1)

NameTypeDescription
nparrayNPARRAY—