Quality Assessment — Guia de uso
Avaliação de qualidade da imagem facial no fluxo de enroll. O Fortface Core chama o troller (fortface-cortex), que analisa exposição, nitidez, uniformidade de iluminação e contraluz, e devolve se a foto está utilizável para enrollment.
Oclusão, pose, olhos fechados e múltiplas faces não fazem parte desta análise.
1. Como ativar
Basta pedir a análise na requisição. Não há configuração de galeria: qualquer galeria
aceita, e o que decide é o request. Se a flag não vier — ou vier false —, a análise não é
executada e o campo não aparece no payload de resposta.
{
"analysis": {
"qualityAssessment": true
}
}
No fluxo SDK a flag é enviada no handshake e vale para a sessão inteira (o body do enroll
SDK não tem analysis); no backoffice, vai no body do próprio enroll.
Thresholds não são configuráveis: são globais, definidos na policy do troller.
Regra no código
EnrollFaceAnalysisOperator:
qualityAssessmentValue === true
onde qualityAssessmentValue é session.analysis?.qualityAssessment no SDK e
analysis?.qualityAssessment no backoffice.
2. Onde se aplica
Apenas na operação enroll. Dois fluxos, ambos no EnrollController:
| Rota | Método | De onde vem o analysis |
|---|---|---|
SdkEnrollRoute | executeSdk | da sessão (handshake) |
BackofficeEnrollRoute | executeBackoffice | do body |
Nenhuma outra operação executa quality. capture, liveness, evidence, identify e
search v2 usam controllers próprios e nenhum deles referencia qualityAssessment.
A análise roda no Promise.all do EnrollFaceAnalysisOperator, junto de liveness,
oclusão e embeddings — portanto não soma latência serial ao enroll.
3. Retorno do Fortface
O bloco fica dentro de photo, ao lado de liveness:
{
"photo": {
"liveness": { "value": true, "confidence": 100 },
"qualityAssessment": { "value": true, "confidence": 100 }
}
}
| Campo | Tipo | Descrição |
|---|---|---|
value | boolean | true = foto utilizável; false = reprovada |
confidence | number | 100 = o cortex respondeu (aprovado, reprovado ou sem conseguir avaliar a imagem); 0 = o cortex não pôde ser acessado (timeout, indisponibilidade) |
Tabela de decisão
| Resposta do troller | value | confidence |
|---|---|---|
passed: true / decision: accepted | true | 100 |
passed: false + reason: rejected_but_debatable | true | 100 |
passed: false + reason: rejected | false | 100 |
status: 'error' (sem rosto, imagem inválida — o troller respondeu, só não conseguiu avaliar) | false | 100 |
| Falha de rede/timeout — o troller não pôde ser acessado | false | 0 |
| Feature desligada | (bloco qualityAssessment ausente do payload) | — |
A reprovação limítrofe (rejected_but_debatable) é aceita pelo Fortface. O troller
marca a imagem como fora dos limites, mas por margem pequena — a regra de negócio do
Fortface trata esse caso como utilizável para enrollment. Só a reprovação clara
(rejected, que estourou um gate do bloco hard_reject) devolve value: false.
O algoritmo do cortex não foi alterado: a decisão continua vindo do troller e o
payload cru é persistido no log de sessão (qualityAssessmentResponse) com scores,
thresholds, failed_metrics, decision e estimated_mst para triagem posterior.
O fluxo de enroll nunca é bloqueado pela análise de qualidade: value: false é
informativo, e a decisão de recapturar a foto é do integrador.
4. Referência do contrato
POST /v2/quality_assessment (troller)
- Request:
Content-Type: application/octet-stream, body = bytes crus da imagem. Não existe variante base64. - Response: sempre HTTP 200 — o endpoint nunca lança.
| Campo | Tipo | Observação |
|---|---|---|
passed | boolean | aprovação do troller; entra na regra do value |
status | "done" | "error" | error = o troller respondeu mas não conseguiu avaliar a imagem → value: false, confidence: 100 (ele foi acessado) |
reason | string | null | null quando aprovou; senão repete decision |
failed_metrics | string[] | métricas reprovadas, ou ["no_face"] |
scores | object | badness em [0,1] — maior = pior |
thresholds | object | limites aplicados; {} quando não avaliou |
estimated_mst | int | Monk Skin Tone estimado (calibração por tom de pele) |
decision | "accepted" | "rejected" | "rejected_but_debatable" | |
review_bucket | string | espelha decision |
Métricas
| Métrica | O que detecta | Threshold |
|---|---|---|
underexposure | foto escura demais | 0.75 |
overexposure | foto estourada de luz | 0.95 |
blur | falta de nitidez / desfoque | 0.85 |
uniformity | iluminação irregular no rosto | 0.98 |
backlit | rosto contra fundo muito claro | 0.45 |
Uma métrica falha quando seu score é maior ou igual ao threshold. passed é true
somente quando todas ficam abaixo dos seus limites.
decision
| Valor | Significado | value no Fortface |
|---|---|---|
accepted | passou em todos os thresholds | true |
rejected | reprovou e estourou um gate do bloco hard_reject — imagem claramente ruim | false |
rejected_but_debatable | reprovou, mas dentro dos gates — limítrofe | true |
Os gates ficam no bloco hard_reject da policy e são avaliados apenas sobre features da
imagem (luminância mediana, faixa dinâmica, caudas escura/clara, score de blur) — a mesma
imagem sempre cai no mesmo bucket.
5. Cenários
Cenário 1 — Aprovada
Resposta do troller:
{
"passed": true,
"status": "done",
"reason": null,
"failed_metrics": [],
"scores": { "underexposure": 0.0, "overexposure": 0.0, "blur": 0.0, "uniformity": 0.1258, "backlit": 0.0 },
"estimated_mst": 6,
"decision": "accepted",
"review_bucket": "accepted"
}
Retorno do Fortface:
{ "photo": { "qualityAssessment": { "value": true, "confidence": 100 } } }
Cenário 2 — Reprovação limítrofe (aceita)
Resposta do troller:
{
"passed": false,
"status": "done",
"reason": "rejected_but_debatable",
"failed_metrics": ["blur"],
"scores": { "underexposure": 0.0, "overexposure": 0.0, "blur": 0.9, "uniformity": 0.3792, "backlit": 0.0 },
"estimated_mst": 4,
"decision": "rejected_but_debatable",
"review_bucket": "rejected_but_debatable"
}
Retorno do Fortface:
{ "photo": { "qualityAssessment": { "value": true, "confidence": 100 } } }
blur: 0.9 estourou o threshold (0.85), mas ficou abaixo do gate duro (1.0) —
caso limítrofe, aceito pelo Fortface.
Cenário 3 — Reprovada
Resposta do troller:
{
"passed": false,
"status": "done",
"reason": "rejected",
"failed_metrics": ["blur"],
"scores": { "underexposure": 0.0, "overexposure": 0.0, "blur": 1.0, "uniformity": 0.4048, "backlit": 0.075 },
"estimated_mst": 9,
"decision": "rejected",
"review_bucket": "rejected"
}
Retorno do Fortface:
{ "photo": { "qualityAssessment": { "value": false, "confidence": 100 } } }
Cenário 4 — Troller respondeu, mas não conseguiu avaliar
Sem rosto detectado, imagem inválida ou body vazio, a resposta do troller é HTTP 200
com status: "error":
{
"passed": false,
"status": "error",
"reason": "rejected",
"failed_metrics": ["no_face"],
"thresholds": {},
"estimated_mst": 0,
"decision": "rejected",
"review_bucket": "rejected"
}
Retorno do Fortface:
{ "photo": { "qualityAssessment": { "value": false, "confidence": 100 } } }
confidence: 100 porque o troller foi acessado e devolveu uma resposta — só não conseguiu
computar as métricas de qualidade para essa imagem. value: false porque reason não é
rejected_but_debatable.
No fluxo de enroll, uma imagem sem rosto normalmente nem chega a gerar esse retorno: a geração do embedding (face-service/troller, um passo anterior e independente) já rejeita a requisição com
400 invalid.face_not_foundantes da resposta ser montada. Esse cenário tende a aparecer só quando o troller falha em avaliar a imagem por um motivo que não impede a detecção de rosto no outro serviço.
Cenário 5 — Troller inacessível
Timeout, indisponibilidade ou qualquer falha de rede na chamada ao troller. Não há resposta para mapear — o adapter apenas não recebe nada do serviço.
Retorno do Fortface:
{ "photo": { "qualityAssessment": { "value": false, "confidence": 0 } } }
confidence: 0 é reservado exclusivamente para este caso: o troller não pôde ser acessado.
Qualquer resposta dele, mesmo status: "error", já conta como acesso bem-sucedido e usa
confidence: 100.