🎯 Rennart Pixel Drift Fix
Put the edit back where the original was
- edited_image
- source_image
- fixed_image
The problem this actually solves
You run an edit - Qwen-Image-Edit, Kontext, a Fill pass, a round trip through Photoshop - and what comes back is almost your original. Except it's zoomed two percent, or nudged fifteen pixels left. Inside the generator nobody notices. The moment you composite it, stitch it into a bigger canvas, diff it against the source, or slide it under a compare node, that offset is all you can see.
This is structural, not a settings mistake. Qwen-Image-Edit's output comes back "slightly misaligned or blurred" even when you feed it a resolution that's a multiple of 112, and even the workflow that made Qwen edits pixel-perfect still draws "mini-zoom adjustments... sometimes it's pixel perfect, often it isn't." Whole-frame editors re-emit the whole frame; they never promised you the old geometry back.
Rennart Pixel Drift Fix is the node you bolt on to get it back: two images in (source, edit), one image out, warped onto the source's geometry and always at the source's dimensions, so crop-and-stitch and mask math downstream still lines up. No model, no VRAM - it's OpenCV doing old-fashioned photogrammetry, and it's fast.
How it works
The mechanism is plain, and knowing it tells you when the node will fail:
- Both images go to 8-bit grayscale and SIFT finds keypoints in each (the SIFT parameters mirror an earlier PixelDriftFix implementation, per the source comments).
- A brute-force matcher pairs descriptors and Lowe's ratio test at 0.80 bins the ambiguous ones.
cv2.findHomographyfits a transform from the edit's points onto the source's - RANSAC at a 5.0px threshold, inliers only - thencv2.warpPerspectiveresamples the edit into the source's frame.
Which is to say: find the structure both images still share, and solve for the one global transform that explains it. Texture, edges, text, a face that didn't move - that's the evidence.
mesh does the same, then layers a piecewise-affine warp on top: boundary anchors projected through the homography, the inliers merged in, scikit-image building a triangulated local warp, and a coverage pass so whatever the mesh misses falls back to the global fit. It can rescue local non-linear distortion one perspective can't - and it's slower, and experimental per its own tooltip.
The inputs that matter
source_image- the original; its width and height define the output size.edited_image- the drifted one. The author deliberately orders the image inputs so a bypassed node passes the edited image through, not the source.method-flat_4_points(default) ormesh. The tooltip: "flat_4_points gives better results and is faster. Mesh is experimental." Despite the name, it's a full homography fit from the RANSAC inliers, not a literal four-point solve. Take the default.max_mesh_points- default 400, range 100–10000, step 100, only read whenmethodismesh. 400 is the fast/good point; 10000 is the accuracy ceiling and a genuine wait.
One output: fixed_image (IMAGE), always at source resolution. Wire it into Save Image, ImageCompositeMasked, an Inpaint Stitch, a compare node - anything that needs the two frames to line up.
Install
Easiest route is ComfyUI Manager - search ComfyUI-Rennart. Manually:
cd ComfyUI/custom_nodes
git clone https://github.com/Rennart2025/ComfyUI-Rennart
Restart ComfyUI. Now the part the pack doesn't tell you: requirements.txt lists only torch and numpy, but this file imports cv2 and skimage.transform at the top, and base ComfyUI pulls in neither. If you run Impact Pack or WAS Node Suite you probably already have OpenCV; scikit-image is the one that bites. Into the same environment ComfyUI runs in:
pip install opencv-python scikit-image
Gotchas
A missing dependency hides the node instead of crashing it. The pack's __init__.py wraps every import in a try/except and prints [Rennart] ⚠️ Не удалось загрузить .... So if "Pixel Drift Fix" isn't under Add Node → Rennart/Image, that Russian line in your console is the answer. The node isn't in the README at all, by the way - those two tooltips are the documentation.
Batches pair by index and get truncated. The loop runs min(len(source), len(edited)) frames, so one source plus four edits gives you one output, paired against edited[0]. Feed it one pair at a time, or duplicate the source with Repeat Image Batch.
"Nothing happened" is a designed outcome. If SIFT finds under 10 keypoints, the ratio test leaves under 10 matches, or RANSAC leaves under 10 inliers, the node warns and passes the edited image through, resized to source dimensions. That's what happens when the edit is too different - a new background, a flat sky, a heavy restyle. The node assumes the two frames share structure; its own warning says "Images should be similar."
It re-renders your whole frame, and it only fixes geometry. Both images take an 8-bit round trip and the output comes out of warpPerspective, so everything is resampled once - untouched pixels aren't byte-identical anymore. Borders are BORDER_REPLICATE, so a big correction smears the edge instead of showing black. Run it late, after the editing and before the final upscale. And if your edit also came back warmer or yellow (the documented Klein and Qwen complaint), this won't touch it - put a color-match node after.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| edited_image | IMAGE | — | |
| source_image | IMAGE | — | |
| method | COMBO | flat_4_points | flat_4_points gives better results and is faster. Mesh is experimental. |
| max_mesh_points | INT | 400100–10000 | Only used when method is set to 'mesh'. Higher values increase alignment accuracy but take longer. 400 = good/fast, 10000 = best quality. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| fixed_image | IMAGE | — |