Skip to content
Document DB v12 - Improved Interceptors with Soft Delete Integration, AI protections, & Admin UI with Aspire Integration! How!?

Enrollment

Enrollment stores one embedding per call. Enroll a person several times and you build a gallery of templates for that identity; recognition takes the nearest across all of them.

// You already have the face box (live frame analyzer, or your own detector).
Task<Person> Enroll(string personIdentifier, byte[] imageData, FaceBox face, CancellationToken ct = default);
// You have a raw still and no box. Runs the detector and applies the quality gates.
Task<Person> Enroll(string personIdentifier, byte[] imageData, bool allowDuplicate = false, CancellationToken ct = default);

Use the box overload when a detector has already run — that’s what the MAUI controls do, and it guarantees the crop matches recognition byte for byte. Use the no-box overload for a still photo, a server-side batch, or an upload endpoint.

var person = await faces.Enroll("emp-4471", jpegBytes, box);
// person.Id — the document id of this one shot
// person.Thumbnail — 128px JPEG crop you can show in a list
// person.EnrolledAt — timestamp

The no-box overload is the one that can say “no”. It runs the registered IFaceDetector and enforces FaceIntelligenceOptions:

OptionDefaultRejects with
MinDetectionConfidence0.6FaceDetectionError.LowConfidence
MinFaceSizeFraction0.08FaceDetectionError.TooSmall
RejectMultipleFacestrueFaceDetectionError.MultipleFaces
FaceDetectionError.NoFace when nothing was found
try
{
await faces.Enroll(employeeId, photo);
}
catch (FaceDetectionException ex)
{
var message = ex.Reason switch
{
FaceDetectionError.NoFace => "No face found — face the camera.",
FaceDetectionError.LowConfidence => "Couldn't see the face clearly.",
FaceDetectionError.MultipleFaces => "More than one person in frame.",
FaceDetectionError.TooSmall => "Move closer.",
_ => ex.Message
};
}

MinFaceSizeFraction is measured as the face box’s shorter side over the image’s shorter side, so 0.08 means “the face fills at least 8% of the frame”. Set it to 0 to disable the check.

With GateEnrollmentOnRecognition on (the default), the no-box Enroll runs a recognition pass before storing. If the face already matches a different identity within MaxDistance, it throws instead of silently creating a second identity for the same person:

try
{
await faces.Enroll("emp-4471", photo);
}
catch (FaceEnrollmentConflictException ex)
{
// ex.Match.PersonIdentifier — who it looks like
// ex.Match.Distance — how close
if (await ConfirmWithUser($"This looks like {ex.Match.PersonIdentifier}. Enroll anyway?"))
await faces.Enroll("emp-4471", photo, allowDuplicate: true);
}

Enrolling a second shot of the same identity is never a conflict — the check only fires for a different one.

The single biggest lever on recognition accuracy is the enrolled gallery, not the threshold.

  • Several shots per person. Recognition takes the nearest across all of them, so more good templates means better recall on a bad day.
  • Vary the shot. Different angle, different lighting, glasses on and off. Six near-identical front-on shots are barely better than one — they all fail in the same conditions.
  • Quality over quantity. A blurry or badly lit shot becomes a permanently weak template that drags every future comparison. Reject it at capture time rather than storing it.
  • Enroll and recognize through the same path. This one is not a nicety. See below.

FaceEnrollmentView runs a multi-step wizard that enforces variety by measuring it — see MAUI Controls. It stores each accepted shot immediately, so an unsatisfiable step (a “move back” prompt a phone at arm’s length can’t reach) doesn’t discard the shots already captured.

var removed = await faces.Forget("emp-4471"); // every stored shot for that identity

There is no per-shot delete on IFaceIntelligence. Enumerate with GetAll() if you need to show what’s stored; each entry carries Id, PersonIdentifier, Thumbnail and EnrolledAt (but not the embedding — it lives only in the vector table).