Skip to content

Finding and framing faces

Pictures of people rarely arrive framed the way a page needs them: a team page wants square head shots, a book jacket a 4:5 author portrait, a story a vertical shot — from photos taken any way at all. Face Detect (image/face_detect) finds where the faces are, with a model that runs on Madoo’s own workers, and frames the crop for you: a rectangle around the main face, in the framing and the shape you choose, always inside the picture. Wire it into Crop and the head shot is made. No provider is called and no credit is spent.

It locates faces and identifies no one: it gives no fingerprint of a face and never compares two faces.

Domain The question
Teams and profiles a square head shot of everyone, from whatever photo they sent
Publishing the author’s portrait in 4:5 for the jacket, the same crop for every author
Events and social a 9:16 head-and-shoulders crop of the speaker for a story
Marketplaces and listings does this profile or host photo show a face at all?
Any pipeline with people measure the face apart from the scene: is it lit?

A portrait in a café, 896 × 1200. The face box, 184 × 184 pixels at score 0.83, with its six points; around it the crop region for the head in 1, 329 × 329 at 272, 251. Beside it the head shot that Crop and Resize made from it, 512 × 512.

For each face the node returns a score from 0 to 1 — how sure the model is, not a calibrated probability —, its box, six points (the eyes, the tip of the nose, the mouth and the two ears, the person’s right and left) and its size compared with the picture. Faces come best first and are numbered 1, 2, … within the run. Then it frames one of them — the largest, or the best scored — and gives the rectangle to crop.

Output What it is
crop_region the rectangle {x, y, width, height} to crop, framed around the selected face: wire it into Crop’s Region; absent when no face is found
faces the whole result as JSON (madoo.image-faces/v1): each face, the crop region, the model that ran
count how many faces were found, a number for a Predicate
found true when at least one was found, a boolean for a Filter
quality_check the check, for a Quality Gate: it fails with NOT_FOUND when no face is found

Top: the café portrait with three rectangles — face, head, head and shoulders — and the three crops they give, 239 × 239, 312 × 331 and 624 × 578. Bottom: a full-length shot of a model, 896 × 1200, cut around her face in three formats — head 1, 175 × 175; head 4, 166 × 208; head and shoulders 9, 332 × 590 — every crop inside the picture.

Framing (framing) says what the crop keeps around the face:

  • face — the face with a small margin, forehead to chin;
  • head (default) — the whole head, hair and chin, a little of the neck;
  • head_and_shoulders — head, neck and shoulders, down to the top of the chest.

Aspect (aspect) gives the crop a shape: none (default) keeps the framing’s own shape; 1:1, 4:5, 3:4, 2:3, 9:16, 5:4, 4:3, 3:2 or 16:9 widen the framing on its shorter side until it has that shape. Either way the rectangle always lies inside the picture:

  • without an aspect, the picture’s edge cuts it — a face near the top loses the room above the hair;
  • with an aspect, it keeps its shape: it moves inside the picture and, only when the picture is too small for it, it is reduced around the face.

faces.crop_region.complete is false when the edge cut or reduced the framing: the head shot is there, with less room than asked. The crop is as sharp as the picture: a face of 99 pixels in a full-length shot gives a 175-pixel head shot, whatever size it is resized to afterwards.

Six pictures. A full-length shot of a model: face 0.97, 99 pixels wide. A painting, Vermeer’s Girl with a Pearl Earring: face 0.78. A portrait of an astronaut: face 0.94. An astronaut on the Moon with a mirrored visor: no face found. A restaurant terrace with no one in it: no face found. A dog: no face found.

The model finds faces turned towards the camera, from about 5% of the picture’s shorter side up: portraits, profile photos, people in a shot, painted faces too. It misses faces more often when they are:

  • small — a face in a crowd or far away;
  • turned away or in profile, covered (a visor, a mask, a hand) or cut by the picture’s edge;
  • drawn in a simple style, or of a statue.

On the people pictures of this guide it finds every face, and on the dog, the visor and the empty room nothing.

Score Threshold (score_threshold, 0.6) leaves out weaker faces. At 0.5 a dark patch on a restaurant table and the reflection in a visor came through as faces; at 0.6 they do not, and every real face of these pictures scores above 0.77. Lower it to find more faces — small or half-turned ones —, at the cost of more false ones; raise it when a false face would be expensive.

To find smaller faces, connect a Region: the node looks only inside that rectangle, so a face that is small in the whole picture is large in the region — a group photo cut in halves, or the box of a person from Instance Segment. Boxes and the crop region stay in pixels of the whole picture.

