01The problem is the scale, not the maths
The obvious way to build this is to measure some facial proportions, measure the same proportions on animal photos, and pick whichever animal is closest. That approach fails immediately, and it fails for a reason worth stating precisely: humans occupy a tiny corner of the space that animals spread across.
Here are the six proportions the page uses, with the range covered by 34 human reference faces next to the range covered by the 12 animal references.
| Feature | Humans (n=34) | Animals (n=12) | Animals inside the human range |
|---|---|---|---|
| Face width ÷ length | 0.760 – 0.915 | 0.713 – 1.542 | 3 / 12 |
| Eye spacing | 0.213 – 0.295 | 0.269 – 0.838 | 3 / 12 |
| Eye size | 0.163 – 0.221 | 0.055 – 0.251 | 4 / 12 |
| Nose ratio | 0.221 – 0.304 | 0.203 – 0.447 | 5 / 12 |
| Mouth width | 0.305 – 0.452 | 0.228 – 0.726 | 6 / 12 |
| Eye tilt (degrees) | 0.34 – 11.74 | −13.99 – 28.84 | 2 / 12 |
Look at the last row. Human eyes tilt between 0.3° and 11.7° in this sample; the animal references run from −14° to +29°. A raw distance in that space does not ask "whose eyes are more like yours" — it asks "whose eyes are least far from the middle of a range you were never in." The answer is the same animal for almost everybody, which is exactly what happened.
Rank first, then compare. Convert your measurement into a percentile within the human sample, convert each animal into a percentile within the animal sample, and compare those two numbers instead. The question stops being "is your face 0.83 wide" and becomes "you are wider-faced than 78% of people — which animal is wider-faced than 78% of animals?"
That single change is the whole idea of the page. Everything after it is bookkeeping.
02What is actually measured
MediaPipe's FaceLandmarker returns 468 points. The page uses twelve of them:
Before anything is computed, the photo has to pass a frontality gate. The distance from the nose tip to the left edge of the face is compared with the distance to the right edge:
A face turned to the side compresses one half in the image, and every ratio downstream inherits that compression. Rejecting the photo with a visible score is more honest than silently returning a confident answer computed from a bad measurement.
03Percentile matching, in full
Both sides are converted with the same mid-rank formula, which is just the fraction of the sample lying below the value, with ties split in half:
Your six measurements go through the human sample; the twelve animals go through the animal sample. Both sides now live in the unit cube, and the comparison is a weighted root-mean-square distance across the six axes:
The weights are a judgement call, not a fitted parameter — face shape and eye placement carry more of what people read as resemblance than mouth width does. I would not defend the exact values; I would defend the ordering.
04The deer collapse
The first working version was tested and it kept returning deer. Not always, but far more often than one animal out of twelve should. It was easy to dismiss as a coincidence of the faces being tested, so I measured it instead.
The test runs 20,000 synthetic people through the matcher. Each synthetic person is built by drawing each of the six percentiles independently from the 34-person sample, which is the most neutral population I can construct from the data I have. With twelve animals, a well-behaved matcher should hand out roughly 8.3% to each.
| Animal | Win rate, no correction | vs 8.3% baseline |
|---|---|---|
| Deer | 21.7% | 2.6× |
| Otter | 15.6% | 1.9× |
| Rabbit | 11.3% | 1.4× |
| Dog | 10.4% | 1.2× |
| Capybara | 9.7% | 1.2× |
| Pig | 7.4% | 0.9× |
| Harbor seal | 7.1% | 0.9× |
| Cat | 6.9% | 0.8× |
| Fox | 4.1% | 0.5× |
| Beluga | 2.6% | 0.3× |
| Wolf | 1.8% | 0.2× |
| Quokka | 1.6% | 0.2× |
A 13.7× spread between the most and least reachable animal is not a quirk. Two of the twelve are effectively unreachable: a wolf or a quokka result was something almost nobody would ever see.
Why deer, specifically
Deer is not "more human-like" in any meaningful sense. Deer simply sits closest to the middle of the animal percentile cube, and the middle is where most people land once you rank them. I made this explicit by computing, for each animal, its mean distance to the whole human reference set:
The ordering of that chart is almost exactly the ordering of the win-rate table, upside down. The winner was being decided by the geometry of the reference set rather than by the face in the photo.
The correction
Ranking divides each raw distance by the animal's own average distance, raised to a power:
An animal that is close to everybody has a small average distance, so dividing by it inflates its distance and it stops winning by default. α controls the strength: 0 is no correction, 1 is full normalisation. I swept it.
| α | Most frequent | Least frequent | Spread | Entropy (max 3.58) |
|---|---|---|---|---|
| 0 | 22.0% | 1.6% | 13.7× | 3.247 |
| 0.2 | 15.3% | 2.2% | 7.0× | 3.378 |
| 0.35 | 14.2% | 2.7% | 5.2× | 3.442 |
| 0.5 | 13.0% | 3.3% | 3.9× | 3.478 |
| 0.65 | 13.0% | 4.1% | 3.2× | 3.495 |
| 0.8 | 13.1% | 3.7% | 3.6× | 3.496 |
| 1.0 | 14.0% | 2.3% | 6.1× | 3.480 |
The shipped value is α = 0.5. It captures most of the available improvement — spread 13.7× → 3.9×, entropy 3.247 → 3.478 out of 3.58 — while changing the top answer for 22.9% of faces rather than the 38.1% that α=1 would rewrite. Past about 0.65 the curve is flat and the only thing still moving is how much the correction overrides the actual measurement.
05What the fix cost
Every correction buys something with something. This one has a visible price, and it is visible on the results page itself.
The ranking uses the adjusted distance, but the percentage shown next to each animal is computed from the raw distance — deliberately, because the percentage should describe how similar you actually are, not how similar you are after a diversity thumb on the scale. The consequence is unavoidable:
In 16.6% of simulated faces, the runner-up displays a higher percentage than the winner. The average size of that inversion is 1.9 percentage points.
It is real, it is on screen, and no reasonable reader would guess the cause. The alternatives were to show the adjusted number — which would be a number that means nothing outside this page — or to rank by the raw distance and accept the deer collapse. I chose the visible oddity over the invisible bias.
06How much should you trust the answer
Landmark positions move between photos of the same person — different lighting, expression, angle. To put a number on what that does, I perturbed each percentile with Gaussian noise and measured how often the winner changed.
| Percentile jitter (σ) | Top answer changes | Falls out of the original top 3 |
|---|---|---|
| ±2 points | 6.9% | 0.1% |
| ±5 points | 16.0% | 0.8% |
| ±10 points | 29.3% | 4.7% |
The honest reading: the top three are stable, the single winner is not. At a jitter that is easy to imagine between two photos of the same person, the headline answer changes one time in six, while the correct answer stays inside the displayed shortlist better than 99 times in 100. That is why the page shows four runners-up instead of one verdict.
And the percentage itself
The displayed number is an exponential decay of the distance, clamped:
Across 20,000 simulated faces the winner's percentage ran from 46.8% to 94.5%, median 68.4%, with the middle half between 63.9% and 73.2%. Neither clamp ever fired — 0.0% of runs hit either bound, so those two constants are dead code that survives only as a guard against a pathological input. The number is a monotone restatement of the distance dressed up to read like a score; it is not a probability of anything.
07What is still wrong with it
Four things I know about and have not fixed.
The human reference set is stored as six sorted lists, not 34 people. Only the marginal distribution of each feature survives; the pairing between features is gone. The balance correction rebuilds "34 people" out of those lists, which produces 34 profiles that sit on the diagonal of the percentile cube — every feature at the same percentile. Real faces are not like that. Rerunning the collapse measurement against that diagonal population instead of the independent one puts deer at 47.1% rather than 21.7%, so the diagonal exaggerates the problem it is used to correct. The correction is calibrated against a population that does not exist. It still helps, and the ordering it produces is right, but the constant it produces is not trustworthy to two decimal places.
34 humans and 12 animals are small. A percentile computed against 34 values moves in steps of about three points, which is the same order as the jitter that flips one answer in six.
The animal references are hand-labelled from photographs. One representative image per species, landmarked by hand. "The average deer face" is doing a lot of work in that sentence, and it is not doing it very well.
There is no ground truth. There is no correct answer to "which animal does this face resemble", so nothing here is an accuracy figure. Everything measured in this write-up is a property of the algorithm — how it distributes its answers, how stable they are — not evidence that the answers are right. I think that distinction is the most important sentence on this page.
08Why write this down
The first version of this page worked. It loaded a model, found a face, computed six numbers and printed an animal with a confident percentage next to it. Nobody using it would have known that one answer in five was the same animal, or that two of the twelve were unreachable. The failure was completely invisible from the outside, and it was only found because the output looked boring rather than wrong.
That is the part worth keeping: the bug was not in the code, it was in the assumption that a distance in a space I had built was a distance that meant something. The fix was not a better algorithm but a measurement of the one I had — and then a second measurement of what the fix itself cost.
Reproducing the numbers
Every figure above comes from a harness that imports the exact constants shipped on the live page — the twelve animal profiles, the six sorted human lists, the weights — and re-implements the matcher around them, so it cannot drift from what the page actually does. Populations are seeded, N = 20,000 unless stated. If a constant on the page changes, the harness reruns and this write-up is wrong until it is updated.
Try it. The photo is processed entirely in your browser; nothing is uploaded and nothing is stored.
Open the Animal Lookalike →