Face Detection and Crop v2
Crop faces out of video frames without making the crop jitter
- image
- cropped_faces
- face_metadata
Face Detection and Crop v2 is the node you reach for when you want every face in a batch cropped, padded, resized, and handed off downstream - without babysitting it frame by frame. It's aimed squarely at LTX-Video avatar pipelines (the author says so in the README) but it earns its keep anywhere you feed faces into another stage: dataset prep for a face LoRA, a crop-before-edit loop, or a face-swap chain that wants a clean square input instead of a full 1024-wide frame.
The mechanism, in one paragraph
Under the hood this is OpenCV Haar cascades - the same 2001 Viola-Jones detector that's been in OpenCV forever. No ML model, no weights to download, no CUDA kernels. It loads haarcascade_frontalface_default.xml (or the alternative classifier if you switch classifier_type), converts your frame to grayscale, and runs detectMultiScale. The code derives the scale factor from detection_threshold (0.8 → scaleFactor 1.04, tighter scan) and keeps minNeighbors=5. Detections get sorted by area, you get the largest or all of them, padding is applied as a percentage of the face size (never hardcoded), the crop is forced to your chosen aspect ratio, and the result is resized to output_height with Lanczos.
The two things that make this more than a trivial wrapper: it iterates the whole batch and returns aligned [B, H, W, C] tensors, and it has temporal smoothing for video. Frame-to-frame Haar boxes jitter, which looks awful if you're feeding them into a video pipeline. Set temporal_smoothing to 1–100 and give every frame in the sequence the same instance_id, and it runs an exponential moving average over the bbox coordinates so your crop glides instead of vibrating.
The inputs that actually matter
image- your batch. Can be a single image or a whole video-length batch; it loops every item.detection_threshold(default 0.8) - how strict the detector is. Zero detections? Drop it toward 0.5. False positives on background noise? Raise it.min_face_size(default 64) - the minimum face dimension in pixels. If your faces are tiny in the frame, lower this; if it's missing them because they're small, this is the knob.auto_padding_ratio(default 35) - padding as a percent of face size. The face isn't a rectangle; give it a little breathing room or you'll crop off the forehead.aspect_ratio-auto,1:1,9:16,16:9,4:3. For LoRA training1:1is the safe choice; for vertical avatar video9:16is the point.output_height(default 512) - the crop's height; width is derived from the ratio.output_mode-largest_faceorall_faces, plusface_output_format(stripvsindividual) for how multiple faces come back.temporal_smoothing+instance_id- video only. Leave both alone (0 / "0") for single images.
The padding input is legacy - if it's above 0 it overrides auto_padding_ratio. You almost never need it.
The outputs
cropped_faces is the IMAGE batch you feed into whatever's next. face_metadata is [B, 6] floats - x, y, w, h, score, detected, normalized to the image - and it is not an image, so don't wire it into an image slot; it's for downstream bbox logic. The node is flagged as an output node, so you can preview crops directly. When no face is found it returns a zero tensor with detected=0 in the metadata rather than crashing - by design, so check that flag if your pipeline starts producing black squares.
Installing it
ComfyUI Manager: search "Face Detection Node" and install. Manual:
cd ComfyUI/custom_nodes
git clone https://github.com/Limbicnation/ComfyUI_FaceDetectionNode.git
cd ComfyUI_FaceDetectionNode
pip install -r requirements.txt
Then restart ComfyUI. The dependency list is refreshingly short - opencv-python, torch, numpy - and there are no model files to fetch, because the Haar cascades ship inside the opencv-python package itself. That's the whole reason this node is so painless.
Where people get burned
Haar is old and it knows it. Profile shots, heavy angles, hats, and glasses trips it up; it's not RetinaFace. If the face isn't roughly frontal and reasonable size, no threshold tweak saves you. The main practical gotcha is treating temporal_smoothing as an image-mode feature - it does nothing (and isn't meant to) outside video, and it only stabilizes if instance_id is shared across frames. And if you're coming from an old v1.x workflow with misaligned positional widgets, the changelog shows the author spent several 2.1.x releases defending against exactly that; invalid combo values get replaced with defaults rather than hard-crashing, which is nicer than most custom nodes bother to be.
Honest verdict: it won't match InsightFace for identity-grade detection, and it's not the flashiest node in your graph. But for fast, batch-friendly, no-download face cropping with actually-usable video smoothing, it's a rare complete package.
Inputs (12)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| detection_threshold | FLOAT | 0.800.1–1 | — |
| min_face_size | INT | 6432–512 | — |
| auto_padding_ratio | INT | 350–100 | Padding as % of detected face size |
| aspect_ratio | COMBO | auto | 5 options: auto, 1:1, 9:16, 16:9, 4:3 |
| output_mode | COMBO | largest_face | 2 options: largest_face, all_faces |
| classifier_type | COMBO | default | 2 options: default, alternative |
| temporal_smoothingopt | INT | 00–100 | 0=disabled (image mode) | 1-100: EMA smoothing strength |
| output_heightopt | INT | 512256–2048 | — |
| instance_idopt | STRING | 0 | Unique ID for temporal smoothing (share across frames in same video sequence). Use '0' for image mode. |
| face_output_formatopt | COMBO | strip | Format for multiple faces (only applies with all_faces). Accepts 'strip' or 'individual'. |
| paddingopt | INT | 00–256 | Legacy padding in pixels. If >0, overrides auto_padding_ratio. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| cropped_faces | IMAGE | — |
| face_metadata | FLOAT | — |