Persona como verificação de vivacidade usando ML Kit

Dec 05 2022
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.

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;
                    }
                }
            }
        }