Проверка персоны на живость с помощью ML Kit
Это копия проверки селфи пользователей Persona с использованием ML Kit на Android.
Так недавно мне довелось использовать Persona для одного из проектов. Я был действительно заинтригован тем, как они справляются с разделом «Захватите свое селфи». По сути, они просят вас взять камеру телефона и выполнить определенный набор действий, чтобы убедиться, что человек на другом конце провода настоящий, а не изображение (защита от мошенничества).
И, конечно же, будучи технарем, я не смог устоять перед желанием воспроизвести то же самое. Я использовал Android с ML Kit, но уверен, что его можно воспроизвести и на других платформах. Вот как выглядит окончательный результат,
Шаг 1: Определение стека
Как только я испытал способ съемки селфи с помощью Persona, я начал исследовать, как этого можно добиться. Конечно, обнаружение человеческого лица и его жестов было ключом к тому, чтобы мой поиск в Google имел направление для начала! И Google, будучи королем поиска, выдал мне то, что мне было нужно, на странице 1. В основном мы будем использовать для этого ML Kit Face Detection , поскольку он обеспечивает угол поворота и ограничивающую рамку, которые в основном используются для достижения желаемого результата.
Шаг 2. Настройка кода Android
Код состоит из следующих основных компонентов:
- FacialScanView, т. е. пользовательская ViewGroup для обработки взаимодействия с пользовательским интерфейсом и наложения камеры. Он расширяет класс ViewGroup и отвечает за рендеринг вывода камеры и предоставление визуальных очередей пользователю.
- FaceOverlayView — настраиваемое представление для информирования пользователя о прогрессе, сделанном динамически на основе жестов лица пользователя.
- FaceDetectioHandler для обнаружения лица и жестов и содержит вспомогательные методы для вызова, если определенное действие было выполнено успешно.
Примечание. Прежде чем люди начнут ругать меня за использование Java для Android, в моей поддержке мои знания Kotlin находятся в стадии разработки.
Наиболее важной частью кода обнаружения является проверка того, было ли выполнено определенное действие. Действия, для которых я написал поддержку, следующие:
- Center Face — Выровняйте свое лицо в отмеченной области.
- Поверните влево
- Повернуть вправо
- Смотри сверху
- Смотри снизу
- мигать
- Улыбка
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;
}
}
}
}