Nodes/ComfyUI CV/cv2.ximgproc.niBlackThreshold
ComfyUI Node

cv2.ximgproc.niBlackThreshold

The threshold that survives a badly lit photo

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
cv2.ximgproc.niBlackThreshold
  • _src
  • result
◄maxValue0.0000►
◄typeTHRESH_BINARY►
◄blockSize0►
◄k0.0000►
◄binarizationMethodBINARIZATION_NIBLACK►
◄r128.0000►

Global thresholding picks one number for the whole frame, which is why it fails on the photo of a page taken next to a window: the left half is blown out, the right half is in shade, and no single value separates ink from paper in both. Niblack and its descendants fix that by computing a threshold per pixel from its own neighbourhood - mean minus a multiple of the local standard deviation. niBlackThreshold is OpenCV's implementation, with four of those formulas behind one dropdown, and it's the node that turns a mediocre phone photo of a document into a clean binary mask.

It's genuinely one of the most useful functions in ximgproc for anyone doing masks rather than models: text extraction, ink/paper separation, low-contrast feature masks, binarisation before skeletons and contours.

How it works

For every pixel: take the local window, get its mean and standard deviation, and apply the chosen formula. Niblack is mean − k·std; Sauvola adds a dynamic-range term (r) so the amount of local variation you need before something counts as foreground scales with the local contrast - which is why Sauvola is the usual choice for documents. Wolf and NICK are the same family tuned for low-contrast and degraded scans respectively.

The pack divides the inputs the way OpenCV does, and the tooltips are worth reading in full because they contain the two facts that decide whether this node works for you:

  • _src - single-channel 8-bit. It's in the wrapper's auto-greyscale list, so linking a colour IMAGE is fine: the node grayscales it before the call. The output echoes the input format, so if what you want downstream is a MASK, feed it a MASK (or bridge a MASK through Mask → CV Array); feed it a colour IMAGE and you'll get a 3-channel black-and-white IMAGE back.
  • maxValue - the value written to pixels that pass. 255 conventionally.
  • type - THRESH_BINARY (foreground is bright) or THRESH_BINARY_INV (foreground is dark - ink on paper). Only the binary variants are offered, sensibly; the other THRESH_* codes make no sense for local binarisation.
  • blockSize - the local window, odd (3, 5, 7 …). The tooltip's rule of thumb: comfortably exceed the features you want to keep. The pack's threshold playground uses 31.
  • k - the bias on the local standard deviation, and the sign is the thing people get wrong. Niblack/Wolf want a negative k (about −0.2 to −0.5) to catch dark text; Sauvola/NICK want a positive one (about +0.2). The author's own note: "flip the sign if the output comes out solid". The playground runs −0.5 Niblack, +0.2 Sauvola, −0.25 NICK on the same block size.
  • binarizationMethod (BINARIZATION_NIBLACK) - Niblack, Sauvola, Wolf, NICK. Try Sauvola first on text.
  • r (128) - the Sauvola/NICK dynamic range of the standard deviation; 128 is right for 8-bit data and it does nothing for Niblack.

Install

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv

Restart ComfyUI, or install ComfyUI CV from ComfyUI Manager. Python ≥3.12, a recent ComfyUI on the V3 node API, opencv-contrib-python-headless~=5.0.0.93. No files to download. Contrib-only node - a non-contrib OpenCV wheel on the same site-packages/cv2 will make it vanish from the menu; tools/repair_opencv_contrib.py --check is the tell, --apply the cure.

Common issues

Everything is white (or everything is black). The k sign, nine times out of ten. The other candidate is blockSize far smaller than the features you're trying to isolate.

Speckle noise in flat regions. Niblack's known weakness: where the local standard deviation ≈ 0, the threshold sits exactly on the mean and noise flips pixels. Sauvola's r term is the standard fix, and it's why the dropdown is worth using.

A grey ring around the result. Local windows eat into the border. Crop or pad (copyMakeBorder) if the edges of the frame matter to you.

What to do next. The output is a mask: feed it to CV Skeletonize or the generated cv2.ximgproc.thinning wrapper for centre lines, to contours for shape measurements, or to connected components for blob counts. One binarisation node, three very different pipelines - which is exactly the classical-CV slot that the model-based segmentation stacks in this ecosystem don't cover.

Categoryimage/CV/low-level/ximgproc

Inputs (7)

NameTypeDefaultDescription
_srcCOMFY_MATCHTYPE_V3Source 8-bit single-channel image. The image output(s) echo this input's format. 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.
maxValueFLOAT0.0000-1e+38–1e+38Non-zero value assigned to the pixels for which the condition is satisfied, used with the THRESH_BINARY and THRESH_BINARY_INV thresholding types.
typeCOMBOTHRESH_BINARYThresholding type, see cv::ThresholdTypes.
blockSizeINT0-2147483648–2147483647Size of a pixel neighborhood that is used to calculate a threshold value for the pixel: 3, 5, 7, and so on.
kFLOAT0.0000-1e+38–1e+38The user-adjustable parameter used by Niblack and inspired techniques. For Niblack, this is normally a value between 0 and 1 that is multiplied with the standard deviation and subtracted from the mean.
binarizationMethodoptCOMBOBINARIZATION_NIBLACKBinarization method to use. By default, Niblack's technique is used. Other techniques can be specified, see cv::ximgproc::LocalBinarizationMethods.
roptFLOAT128.0000-1e+38–1e+38The user-adjustable parameter used by Sauvola's technique. This is the dynamic range of standard deviation. Preset to the OpenCV default (128.0).

Outputs (1)

NameTypeDescription
resultCOMFY_MATCHTYPE_V3Echoes the '_src' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY.