AdaptiveThresholding
The threshold that survives shadows and uneven light
- src
- IMAGE
The difference between "threshold" and "adaptive threshold" is whether a shadow wrecks your mask. A plain threshold picks one cutoff for the entire image, so a photograph with a gradient of light turns into a mess - one half goes white, the other half goes black, and your nice binary mask looks like a split screen. Adaptive thresholding computes the cutoff locally, per neighborhood, which means it can follow the lighting. That's why it's the go-to first step for binarizing photos of documents, receipts, or anything shot under real-world light.
This node is a clean wrapper around OpenCV's cv.adaptiveThreshold, sitting in Bmad/CV/Thresholding alongside the pack's CLAHE and OtsuThreshold nodes. If you're working with uneven lighting, the standard chain is CLAHE → AdaptiveThresholding: equalize local contrast first, then threshold adaptively.
The inputs that matter
src- your image (grayscale internally; the node converts).adaptive_method- ADAPTIVE_THRESH_MEAN_C (average of the neighborhood) or ADAPTIVE_THRESH_GAUSSIAN_C (weighted average, sharper on edges). Gaussian is the usual pick.threshold_type- the standard OpenCV five: BINARY, BINARY_INV (inverted), TRUNC, TOZERO, TOZERO_INV. You'll almost always use BINARY or BINARY_INV.block_size(default 4, min 2, step 2) - the size of the neighborhood used to compute each pixel's cutoff. Bigger = more global behavior; smaller = more local, and noisier.c(default 2, range down to −999) - a constant subtracted from each computed threshold. Raise it to suppress noise, lower it to keep faint strokes.max_value(default 255) - the value assigned to pixels that pass the threshold.
One subtlety the author handled for you: OpenCV requires an odd block_size, but the widget takes even numbers and the node adds 1 internally. So block_size: 4 really means a 5×5 neighborhood. Don't fight it - just know the widget value and the actual kernel differ by one.
Output is a single IMAGE, returned as grayscale copied into RGB (the pack's standard convention), ready for the next masking node.
Install
Ships in bmad4ever/comfyui_bmad_nodes. ComfyUI Manager → search comfyui_bmad_nodes, or:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_bmad_nodes
cd comfyui_bmad_nodes
pip install -r requirements.txt
Restart. It's pure OpenCV (opencv-python~=4.8.1.78 pinned in the pack's requirements.txt); no models.
Gotchas
The two knobs to tune are block_size and c, and they interact. Too-small block + too-low c and your binary image is static. Too-large block and you're back to a global threshold with all the shadow problems. Also remember src is treated as grayscale - if your input is already a soft-edged mask, run it through a threshold first to get clean 0/255 values, or the "adaptive" part will react to mid-gray gradients you didn't intend.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| src | IMAGE | — | |
| max_value | INT | 2550–255 | — |
| adaptive_method | COMBO | ADAPTIVE_THRESH_GAUSSIAN_C | 2 options: ADAPTIVE_THRESH_MEAN_C, ADAPTIVE_THRESH_GAUSSIAN_C |
| threshold_type | COMBO | BINARY | 5 options: BINARY, BINARY_INV, TRUNC, TOZERO, TOZERO_INV |
| block_size | INT | 4 | — |
| c | INT | 2 | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |