Persona como verificação de vivacidade usando ML Kit
Esta é uma réplica da verificação de vivacidade da selfie do usuário do Persona usando o ML Kit no Android.
Então, recentemente, usei o Persona para um dos projetos. Fiquei realmente intrigado com a forma como eles lidam com a seção “Capture your Selfie”. Basicamente, eles pedem que você segure a câmera do telefone e execute um determinado conjunto de ações para verificar se a pessoa do outro lado é real e não uma imagem (à prova de fraude).
E claro sendo um techie que sou, não resisti à vontade de replicar o mesmo. Usei Android com ML Kit, mas tenho certeza que pode ser replicado em outras plataformas também. Aqui está a aparência da saída final,
Etapa 1: identificando a pilha
Assim que experimentei a maneira Persona de capturar selfie, comecei a pesquisar como isso pode ser feito. É claro que detectar um rosto humano e seus gestos foram fundamentais para que minha pesquisa no Google tenha uma direção para começar! E o Google, sendo o rei da pesquisa, me deu o que eu precisava na página 1. Basicamente, usaremos a detecção de rosto do ML Kit para isso, pois fornece o ângulo de rotação e a caixa delimitadora que é usada principalmente para obter o resultado desejado.
Etapa 2: configurando seu código Android
O código tem os seguintes componentes principais,
- FacialScanView, ou seja, um ViewGroup personalizado para lidar com as interações da interface do usuário e a sobreposição da câmera. Ele estende a classe ViewGroup e é responsável por renderizar a saída da câmera e fornecer filas visuais ao usuário.
- FaceOverlayView uma visualização personalizada para o usuário íntimo do progresso feito dinamicamente com base nos gestos faciais do usuário.
- FaceDetectioHandler para detectar rostos e gestos e conter métodos de suporte para callout se uma ação específica foi executada com sucesso.
Nota: Antes que as pessoas comecem a me xingar por usar Java para Android, em meu apoio, meu conhecimento de Kotlin é um trabalho em andamento.
A parte mais importante do código de detecção é verificar se uma determinada ação foi executada. As ações para as quais escrevi o suporte são as seguintes,
- Face central — Alinhe sua face dentro da área marcada
- Vire à esquerda
- Vire à direita
- Look Top
- olhar para baixo
- Piscar
- Sorriso
public void updateFacialFeatures(FacialFeatures facialFeatures, RectF faceRect) {
if(!isDetectionComplete) {
this.facialFeatures = facialFeatures;
switch (detectionType) {
case TOP:
if (facialFeatures.getRotationZ() < TOP_ANGLE)
progress = 100;
else if(facialFeatures.getRotationZ() > 0)
progress = 0;
else if (facialFeatures.getRotationZ() > TOP_ANGLE && facialFeatures.getRotationZ() < 0)
progress = -(int) ((facialFeatures.getRotationZ() / (3)) * 100);
break;
case BOTTOM:
if (facialFeatures.getRotationZ() > BOTTOM_ANGLE)
progress = 100;
else if(facialFeatures.getRotationZ() < 0)
progress = 0;
else if (facialFeatures.getRotationZ() < BOTTOM_ANGLE && facialFeatures.getRotationZ() > 0)
progress = (int) ((facialFeatures.getRotationZ() / (3)) * 100);
break;
case LEFT:
if (facialFeatures.getRotationY() > LEFT_ANGLE)
progress = 100;
else if(facialFeatures.getRotationY() < 0)
progress = 0;
else if (facialFeatures.getRotationY() < LEFT_ANGLE && facialFeatures.getRotationY() > 0)
progress = (int) ((facialFeatures.getRotationY() / (30)) * 100);
break;
case RIGHT:
if (facialFeatures.getRotationY() < RIGHT_ANGLE)
progress = 100;
else if(facialFeatures.getRotationY() > 0)
progress = 0;
else if (facialFeatures.getRotationY() > RIGHT_ANGLE && facialFeatures.getRotationY() < 0)
progress = (int) ((facialFeatures.getRotationY() / (30)) * 100);
break;
case BLINK:
if (!facialFeatures.isLeftEyeOpen() && !facialFeatures.isRightEyeOpen())
progress = 100;
break;
case SMILE:
if (facialFeatures.isSmiling())
progress = 100;
break;
case READ_TEXT:
//TODO
break;
case CENTER_FACE:
if (facialFeatures.isAllLandmarksDetected() && faceRect.contains(new RectF(facialFeatures.getFaceBounds())))
progress = 100;
else
progress= 0;
break;
}
if (progress == 100) {
endTime = System.currentTimeMillis();
isDetectionComplete = true;
detectionCallback.onCompletion(id, detectionType, getTimeTakenToPerformGesture(endTime));
} else
detectionCallback.onProgress(id, detectionType, progress, getTimeTakenToPerformGesture(System.currentTimeMillis()));
} else
detectionCallback.onCompletion(id, detectionType, getTimeTakenToPerformGesture(endTime));
}
faceDetectionHandler = new FaceDetectionHandler(this);
cameraView.setFacing(Facing.FRONT);
cameraView.addFrameProcessor(new FrameProcessor() {
@Override
public void process(Frame frame) {
faceDetectionHandler.detectFacesIn(frame);
}
});
@Override
public void onFaceDetected(FacialFeatures facialFeatures) {
//Log.i(TAG, facialFeatures.toString());
if (stepComplete) {
if(currentCount == 0)
faceOverlayView.addGesture(1, facialFeatures, DetectionType.CENTER_FACE);
if(currentCount == 1)
faceOverlayView.addGesture(2, facialFeatures, DetectionType.RIGHT);
if(currentCount == 2)
faceOverlayView.addGesture(3, facialFeatures, DetectionType.LEFT);
stepComplete = false;
} else {
faceOverlayView.updateFacialFeature(facialFeatures);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if(canDraw && !detectionComplete) {
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.BLACK);
paint.setAlpha(150);
//canvas.drawRect(overlayRect, paint);
//canvas.drawPath(mPath, paint);
if(detectionType != null) {
switch (detectionType) {
case TOP:
progressPaint.setColor(Color.WHITE);
canvas.drawLine(getWidth() / 2, startY, getWidth() / 2, endY, progressPaint);
canvas.drawLine(getWidth() / 2, startY, (getWidth() / 2) + 70, startY + 50, progressPaint);
canvas.drawLine(getWidth() / 2, startY, (getWidth() / 2) - 70, startY + 50, progressPaint);
progressPaint.setColor(Color.parseColor("#FF5DCFC1"));
canvas.drawLine(getWidth() / 2, endY, getWidth() / 2, endY - (gestureProgress * 2), progressPaint);
break;
case LEFT:
progressPaint.setColor(Color.WHITE);
canvas.drawLine(startX, getHeight() / 2, endX, getHeight() / 2, progressPaint);
canvas.drawLine(startX, getHeight() / 2, startX + 50, (getHeight() / 2) - 70, progressPaint);
canvas.drawLine(startX, getHeight() / 2, startX + 50, (getHeight() / 2) + 70, progressPaint);
progressPaint.setColor(Color.parseColor("#FF5DCFC1"));
canvas.drawLine(endX, getHeight() / 2, endX - (gestureProgress * 2), getHeight() / 2, progressPaint);
break;
case RIGHT:
progressPaint.setColor(Color.WHITE);
canvas.drawLine(startX, getHeight() / 2, endX, getHeight() / 2, progressPaint);
canvas.drawLine(endX, getHeight() / 2, endX - 50, (getHeight() / 2) - 70, progressPaint);
canvas.drawLine(endX, getHeight() / 2, endX - 50, (getHeight() / 2) +70, progressPaint);
progressPaint.setColor(Color.parseColor("#FF5DCFC1"));
canvas.drawLine(startX, getHeight() / 2, startX - (gestureProgress * 2), getHeight() / 2, progressPaint);
break;
case BOTTOM:
progressPaint.setColor(Color.WHITE);
canvas.drawLine(getWidth() / 2, startY, getWidth() / 2, endY, progressPaint);
canvas.drawLine(getWidth() / 2, endY, (getWidth() / 2) + 70, endY - 50, progressPaint);
canvas.drawLine(getWidth() / 2, endY, (getWidth() / 2) - 70, endY - 50, progressPaint);
progressPaint.setColor(Color.parseColor("#FF5DCFC1"));
canvas.drawLine(getWidth() / 2, startY, getWidth() / 2, startY + (gestureProgress * 2), progressPaint);
break;
case CENTER_FACE:
canvas.drawOval(faceContainer, facePaint);
break;
}
}
}
}