DEV Community

Cover image for Age Detection API in Python: Estimate Age from a Face Photo
AI Engine
AI Engine

Posted on Originally published at ai-engine.net

Age Detection API in Python: Estimate Age from a Face Photo

An age detection API estimates how old a person is from a photo of their face. You send an image, and you get back an age range for every face in it. This tutorial calls one from Python, then uses the result to label a group photo, bucket ages for analytics, and flag possible minors. We tested every script on public domain photos of people whose age is known.

Want to try it on your own photos? Get a key for the Face Analyzer API.


Results on photos with known ages

The two children were photographed by Lewis Hine, who wrote their age in his captions. The third photo is John Glenn's official NASA portrait from April 1998, taken when he was 76.

Photo Real age API age range Midpoint
Luigi, newsboy, Sacramento, 1915 6 6 to 12 9
Addie Card, mill spinner, Vermont, 1910 12 9 to 15 12
John Glenn, NASA portrait, 1998 76 73 to 79 76

All three real ages fall inside the returned range, and the midpoint is exact for two of them. Each request took about one second.


Detect age in Python

You need Python 3 with requests and a RapidAPI key in the RAPIDAPI_KEY environment variable. Save this as age_api.py:

import os
import requests

API_URL = "https://faceanalyzer-ai.p.rapidapi.com/faceanalysis"
HEADERS = {
    "x-rapidapi-host": "faceanalyzer-ai.p.rapidapi.com",
    "x-rapidapi-key": os.environ["RAPIDAPI_KEY"],
}


def detect_ages(path):
    """Return one age estimate per face found in the image."""
    with open(path, "rb") as f:
        r = requests.post(API_URL, headers=HEADERS, files={"image": f}, timeout=30)
    r.raise_for_status()
    data = r.json()
    if data.get("statusCode") != 200:
        raise RuntimeError(data["body"].get("error", "unknown error"))

    results = []
    for face in data["body"]["faces"]:
        age = face["facialFeatures"]["AgeRange"]
        results.append({
            "low": age["Low"],
            "high": age["High"],
            "estimate": (age["Low"] + age["High"]) / 2,
            "box": face["boundingBox"],
        })
    # Sort faces left to right so the output order is stable
    return sorted(results, key=lambda f: f["box"]["topLeft"]["x"])


if __name__ == "__main__":
    for face in detect_ages("glenn.jpg"):
        print(f"Age {face['low']}-{face['high']} (estimate {face['estimate']:.0f})")
Enter fullscreen mode Exit fullscreen mode

On the John Glenn portrait it prints:

Age 73-79 (estimate 76)
Enter fullscreen mode Exit fullscreen mode

An image with no face returns an empty list rather than an error, so "no face" is a normal outcome.

What the response contains: facialFeatures.AgeRange holds Low and High in whole years. The midpoint is the best single number for charts and averages. boundingBox uses coordinates between 0 and 1, relative to the image size. The same object also returns gender, smile, glasses and emotions.

One detail: when a request fails (for example an image URL the API cannot download), the HTTP status is still 200 and the error is in statusCode and body.error. That is why the function checks statusCode and does not rely on raise_for_status alone.


Estimate the age of everyone in a group photo

Each face comes with its box, so you can draw the age range above every person (pip install pillow):

from PIL import Image, ImageDraw, ImageFont
from age_api import detect_ages