The lab workflow on a picture of a dog: Face Detect finds no face, found false; the Quality Gate rejects the picture, so Head shot and 512 × 512 are skipped — branch rejected by the gate — and the profile photo is absent. The run completes with no failed step.

When no face is found, crop_region is absent — there is nothing to frame. Crop with no region falls back to its own width and height and, with none set, fails. So when a picture may have no face, decide first:

  • Quality Gate: quality_check into the gate’s checks, the picture into its Value, mode route; Crop reads the picture from the gate’s Value output. A picture without a face stops at the gate: Crop and everything after it are skipped, the run completes, and nothing is delivered for that picture. This is the lab, here on a dog.
  • Filter on found, when the workflow should do something else with those pictures: found into a Filter’s condition and the picture into its value for the pictures with a face, a second Filter with Invert on for the others — keep them aside, send them to a person (an if/else, see Branches).
  • Upright, in sRGB, 8 bits. As the other measuring nodes: EXIF orientation applied, colour profiles converted, HDR (PQ/HLG) refused with HDR_NOT_SUPPORTED; up to 40 megapixels.
  • At the model’s resolution. The picture is fitted into 192 × 192 for the model, whatever its size; boxes and points come back in pixels of the picture, to within a pixel or two.
  • Locally. The model (BlazeFace full range by Google MediaPipe, Apache-2.0, trained on pictures of people who gave their consent) runs on the worker’s CPU in a few milliseconds per picture; reading the picture takes longer than the model. The first step on a worker also loads the model.

Profile photos for a team page. Photo → Face Detect (head, 1:1) → Quality Gate (route, the photo into Value) → Crop (image from the gate’s Value, region from crop_region) → Resize 512 × 512. Every photo with a face becomes a square head shot of the same framing; one without stops at the gate. The lab is this recipe.

An author portrait for a book jacket. Portrait → Face Detect (head_and_shoulders, 4:5) → Crop → Resize to the jacket’s flap: the same framing for every author of the series.

A story from a full-length photo. Photo → Face Detect (head_and_shoulders, 9:16) → Crop: a vertical crop around the person, moved down if the head is near the top.

Require a face before an expensive step. Picture → Face Detect (Check ID has-face) → quality_check into a Quality Gate in route mode, the picture into its Value → the paid step reads the gate’s Value: a picture without a face never reaches it.

Measure the face, not the scene. Portrait → Face Detect → faces into Image Statistics’ Regions (each face is a region, face-1, face-2, …), the portrait into its Image: the brightness of each face — a face in shadow against a bright window.

Count the people facing the camera. Group photo → Face Detect → count, or faces into the JSON Enumerator (array path faces) to go through each face — to crop them all, give each crop its own Crop with the face’s bbox as Region.

Setting Default What it does
Framing (framing) head what the crop keeps: face, head, head_and_shoulders
Aspect (aspect) none the crop’s shape: none, 1:1, 4:5, 3:4, 2:3, 9:16, 5:4, 4:3, 3:2, 16:9
Crop Around (select) largest the face to frame when there are several: the largest or the one with the highest_score
Score Threshold (score_threshold) 0.6 faces scoring below are left out, 0.1 to 1
Max Faces (max_faces) 20 the most faces kept, best first, up to 100
Untagged Images (untagged) assume_srgb read a picture without a profile as sRGB, or fail
Check ID (check_id) face-detect the check’s name in a Quality Gate

A wrong setting — an aspect that is not in the list, a threshold out of range — is reported when the workflow is saved, with INVALID_FACE_DETECT_PARAMETER. In an App, Framing and Aspect can be offered to the people who use it.

  • It identifies no one. It says where a face is, never whose; it cannot tell whether two pictures show the same person, and nothing in its result could be used to.
  • It does not read expressions, age or anything about the person. A box, six points and a score.
  • It does not find a given person. To find this person — the one of a reference portrait — in a scene, use Locate Subject.
  • It does not crop by itself. It frames; Crop cuts — so the same region can also feed Image Statistics, or be adjusted before cutting.
  • It does not find every face. Small, turned away, covered or drawn faces are often missed: a picture without a face found is “none above the threshold”, not a proof that there is no one.

The faces lab runs the profile-photo recipe, at no credit cost: on the café portrait it finds one face at 0.83, frames the head in a square, 329 × 329, and makes the 512 × 512 head shot above; on the dog it finds none and the gate stops the picture, as shown in No face, no crop.

The lab workflow on the café portrait: Photo goes to Find the face, which finds 1 face at 0.83 and gives the crop region; Only with a face lets the photo through; Head shot crops it and 512 × 512 resizes it to the profile photo.