cd /news/computer-vision/fixing-there-are-no-faces-in-the-ima… · home › topics › computer-vision › article
[ARTICLE · art-161325] src=dev.to ↗ pub= topic=computer-vision verified=true sentiment=· neutral

Fixing 'There are no faces in the image' in Rekognition SearchFacesByImage

A developer documented the root cause of the `InvalidParameterException: There are no faces in the image` error from Amazon Rekognition's SearchFacesByImage, explaining that the API detects only the single largest face in the input image before searching the collection. The writeup attributes most failures to faces that are small relative to the frame, over-tight client-side crops, extreme head poses, and poor lighting or sharpness, and recommends calling DetectFaces on the same bytes to diagnose, cropping group shots with a margin, and catching the exception to return a friendly retry message instead of a 500.

by read7 min views1 publishedOct 31, 2026

Editor note for Ashish (delete before publishing): this draft was written with AI. Rewrite it in your own voice and check every claim. If any AI-written text remains, add a line saying the post was written with AI assistance (DEV.to and Medium both require this).

TL;DR SearchFacesByImage first detects the largest face in your input image. If it finds none, it fails with InvalidParameterException: There are no faces in the image. Should be at least 1. The usual causes are a face that is small relative to the frame, a selfie cropped too tightly, an extreme head pose, or a dark or blurry photo. Call DetectFaces on the same bytes to see which one you hit, crop group shots around the face with a margin, and catch the exception so your user gets "try another photo" instead of a 500.

This is the error every selfie-search feature runs into in its first week of real traffic. Test photos work. Then real users upload a photo of themselves from across the room at a wedding, or a selfie their camera app already cropped to the face, and the call fails before any searching happens.

botocore.errorfactory.InvalidParameterException: An error occurred (InvalidParameterException)
when calling the SearchFacesByImage operation: There are no faces in the image. Should be at least 1.

InvalidParameterException is a generic "input violated a constraint" error with HTTP status 400, so the message text is the only thing that tells you it was face detection that failed and not, say, a bad CollectionId.

Per the API reference, the operation runs detection first, takes the single largest face it found, and only then searches the collection with it. Two consequences matter:

IndexFaces and then SearchFaces by face ID.FaceMatches list for it. The quota page says a face must be at least 40×40 pixels in a 1920×1080 image, and that larger images need a proportionally larger face. The input-image recommendations give 50×50 at the same resolution. Either way, the limit is relative. A face that is 60 pixels tall in a 6000×4000 photo behaves like a face of about 16 pixels in a 1080p frame, far below the minimum.

This is why downscaling a large photo doesn't help: the face shrinks with it. Cropping does help, because it makes the face a larger share of the frame.

The opposite mistake. Some upload widgets and camera apps crop to the face before you ever see the image. AWS's recommendation is explicit: the image "should contain the full head and shoulders of the person. It should not be cropped to the face bounding box." If your client code crops, leave a margin of roughly one face-width on every side.

AWS recommends pitch under 30° looking down and under 45° looking up, and yaw under 45° either way. Roll (head tilt) has no restriction, so a sideways phone photo is rarely the cause on its own. Profile shots and people looking down at a phone are.

Night-time event photos with motion blur, heavy backlight, or a face in deep shadow. IndexFaces reports these as UnindexedFaces with reasons like LOW_BRIGHTNESS and LOW_SHARPNESS, and SearchFacesByImage simply fails to find a face.

Sunglasses, masks, a hand over the mouth, or a phone in front of the face during a mirror selfie. AWS asks for both eyes open and visible.

SearchFacesByImage defaults QualityFilter to NONE. If your code sets LOW, MEDIUM or HIGH, a borderline selfie can be filtered out before search. Set it back to NONE while debugging to see whether the filter or detection is what rejects the face.

Info: Not the same error: InvalidImageFormatException means the bytes aren't a PNG or JPEG. Rekognition accepts only those two formats, so iPhone HEIC uploads fail with it, and so does passing a base64 string to Image={"Bytes": ...} in boto3 (boto3 encodes the bytes for you). ImageTooLargeException means raw bytes over 5 MB; S3 objects can be up to 15 MB.

DetectFaces with Attributes=["ALL"] returns every face it sees with its bounding box, pose and quality scores. Run it on the exact bytes that failed. If it returns nothing, the problem is the image. If it returns a face, the numbers tell you which cause above you hit. Note that DetectFaces is a separately billed call, so use it for debugging and sampling, not on every request.

Run on the bytes that failed. Prints size relative to the 1080p minimum, pose and quality.

import boto3
from PIL import Image
import io

rek = boto3.client("rekognition", region_name="us-east-1")

