ComfyUI-H3-FaceRefine README

The project's README from github.com/Carasibana/ComfyUI-H3-FaceRefine. As one markdown file: README.md.

ComfyUI-H3-FaceRefine

A ComfyUI custom node set to refine and improve the quality of small faces in MiniMax H3 video.

MiniMax H3 renders faces poorly when the head occupies a small fraction of the frame. This is a property of head-size-in-frame, not of output resolution, so it persists at 720p and above. These nodes detect the face on every frame, crop to it so it fills a canvas, let H3 re-generate it, and composite the result back into the original video.

Modelled on Impact Pack's FaceDetailer, adapted from stills to video.


Results

Source on the left, refined on the right:

source vs refined, side by side

Single frame at full resolution:

Source Refined
source frame refined frame

The reason it works is what H3 is handed. Instead of a distant head a few dozen pixels tall, it gets the face tracked and normalized to fill the canvas:

Crop in: what H3 sees Crop out: what H3 returns
input crop refined crop
full-res still full-res still

These two are what to watch for temporal behaviour: the box has to sit still on a moving subject, or the refined face boils.


What's new in 1.1.0

H3 Load Video + Face Select is new. It loads the video, finds every face and every hard cut in one pass, and settles which face is the subject before the graph runs, so the tracker never detects the same video twice. It brings two interactive surfaces with it: Pick faces, for choosing the subject per shot by eye, and Preview coordinates, for checking where an X, Y lands.

That node adds one dependency: PySceneDetect (scenedetect>=0.7), which installs automatically with the pack. It backs cut_detection = auto (pyscenedetect) on both the new node and H3 Face Track + Crop, finding the hard cuts in a clip. Cuts matter because a cut renumbers every face and breaks continuity: with them known, the subject is chosen once per shot and the smoothing, interpolation and composite fade all run per shot instead of being dragged across the join. It is fed frame by frame from the decode pass that is already running, so it costs no second decode of the video. cut_threshold is PySceneDetect's own adaptive default of 3.0.

It is a soft dependency in practice: if it is missing or fails to import, the run does not stop. The video is treated as a single shot and the report says so — cut detection unavailable, treating the video as one shot.

H3 Per-Frame Denoise now takes and returns MODEL. A per-frame mask needs two changes to the model rather than the latent, so the node must sit in the model path and its model output has to reach the guider. Its two strength widgets were renamed denoise_multiplier_small_face and denoise_multiplier_large_face, which is what they always were.

This breaks workflows saved against 1.0.0. model is a required input, so an old graph fails validation with Required input is missing: model until you route the model through the node and take its model output onward to the guider. Nothing else in this release requires rewiring.

H3 Face Track + Crop gained a frame_count output to wire into the H3 node's length, a face_pick input for the node above, absent_shots for skipping shots the subject is not in, identity_model for choosing how faces are compared, cut detection, and the expanded select list below.

select went from two modes to nine. 1.0 offered largest and most_central; there are now largest_face, smallest_face, left_most, right_most, top_most, bottom_most, centre_most, closest_to_xy and detector_score, with direction in the name rather than a separate order widget. Saved workflows migrate on load: largest becomes largest_face, most_central becomes centre_most.

canvas_width / canvas_height now default to 768, H3's native short edge, rather than 512.

The auto canvas modes now clamp up to a minimum of 512×512. They size the canvas from the largest crop, and the crop is bounded by the source frame, so on a small face in a low-resolution clip the canvas used to track the crop down to a couple of hundred pixels — handing H3 the same small face it renders badly. manual is unaffected and still takes whatever you type.


Installation

Clone into ComfyUI/custom_nodes/:

git clone https://github.com/Carasibana/ComfyUI-H3-FaceRefine.git

Restart ComfyUI. The nodes appear under MiniMax H3/Face Refine.

Requirements

For the nodes themselves:

ComfyUI with MiniMax H3 support H3's own nodes are core (comfy_extras/nodes_minimax_h3.py), not an add-on, you just need a build recent enough to have them
a face detector e.g. face_yolov8m.pt in models/ultralytics/bbox/. The one thing you must supply yourself. For anime, use an anime face model instead. The nodes find it there on their own; the example workflows additionally need the folder registration described below, because their stored detector values carry its bbox\ prefix

Python packages (ultralytics, scipy, insightface, scenedetect) install automatically from requirements.txt / pyproject.toml. scenedetect is PySceneDetect, used only by cut_detection; without it cut detection is skipped and the clip is treated as one shot.

