SampleColorHSV
Stop guessing HSV bounds, steal them from a reference image
- rgb_image
- HSV_SAMPLES
The most annoying part of color-based masking is picking the HSV bounds by hand. You think the blue sky is "about 110 hue," you get a mask full of holes, and you slide sliders for twenty minutes. SampleColorHSV is the node that says: stop guessing - sample the actual pixels from a reference image, convert them to HSV, and hand them off to a node that computes the bounds for you.
Give it an image and it grabs sample_size random pixels (default 1000, up to 65536), converts just those samples to HSV, and wraps them in an HSV_SAMPLES object. That object carries the raw samples plus lazily-computed statistics - circular hue mean and standard deviation (hue wraps around, so it can't use plain averages), median, mode, and max deviations for saturation and value. The stats are cached, so downstream nodes can ask for them repeatedly without recomputing.
How you use it
Inputs: rgb_image, sample_size, and sampling_seed. That seed matters more than it looks - it seeds numpy's RNG, so the same seed always picks the same pixels. That's how you keep a color analysis reproducible across runs instead of chasing a mask that drifts every time you hit queue.
The output, HSV_SAMPLES, feeds BuildColorRangeHSV, which turns the sample statistics into an HSV color range. It has several interval strategies - a centered percentile band, mean ± 2 or 3 standard deviations, median-interpolated, and more - each tuned for whether your reference color is tight or messy. The range it produces is an HSV_COLOR pair you can feed straight into InRangeHSV to build the actual mask:
Image → SampleColorHSV → HSV_SAMPLES → BuildColorRangeHSV → HSV_COLOR → InRangeHSV → MASK
This is the workflow that finally made color segmentation click for me: sample a clean patch of the thing you want (a green shirt, a red car), derive the range, and the mask tracks it across frames or variants. For video or batch work, sampling from one representative frame and reusing the seed keeps the same pixels and the same bounds every time.
Installing
Ships in comfyui_bmad_nodes. ComfyUI Manager - search "comfyui_bmad_nodes" (or "Bmad Nodes") - install, restart. Manual:
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_bmad_nodes
cd comfyui_bmad_nodes
pip install -r requirements.txt
Restart after. No models to download. It's a CV node, so it needs opencv-python (pinned to ~4.8); if your startup log says "Not loaded: CV nodes," install the requirements and restart. Keep ComfyUI current too.
Watch out for
sample_size is capped at 65536 (256²) in the code - plenty for a stable estimate. And remember sampling without replacement means you can't sample more pixels than the image has. One honest note: the stats live inside a custom object type, so this node's output only plugs into the pack's own HSV nodes - that's a feature (it's how the stats get reused) but it does mean the family is a self-contained little ecosystem.
Inputs (3)
| Name | Type | Default | Description |
|---|---|---|---|
| rgb_image | IMAGE | — | |
| sample_size | INT | 10001–65536 | — |
| sampling_seed | INT | 00–9223372036854776000 | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| HSV_SAMPLES | HSV_SAMPLES | — |