def explain(image_bytes: bytes) -> None:
    w, h = Image.open(io.BytesIO(image_bytes)).size
    faces = rek.detect_faces(Image={"Bytes": image_bytes}, Attributes=["ALL"])["FaceDetails"]
    if not faces:
        print(f"{w}x{h}: no face detected at all")
        return
    for f in faces:
        box = f["BoundingBox"]
        face_px = min(box["Width"] * w, box["Height"] * h)
        at_1080p = face_px * 1080 / min(w, h)
        p, q = f["Pose"], f["Quality"]
        print(
            f"face ~{face_px:.0f}px ({at_1080p:.0f}px at 1080p, min ~40-50) "
            f"yaw={p['Yaw']:.0f} pitch={p['Pitch']:.0f} "
            f"brightness={q['Brightness']:.0f} sharpness={q['Sharpness']:.0f}"
        )

The error is a user-input problem, so treat it like a validation error. Catch it by code and message, and ask for another photo.

from botocore.exceptions import ClientError

def search_selfie(collection_id: str, image_bytes: bytes):
    try:
        return rek.search_faces_by_image(
            CollectionId=collection_id,
            Image={"Bytes": image_bytes},
            FaceMatchThreshold=90,
            MaxFaces=50,
        )["FaceMatches"]
    except ClientError as e:
        err = e.response["Error"]
        if err["Code"] == "InvalidParameterException" and "no faces" in err["Message"].lower():
            return None  # tell the user: "We couldn't see a face. Try a closer, well-lit photo."
        raise

ImageOps.exif_transpose plus a JPEG re-encode fixes both, and keeps you under the 5 MB bytes limit. Rotation, optional crop with margin, JPEG under the 5 MB bytes limit.

import io
from PIL import Image, ImageOps

def prepare(path: str, crop=None, max_side=2048) -> bytes:
    img = ImageOps.exif_transpose(Image.open(path)).convert("RGB")
    if crop:  # (left, top, width, height) as ratios, e.g. from a tap or a detector
        l, t, cw, ch = crop
        W, H = img.size
        mx, my = cw * W, ch * H  # one face-width of margin each side
        img = img.crop((
            max(0, l * W - mx), max(0, t * H - my),
            min(W, (l + cw) * W + mx), min(H, (t + ch) * H + my),
        ))
    img.thumbnail((max_side, max_side))
    buf = io.BytesIO()
    img.save(buf, "JPEG", quality=90)
    return buf.getvalue()

We build SightRadar, a face search API that keeps Rekognition's collection model (create a collection, index photos, search by selfie), so this is a problem we handle every day. Two differences are relevant here:

200 with an empty matches list and a reason of no_face or low_quality_selfie, so the "ask for another photo" branch is a field check, not an exception handler. Like any processed photo, the call is billed.

import os, requests

r = requests.post(
    f"https://api.sightradar.com/v1/collections/{collection_id}/search",
    headers={"Authorization": f"Bearer {os.environ['SIGHTRADAR_API_KEY']}"},
    files={"file": open("selfie.jpg", "rb")},
    data={"limit": "50"},
    timeout=30,
)
body = r.json()
if not body["matches"] and body.get("reason") in ("no_face", "low_quality_selfie"):
    print("Ask for another photo:", body["reason"])
for m in body["matches"]:
    print(m["photo_id"], round(m["score"], 3))

If you already have boto3 code, the Rekognition shim keeps your search_faces_by_image calls and converts the 0 to 100 threshold scale. The migration guide covers re-indexing and cut-over.

Try detection on your own problem photos. New accounts get free credits, no card. Open the face detector

Rekognition's minimum face size is relative to the image: about 40 to 50 pixels in a 1920x1080 frame, scaled up for larger images. A face that looks fine to you in a 24-megapixel group photo can be far below that. Tight crops, extreme head pose, darkness, blur and occlusion are the other common causes.

Downscaling does not, because the face shrinks in proportion. Cropping around the face does, because the face becomes a larger share of the frame. Keep a margin so the crop still shows head and shoulders.

SearchFacesByImage only searches the largest face. To search every face, call IndexFaces on the photo, then call SearchFaces with each returned FaceId.

InvalidParameterException with the message 'There are no faces in the image' means the image decoded but no face was detected. InvalidImageFormatException means the bytes are not a PNG or JPEG, for example an iPhone HEIC file or a base64 string passed where raw bytes are expected.

The face search API used in the examples. Originally published on the SightRadar blog.

── more in #computer-vision 4 stories · sorted by recency
── more on @amazon rekognition 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/fixing-there-are-no-…] indexed:0 read:7min 2026-10-31 · —