cv2.textureFlattening
Keep the edges, delete the texture
- src
- mask
- result
Some filters smooth; this one removes texture while holding onto the structural edges, which is a different job. Fabric weave, skin pores, film noise, brick grain - the stuff that gives a surface its identity at 100% zoom and gets in the way when you want flat colour regionally. cv2.textureFlattening uses a Sobel gradient to decide where an edge is, protects those, and flattens everything else into plate-like areas.
It's the gentler sibling of cv2.stylization in the same NPR family: less painterly, more "airbrushed plate", and it takes a mask that decides where it's allowed to work. In a modern workflow that puts it squarely in clean-plate and prep territory - flatten a surface before you clone something onto it, or knock texture off a region before you upscale it and let the upscaler invent cleaner detail.
One of roughly 470 auto-generated raw cv2.* wrappers in ComfyUI CV (bmad4ever/comfyui_cv); LLM-generated and uncurated, per the pack's own disclaimers.
Inputs and outputs
src is a type-preserving, image-only socket: link an IMAGE, get an IMAGE back, no conversion nodes needed. MASK and LATENT aren't accepted - the filter wants 8-bit 3-channel input. Batched IMAGEs are looped frame by frame and re-stacked.
mask is the one to think about. Its tooltip is OpenCV's own: "Input 8-bit 1 or 3-channel image." It's exposed as an optional input, so you can leave it unconnected - the pack's 17_seamless_clone.json workflow does exactly that - but the mask is not decoration here. It's how you say "flatten this region and leave my subject alone", which is what makes the node useful in a real graph rather than as a whole-image filter. If you want the effect everywhere, pass a solid white mask; being explicit about scope is worth one extra node.
The three numeric knobs come straight from OpenCV:
low_threshold, default 30, documented range 0–100 - the lower gradient bound. Edge pixels below it are treated as flat and get flattened.high_threshold, default 45, documented as "value > 100" - the upper bound. Edges above it are protected absolutely.kernel_size, default 3 - the Sobel aperture used to measure the gradient. 3 is the sane choice; larger is blurrier edge detection.
The gradient band between the two thresholds is the interesting part: pixels in it are treated as edges in proportion, which is why the two numbers matter relative to each other and not on their own. Pull low_threshold down and more survives; push high_threshold up and more gets flattened.
Practical framing
Two uses I'd actually wire. Clean-plate preparation: mask the surface you're about to patch, flatten the texture inside it, and the clone or generative fill you apply afterwards has a boring background to match instead of a busy one. Detail separation: flattening a copy and subtracting it from the original gives you a texture-only layer you can re-add at lower strength or recolour independently.
It's also the entry-level version of the NR filters playground in the pack (16_npr_filters_playground.json), which is the best way to see how it relates to stylization, detailEnhance, edgePreservingFilter and xphoto.oilPainting on one photo. Run them side by side once and you'll stop guessing which name does what.
Installing the pack
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"
Manager → search ComfyUI CV → install → restart also works. Python ≥ 3.12 and a ComfyUI recent enough for the V3 node API - older installs don't even list these nodes. The OpenCV wheel has to be contrib: all four distributions share one site-packages/cv2, so a stray pip install opencv-python empties the contrib submodules and contrib nodes disappear (tools/repair_opencv_contrib.py --check then --apply). Curated against 5.0.0.93.
Watch out for
Passing a 3-channel mask where the source is a different size - cv2 wants them matched, and it raises rather than resizing for you. Setting low_threshold above high_threshold, which produces an effect you'll struggle to explain. And judging it at full strength on a textured photo: the interesting settings keep some texture deliberately, because a completely flat plate reads as plastic.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| src | COMFY_MATCHTYPE_V3 | Input 8-bit 3-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. | |
| maskopt | NPARRAY,IMAGE,MASK | Input 8-bit 1 or 3-channel image. Optional - leave unconnected for the OpenCV default (None). 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. | |
| low_thresholdopt | FLOAT | 30.0000-1e+38–1e+38 | %Range from 0 to 100. Preset to the OpenCV default (30.0). |
| high_thresholdopt | FLOAT | 45.0000-1e+38–1e+38 | Value > 100. Preset to the OpenCV default (45.0). |
| kernel_sizeopt | INT | 3-2147483648–2147483647 | The size of the Sobel kernel to be used. Preset to the OpenCV default (3). |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| result | COMFY_MATCHTYPE_V3 | Echoes the 'src' input's format: an IMAGE link comes back as IMAGE, MASK as MASK, NPARRAY stays NPARRAY. |