CV Align MTB To Reference
When you don't want the frames negotiating with each other
- image
- image
Its sibling, CV Align MTB, runs OpenCV's joint process() - the frames optimise a common alignment together. This node takes the other approach: you name one frame as the reference, and every other frame is shifted independently until it best matches that. Same median-threshold-bitmap machinery, different contract. The reference frame passes through untouched, which means the output has a fixed point you can point at.
Why you'd reach for it
- Video stabilisation against frame 0. Independent per-frame shifts don't drift or average each other's errors, so the result is reproducible: same batch, same reference, same output, every time.
- Brackets where one exposure is the keeper. In a hand-held bracket of sky and shadow, the mid-exposure is usually the sharpest and least noisy. Make it
reference_indexand everything else registers to it, rather than to a consensus the dark frame gets a vote in. - Any time you need a deterministic answer. Two-frame joints are sensitive to which pair you compare; a fixed reference removes the ambiguity.
How it works
Each frame is reduced to grayscale uint8 and to a median threshold bitmap - "brighter or darker than this frame's grayscale median", which is exposure-invariant - and calculateShift finds the best integer translation against the reference's bitmap. If that shift is (0, 0) the frame is passed through unmodified; otherwise shiftMat applies it. Then the batch is reassembled in the input's own format.
That last part matters: the node accepts IMAGE, MASK, NPARRAY (list or 4-D) or LATENT and gives you back whatever you put in. Nothing is normalised to uint8 BGR permanently - a mask batch comes back as a mask batch, a latent batch as a latent batch.
The inputs that matter
image- the batch of 2+ frames to align.reference_index- 0-based index of the frame everything else is matched against, default 0. It is not shifted. An out-of-range index raises rather than quietly falling back to frame 0, which is the behaviour you want.max_bits- bit depth of the median bitmaps, default 4, range 1–32. Values 2–4 give reliable results in this OpenCV build; higher may return incorrect shifts on some inputs - the pack's own tooltip says that, and the code enforces it by clamping to 4 regardless. Don't crank this expecting precision.exclude_range- half-width of the pixel band around the median excluded from the bitmap, default 4. Raising it throws away more noise-sensitive pixels and makes the match more robust, up to the point where there's nothing left to match.cut- exclude the darkest and brightest pixels, on by default.
Output: image, the aligned batch, same format and size, same frame count and order.
Progress and previews
Because alignment is per-frame rather than one bulk call, the node drives a real ComfyUI progress bar and pushes a preview roughly twenty times across the run - so you can watch it work on a long batch instead of staring at a blank node. Small thing; it's the difference between "is this hung?" and knowing.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv
Restart, or install via ComfyUI Manager by searching ComfyUI CV. The pack's single hard dependency is opencv-contrib-python-headless~=5.0.0.93, and it needs Python 3.12+ plus a ComfyUI built on the V3 node API. Contrib is not optional for the pack as a whole (the xphoto/ximgproc nodes vanish without it) even though this node only needs core cv2.
Traps
max_bitsabove 4 is wasted. The node clamps it and the tooltip warns you; a high value on an input the build can't handle gives you a wrong shift rather than an error, so just leave it at 4.- A bad reference poisons the whole batch. If frame 0 is the blurriest one in the set, every other frame will dutifully blur-align itself to it. Pick the sharpest middle exposure.
- Translation only. Wind, water, a hand-held tilt, a subject that moved between shots - none of that is corrected, and no amount of tuning will change it. Align your bracket, then fuse.
- Order is preserved. The reference stays at its own index rather than being pulled to the front, which matters if a downstream node expects
exposure_timesin the same order as the frames you stacked (the pack's HDR merge nodes do).
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| image | COMFY_MATCHTYPE_V3 | Batch of 2+ images to align. Accepts IMAGE, MASK, NPARRAY (list or 4-D array), or LATENT. 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. | |
| reference_index | INT | 0 | Index of the frame to use as the alignment reference (0-based). This frame is not shifted; all others are shifted to match it. |
| max_bits | INT | 41–32 | Bit depth for the median bitmaps used by calculateShift. Values 2-4 give reliable results in this OpenCV build; higher values may return incorrect shifts on some inputs. |
| exclude_range | INT | 40–128 | Half-width of the pixel-value range excluded from the median threshold bitmap. |
| cut | BOOLEAN | true | Exclude the darkest and brightest pixels from the median threshold bitmap. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| image | COMFY_MATCHTYPE_V3 | Aligned images in the same format as the input. |