A note on onnxruntime. insightface needs it, and this pack deliberately does not pin a variant. If you install onnxruntime-gpu alongside an existing onnxruntime, the CPU-only package shadows it, CUDAExecutionProvider disappears, and identity matching silently runs on CPU. Install one or the other, not both. Check with: python -c "import onnxruntime; print(onnxruntime.get_available_providers())"

Additionally, to run the example workflows. The first three are needed to queue either template as shipped; the last two back a node pair that ships muted:

ComfyUI-VideoHelperSuite Both templates, for the save (VHS_VideoCombine). Auto Select also loads the source with it (VHS_LoadVideoPath); Manual Select loads video with this pack's own H3 Load Video + Face Select instead, so it needs VideoHelperSuite only to save
ComfyUI-H3-NativeAudioLock Both templates. Supplies MiniMaxH3NativeAudioLock, which drives lipsync. It ships inside that repository under custom_nodes/ComfyUI-H3-NativeAudioLock, so copy that folder into your own ComfyUI/custom_nodes/
ComfyUI-Impact-Pack Both templates. Its subpack registers the ultralytics_bbox / ultralytics model folders, and the templates' detector values are stored in that registration's naming — bbox\face_yolov8m.pt and segm\person_yolov8m-seg.pt. Without it those values are not in the dropdown and the prompt fails validation on load. It also supplies SAMLoader if you unmute the SAM mask pair
ComfyUI-GGUF only if you unmute the GGUF loader pair. Both workflows ship it muted in favour of the stock loaders
a SAM model in models/sams/ only if you unmute the SAM mask pair, alongside Impact Pack above

Model lookups go through ComfyUI's folder_paths, so anything registered in extra_model_paths.yaml is found automatically.

Models

Model Goes in Source
face_yolov8m.pt models/ultralytics/bbox/ Bingsu/adetailer
person_yolov8m-seg.pt (optional for the nodes, but both templates ship it wired as fallback_detector) models/ultralytics/segm/ Bingsu/adetailer
an anime face detector (optional, anime and other illustration) models/ultralytics/bbox/ deepghs/anime_face_detection, or Anzhc's face-seg models. face_yolov8m.pt may not find anime faces — see Anime and other non-photographic material
a CLIP Vision model (optional, only for identity_model = clip_vision) models/clip_vision/ any CLIP Vision model, wired through a CLIPVisionLoader. You probably already have one for IPAdapter or Redux
MiniMax H3 diffusion model models/diffusion_models/ Comfy-Org/MiniMax-H3
Qwen3-VL text encoder models/text_encoders/ Comfy-Org/MiniMax-H3
minimax_h3_video_vae_fp16.safetensors models/vae/ Comfy-Org/MiniMax-H3
minimax_h3_audio_vae_fp32.safetensors models/vae/ Comfy-Org/MiniMax-H3
a turbo LoRA (optional, big speed-up) models/loras/ Kijai/MiniMax-H3_comfy, distilled by lightx2v. The example workflows ship the 8-step v1.0 at strength 1.0, with BasicScheduler set to 8 steps to match — the step count must follow whichever turbo LoRA you load
sam_vit_b_01ec64.pth (optional, only if you unmute the SAM mask pair) models/sams/ Meta segment-anything
InsightFace buffalo_l (optional, crowd tracking on photographed faces) models/insightface/ downloaded automatically on first use. Backs identity_model = insightface; on illustration use clip_vision or ccip instead

H3's original weights are MiniMaxAI/MiniMax-H3; the Comfy-Org repository above is the repackaged form ComfyUI expects. The example workflows load the model and text encoder through the stock UNETLoader and CLIPLoader. Their stored values name the specific quantisations the examples were built against, so unless you happen to have those exact files you will need to re-pick both loaders after opening a template. A GGUF pair (ComfyUI-GGUF) sits beside them, muted, with a note: unmute it and mute the stock pair to fit H3 in 12 GB.

Only the face detector is genuinely required by the nodes themselves. Everything else is needed because the pipeline runs H3, so if you are already generating H3 video you will have it already.


Reading videos from outside ComfyUI

H3 Load Video + Face Select takes a path to any video on your machine, because source footage usually lives somewhere other than ComfyUI's input folder. Two things follow from that.

Browse uploads; pasting does not. The Browse… button copies the file you pick into ComfyUI's input folder and refers to it by name from then on, exactly like the stock image loaders. Pasting a full path instead leaves the file where it is and reads it in place — which is what you want for a 4 GB clip you would rather not duplicate.