def annotate(path, out_path):
    img = Image.open(path).convert("RGB")
    w, h = img.size
    draw = ImageDraw.Draw(img)
    font = ImageFont.truetype("DejaVuSans-Bold.ttf", max(18, w // 40))

    for face in detect_ages(path):
        tl, br = face["box"]["topLeft"], face["box"]["bottomRight"]
        box = (tl["x"] * w, tl["y"] * h, br["x"] * w, br["y"] * h)
        draw.rectangle(box, outline=(124, 58, 237), width=max(3, w // 300))
        label = f"{face['low']}-{face['high']}"
        x, y = box[0], max(0, box[1] - font.size - 12)
        tw = draw.textlength(label, font=font)
        draw.rectangle((x, y, x + tw + 16, y + font.size + 10), fill=(124, 58, 237))
        draw.text((x + 8, y + 4), label, fill="white", font=font)

    img.save(out_path, quality=90)


annotate("artemis.jpg", "artemis_ages.jpg")
Enter fullscreen mode Exit fullscreen mode

On NASA's official Artemis II crew portrait, the API found all four faces in one request:

The four Artemis II astronauts, each face framed by a purple box with the estimated age range above it


Batch a folder and bucket ages for analytics

This script scans a folder, writes one CSV row per face and counts faces per age bracket:

import csv
from collections import Counter
from pathlib import Path
from age_api import detect_ages

BRACKETS = [(0, 12, "0-12"), (13, 17, "13-17"), (18, 24, "18-24"),
            (25, 34, "25-34"), (35, 44, "35-44"), (45, 54, "45-54"),
            (55, 64, "55-64"), (65, 200, "65+")]


def bracket(age):
    for low, high, name in BRACKETS:
        if low <= age <= high:
            return name


def scan_folder(folder, csv_path):
    counts = Counter()
    with open(csv_path, "w", newline="") as f:
        writer = csv.writer(f)
        writer.writerow(["file", "face", "low", "high", "estimate", "bracket"])
        for path in sorted(Path(folder).glob("*.jpg")):
            for i, face in enumerate(detect_ages(path)):
                b = bracket(round(face["estimate"]))
                counts[b] += 1
                writer.writerow([path.name, i, face["low"], face["high"],
                                 face["estimate"], b])
    return counts


counts = scan_folder("photos", "ages.csv")
for name in [b[2] for b in BRACKETS]:
    print(f"{name:>6}: {counts[name]}")
Enter fullscreen mode Exit fullscreen mode

On our four test photos (seven faces), it prints:

  0-12: 2
 13-17: 0
 18-24: 0
 25-34: 0
 35-44: 4
 45-54: 0
 55-64: 0
   65+: 1
Enter fullscreen mode Exit fullscreen mode

For analytics, the midpoint is the right input: errors in both directions cancel out over many faces. Running the same photos twice returned the same numbers.


Flag possible minors

For safety decisions, use the Low value and a buffer zone where you ask for more proof:

from age_api import detect_ages

ADULT_AGE = 18
BUFFER = 7  # treat anyone who might be under 25 as "check further"


def classify(path):
    faces = detect_ages(path)
    if not faces:
        return "no_face"
    youngest = min(f["low"] for f in faces)
    if youngest < ADULT_AGE:
        return "possible_minor"
    if youngest < ADULT_AGE + BUFFER:
        return "verify_id"
    return "adult"


for name in ["luigi.jpg", "addie.jpg", "glenn.jpg", "artemis.jpg", "noface.jpg"]:
    print(name, classify(name))
Enter fullscreen mode Exit fullscreen mode
luigi.jpg possible_minor
addie.jpg possible_minor
glenn.jpg adult
artemis.jpg adult
noface.jpg no_face
Enter fullscreen mode Exit fullscreen mode

The rule looks at the youngest face, so one child in a group picture is enough to route it for review. For a complete age gate with Flask, consent and data retention, see the face-based age verification tutorial. To choose a provider for regulated checks, see the comparison of age verification APIs.


Production tips

  • Upload the file when you can. The API also accepts a public image URL, but some hosts block server-side downloads. A Wikimedia Commons URL returned "Could not load image", while the same photo uploaded as a file worked.
  • Use ranges for decisions, midpoints for statistics. The API estimates apparent age. Studio lighting, makeup and retouching can make an adult look younger, which is another reason to decide on Low with a buffer.
  • Give the face enough pixels. In the Luigi photo the face covers less than 10% of the image width, and the API still found it.
  • Store the result, not the photo. Age is personal data in many jurisdictions: save the numbers, delete the image, and tell users what you process.

Full tutorial with images: Age Detection API in Python on ai-engine.net.


Sources

Try it on your own photos: grab a key from the Face Analyzer API page and run age_api.py.

Top comments (0)