Nodes/Ruucm's ComfyUI Nodes/Gaze Offset Calculator
ComfyUI Node

Gaze Offset Calculator

The tiny node that stops LivePortrait from staring through you

By ruucm·Created 2 years ago·Updated 6 months ago· 3
Gaze Offset Calculator
  • sample_image
  • source_image
  • pupil_x
  • pupil_y
strength1.5

GazeOffsetCalculator is the pack's quietest node and also its most honest: it does one measurement, returns two numbers, and leaves the heavy lifting to ExpressionEditor. If you've ever driven LivePortrait-style expression nodes, you know the pain - the pupil_x / pupil_y inputs on ExpressionEditor move a character's pupils, but calibrating them by hand is guesswork, and "look slightly to the left" is not a number you can feel out. This node computes those numbers for you by comparing two photos: a sample_image (the character you want to redirect) and a source_image (the reference whose gaze you want to copy).

How it works

Under the hood it's InsightFace, the same buffalo_l landmark pack the rest of this repo leans on. It detects the face, then measures the iris position against the eye socket: for each eye it takes the 106-point landmarks for the socket corners, finds the socket center, and computes how far the iris (from the 5-point keypoints) sits from that center, normalized by eye width so resolution doesn't matter. Average the two eyes, and that normalized offset is the delta you need to add.

Then comes the calibration magic. ExpressionEditor's pupil_x response is saturating and non-linear - the author's own notes say pupil_x=-5 gives about a 0.0106 normalized shift but pupil_x=-15 only gets to ~0.0056. So the node multiplies the measured delta by a scale factor of 250 and a strength multiplier (default 1.5, deliberately set to overshoot and compensate for that saturation), then clamps to ExpressionEditor's ±15 range.

One honest limitation buried in the code: pupil_y is always 0.0. The comment says there wasn't enough data to calibrate vertical offset, so vertical gaze matching is on you for now. Horizontal is where this node earns its keep.

Inputs and output

  • sample_image - the face whose gaze you want to change.
  • source_image - the face whose gaze is the target.
  • strength - default 1.5, range 0–5. Bump it if the gaze change comes out too weak; drop it if ExpressionEditor overshoots and the eyes look like they're rattling around.

Outputs: pupil_x (FLOAT) and pupil_y (FLOAT). Wire both straight into ExpressionEditor's pupil_x and pupil_y inputs. That's the whole circuit.

Setting it up

Part of Ruucm's ComfyUI Nodes:

cd ComfyUI/custom_nodes
git clone https://github.com/ruucm/ruucm-comfy

Restart ComfyUI. You need InsightFace installed (insightface + onnxruntime-gpu are in the pack's requirements.txt), and on first run it auto-downloads the buffalo_l model pack (~326MB), so the first execution hangs for a while. Watch the console: the node prints its measured offsets to stdout, which makes tuning strength much easier than eyeballing a video feed.

Troubleshooting

  • It outputs 0,0 and nothing moves. No face detected - InsightFace returned nothing, so the node defaults to zero offset and ExpressionEditor does nothing. Use a crop where the face is clearly visible and roughly frontal.
  • The eyes shift but undershoot or overshoot. That's the strength dial doing its job; 1.0 is an exact-match attempt, and most people land between 1.0 and 2.0 depending on how saturated their ExpressionEditor setup is.
  • InsightFace won't install. The classic - its pretrained weights are also non-commercial, so keep that in mind if any of this feeds a product. See the InsightFace KB panel for the full install and licensing saga.

It's a niche node with a very specific job: make gaze-transfer workflows not miserable. If you're not driving ExpressionEditor, skip it. If you are, this is the calibration step you've been doing by feel.

Categoryface/gaze

Inputs (3)

NameTypeDefaultDescription
sample_imageIMAGE
source_imageIMAGE
strengthFLOAT1.50–5Multiplier for the calculated offset. 1.0 = exact match attempt. 1.5 = overshoot to compensate for ExpressionEditor saturation. Increase if gaze change is too weak.

Outputs (2)

NameTypeDescription
pupil_xFLOAT
pupil_yFLOAT