Previews of pasted paths are served by this pack. ComfyUI's own file server only reaches its input folder, so a pasted path needs a route of its own. That route reads any file with a video extension that ComfyUI itself can read, named by the request. ComfyUI listens on localhost only unless you have deliberately changed that, so on a normal single-machine setup this is the same access you already have.

If ComfyUI is reachable from anywhere else — --listen, a tunnel, a shared box — set this to confine those routes to ComfyUI's own folder:

variable effect
H3_FACEREFINE_STRICT_PATHS Unset (default): paths anywhere are read. Set to any non-empty value: only paths inside ComfyUI's own folder are read; everything else is refused with a message saying so.

With it set, use Browse… to bring a video in rather than pasting a path to it.

Setting it, in the launcher you already use:

:: run_nvidia_gpu.bat, before the python line
set H3_FACEREFINE_STRICT_PATHS=1
# PowerShell, for the session
$env:H3_FACEREFINE_STRICT_PATHS = "1"
# Linux / macOS
export H3_FACEREFINE_STRICT_PATHS=1

It is read per request, so it takes effect as soon as ComfyUI restarts with it set.


Quick start

Two example workflows are included, both annotated in-graph. They differ in how the subject is chosen; everything downstream is identical:

Template Which face gets refined Video loaded by
H3 Face Refine Auto Select a ranking rule, largest_face as shipped your own video loader
H3 Face Refine Manual Select you, in Pick faces, one per shot H3 Load Video + Face Select

Both ship the SAM mask pair present but muted, so neither needs a SAM model to run. Unmute the pair to swap the rect paste mask for a face-shaped one, and drop feather to 4-8 when you do. Impact Pack itself is still needed either way — see Requirements — because the templates' detector values use the model-folder naming its subpack registers.

Both are installed as ComfyUI workflow templates. Open them from Workflow → Browse Templates once the pack is installed, no file hunting required. They also live in example_workflows/ if you would rather load the JSON directly.

Common to both

  1. Ref image(s) — the same character references you generated the video with. Ref 1 is the identity; Ref 2 is optional, for wardrobe or a second look.
  2. Prompt — the video's own prompt.

That is all either template needs beyond the video. length on the H3 node is already wired from the track node's frame_count, so it follows whatever clip you load, and width/height are wired from canvas_w/canvas_h so the crop and the latent cannot disagree.

Audio comes from the source clip. If you have an isolated vocal track for lipsync, unmute the separate audio loader and wire it in place of the clip's own audio.

Auto Select — a rule picks the face

Point VHS_LoadVideoPath at your video and queue. The tracker detects every face and takes the one select names — largest_face as shipped, with select_index taking the 2nd, 3rd and so on out of that ranking. See Choosing which face for the full vocabulary.

The subject is chosen once per shot, not per frame, and continuity follows that same face from there. If the clip has hard cuts, set cut_detection to auto (pyscenedetect) so each shot chooses again — a cut renumbers everyone, so a rule can otherwise land on a different person after the join.

Use this when one rule describes your subject for the whole clip: they are the biggest face, or the only face, or reliably centre frame.

Manual Select — you pick the face

Here H3 Load Video + Face Select replaces the video loader and does the detection itself, handing the tracker its boxes, its cuts and your choice on face_pick. The tracker never decodes or detects the clip a second time.

  1. Point it at your video — Browse… copies a file into ComfyUI's input, or paste any absolute path, since source footage usually lives elsewhere.
  2. Set cut_detection to auto (pyscenedetect) if the clip has hard cuts, so it is split into shots.
  3. Click Pick faces. The dialog scans once, then shows a frame per shot with every face outlined and numbered. Choose your subject in each one and press Use these — see Pick faces.
  4. Queue.

With select on manual the node will not run until a pick exists, so an unanswered graph fails fast and says so rather than silently refining whoever happens to rank first. The other select modes work here too if you would rather it choose: identity_reference matches a wired reference image per shot, and the ranking rules behave exactly as they do on the tracker.

Use this when a rule cannot describe your subject: a crowd, a face that is not the biggest, or a person who has to be followed across cuts that renumber everyone.

How it fits together

