DEV Community

Cover image for Face Comparison API: Match and Verify Two Faces in Python
AI Engine
AI Engine

Posted on Edited on Originally published at ai-engine.net

Face Comparison API: Match and Verify Two Faces in Python

If your app has to confirm that the person holding the phone is the person on the ID card, or that today's check-in selfie matches the photo enrolled last month, you need a face comparison API: send two images, get back whether they show the same person. Here is the one-call version with the Face Analyzer API, how to read the response, and a verify function that handles the cases naive integrations miss.

Want to try it on your own photos? Get a key on the Face Analyzer API page and run the snippets below.

Compare Two Faces in One Call

/compare-faces takes a source_image (the reference face, e.g. the ID photo) and a target_image (the photo to check, e.g. the selfie) as multipart uploads.

curl -X POST "https://faceanalyzer-ai.p.rapidapi.com/compare-faces" \
  -H "x-rapidapi-key: YOUR_API_KEY" \
  -H "x-rapidapi-host: faceanalyzer-ai.p.rapidapi.com" \
  -F "source_image=@id_photo.jpg" \
  -F "target_image=@selfie.jpg"
Enter fullscreen mode Exit fullscreen mode
import requests

URL = "https://faceanalyzer-ai.p.rapidapi.com/compare-faces"
HEADERS = {
    "x-rapidapi-host": "faceanalyzer-ai.p.rapidapi.com",
    "x-rapidapi-key": "YOUR_API_KEY",
}

with open("id_photo.jpg", "rb") as src, open("selfie.jpg", "rb") as tgt:
    response = requests.post(
        URL,
        headers=HEADERS,
        files={"source_image": src, "target_image": tgt},
    )

body = response.json()["body"]
print(len(body["matchedFaces"]), "matched,", len(body["unmatchedFaces"]), "unmatched")
# 1 matched, 0 unmatched
Enter fullscreen mode Exit fullscreen mode

Photos already online? Send source_image_url and target_image_url as form fields (form-urlencoded or multipart; a JSON body is rejected).

Reading the Response

Each face found in the target lands in matchedFaces (same person as the source) or unmatchedFaces (anyone else), with a normalized boundingBox and eye, nose and mouth landmarks. A real result on a target photo with two people:

Face comparison API response on a target photo with two women: matching face in green, the other in red

Two things to know:

  • The endpoint makes the match decision for you: no threshold to tune.
  • No usable face in either image returns two empty lists: check both and ask for a new photo.

So handle four outcomes:

  • 1 matched, 0 unmatched: same person, accept
  • 0 matched, 1 unmatched: different person, reject or review
  • 0 and 0: no usable face, ask for a new photo
  • more than one face in total: ask for a solo photo

A Verify Function You Can Ship

def verify(source_path, target_path):
    with open(source_path, "rb") as src, open(target_path, "rb") as tgt:
        response = requests.post(
            URL,
            headers=HEADERS,
            files={"source_image": src, "target_image": tgt},
        )
    body = response.json()["body"]
    matched = len(body["matchedFaces"])
    unmatched = len(body["unmatchedFaces"])

    if matched == 0 and unmatched == 0:
        return "NO_FACE"         # no usable face found: ask for a new photo
    if matched + unmatched > 1:
        return "MULTIPLE_FACES"  # a selfie should show exactly one person
    return "MATCH" if matched == 1 else "NO_MATCH"

print(verify("id_photo.jpg", "selfie.jpg"))       # MATCH
print(verify("id_photo.jpg", "stranger.jpg"))     # NO_MATCH
print(verify("id_photo.jpg", "landscape.jpg"))    # NO_FACE
print(verify("id_photo.jpg", "two_people.jpg"))   # MULTIPLE_FACES
Enter fullscreen mode Exit fullscreen mode

Those are the real outputs on our test photos. Keep the source to one face too: with two people in the source, the API compared using the larger face.

The full guide has the JavaScript version and the annotated results.

Need a Similarity Score? Use 1:N Search

For access control, check-in or attendance you have one new photo and many enrolled people. Enroll each person in a facial repository, then search: the response includes a Similarity from 0 to 100.

BASE = "https://faceanalyzer-ai.p.rapidapi.com"

requests.post(f"{BASE}/create-facial-repository", headers=HEADERS,
              data={"repository_id": "office-access"})

with open("id_photo.jpg", "rb") as f:
    requests.post(
        f"{BASE}/save-face-in-repository",
        headers=HEADERS,
        files={"image": f},
        data={"repository_id": "office-access", "external_id": "employee-001", "max_faces": "1"},
    )

with open("selfie.jpg", "rb") as f:
    response = requests.post(
        f"{BASE}/search-face-in-repository",
        headers=HEADERS,
        files={"image": f},
        data={"repository_id": "office-access"},
    )

matches = response.json()["body"]["FaceMatches"]
if matches and matches[0]["Similarity"] >= 90:
    print("ACCESS GRANTED:", matches[0]["ExternalImageId"], round(matches[0]["Similarity"], 2))
else:
    print("ACCESS DENIED")
# ACCESS GRANTED: employee-001 99.96
Enter fullscreen mode Exit fullscreen mode

Someone never enrolled returns an empty FaceMatches list.

Use Cases

  • KYC: selfie vs the portrait cropped from an ID document
  • Access control and event check-in: search camera captures against enrolled people
  • Attendance: a daily 1:N search stops buddy punching
  • Duplicate accounts: search new signup selfies against existing users
  • Exam proctoring: re-check the student at random intervals

Production Tips

  • Several faces in the frame make the result ambiguous: reject for verification
  • ID portraits are small: crop tightly and use the best scan available
  • Front-facing, evenly lit selfies without heavy sunglasses give the cleanest match
  • Pair identity with liveness: for onboarding, payments or door access, add a liveness step
  • Faces are biometric data: consent, documented retention, deletion on request

👉 Get your key on the Face Analyzer API page and run verify() on your own ID photo and selfie.

Top comments (0)