FRONT END, one of the two:
  Auto Select    VHS_LoadVideoPath           -> images, audio
  Manual Select  H3 Load Video + Face Select -> images, audio, face_pick

  images ─────────────────────────────────────────────────────────────┐
     │                                            (face_pick, Manual) │
     ▼                                                                │
  H3 Face Track + Crop                                                │
     ├── canvas_w/h, frame_count ──► MiniMaxH3ReferenceToVideo        │
     │                               (refs + prompt)                  │
     │                                      │ av_latent               │
     ├── crops ──────────────────► H3 Inject Video Latent             │
     │                                      │                         │
     │        UNETLoader ► turbo LoRA ─┐    │                         │
     │                                 ▼    ▼                         │
     │                      MiniMaxH3NativeAudioLock ◄── audio        │
     │                                 │ model + av_latent            │
     ├── transform ──────────► H3 Per-Frame Denoise                   │
     │                            │ model        │ av_latent          │
     │                            ▼              ▼                    │
     │                   BasicGuider     SamplerCustomAdvanced        │
     │                   BasicScheduler           │                   │
     │                                            ▼                   │
     │                                        VAEDecode               │
     │                                            │                   │
     └── transform ──────────► H3 Face Stitch Back ◄──────────────────┘
                                       │
                          original audio ──► save

H3 Per-Frame Denoise sits in the model path, not beside it. It takes MODEL in and returns a patched MODEL out, and that output must reach BasicGuider and BasicScheduler. Route the unpatched model straight past it and the mask still applies but the held frames come back nearly clean instead of at the sampler's current sigma — with no error to tell you.

The transform output is the spine of the whole thing: it records where every crop came from, so the stitch can put each refined face back exactly where it belongs.


Node reference

H3 Load Video + Face Select

H3 Load Video + Face Select

Loads a video, finds every face and every hard cut in one pass, and decides which face is the subject before the graph runs. Optional: without it the tracker detects and ranks exactly as it always has.

It replaces th

ComfyUI-H3-FaceRefine (Carasibana/ComfyUI-H3-FaceRefine)

face-restoration
Searching Database…

What is this?This is a ComfyUI custom node pack that fixes small, blurry faces in MiniMax H3 generated video by tracking each face per frame, cropping it to fill a canvas, letting H3 re-render the crop in detail, and compositing the refined face back into the original video.
Is this a standalone app or a plugin for ComfyUI?It is a set of custom nodes that drop into ComfyUI's node graph, modelled on Impact Pack's FaceDetailer but adapted from still images to video — not a separate application.
How do I install it and get the nodes into ComfyUI?Clone the repository into ComfyUI's custom_nodes folder and restart ComfyUI; the new nodes then appear under 'MiniMax H3/Face Refine', and two annotated example workflows are installed as ComfyUI templates.
What do I need already set up before using this — H3, detectors, Python packages?You need a ComfyUI build with MiniMax H3's own nodes, a face-detector model such as faceyolov8m.pt placed in models/ultralytics/bbox/, and Python packages (ultralytics, scipy, insightface, scenedetect) which install automatically from the pack's requirements.
Any pitfalls when upgrading or installing dependencies?Workflows saved against version 1.0.0 break on 1.1.0 because the Per-Frame Denoise node now requires a MODEL input that must be rewired to the guider; separately, installing both onnxruntime and onnxruntime-gpu can silently push identity matching onto the CPU without any error.
Is it safe to read videos from paths outside ComfyUI's input folder?By default the video-loading node can read any file path on your machine; if your ComfyUI instance is exposed beyond localhost, you should set the H3FACEREFINESTRICTPATHS environment variable so only files inside ComfyUI's own folder can be read.
What's new in version 1.1.0?It adds the H3 Load Video + Face Select node, which finds every face and hard cut in one pass so you can pick the subject per shot, expands automatic face selection from two modes to nine (largestface, leftmost, centremost, etc.), and raises the default refine canvas to 768px to match H3's native short edge.
Does face detection work on anime or illustrated characters?The bundled faceyolov8m.pt detector is trained on photographic faces and may miss anime faces, so for illustrated material you should swap in an anime-specific detector like deepghs/animefacedetection instead.
What extra ComfyUI nodes do the example workflows need?Beyond this pack, the templates require ComfyUI-VideoHelperSuite to save output video, ComfyUI-H3-NativeAudioLock to drive lipsync, and ComfyUI-Impact-Pack, whose model-folder naming the templates' detector dropdowns rely on.
Why does MiniMax H3 need a face refiner at all?MiniMax H3 renders faces poorly whenever the head takes up only a small fraction of the frame, regardless of output resolution; this pack crops each face to fill the canvas before handing it back to H3, which fixes the problem at its source.
Creator
Carasibana
Size
19.8 MB
Stars
516
Age
2mo
Commits · 90 Days