sdk

Manual Completo para Detección y Grabación Facial en Aplicaciones Android

Tiempo estimado: 45-60 minutos para configuración completa


¿Qué aprenderás en este manual?

Este manual te enseñará a implementar y usar el JAAKVisage SDK para detección facial automatizada y grabación de video en aplicaciones Android nativas. No necesitas conocimientos técnicos avanzados - solo sigue los pasos.


Antes de Empezar - Lista de Verificación

Asegúrate de tener estos elementos listos:

  • Android Studio Iguana instalado
  • Dispositivo Android 6.0+ (API 23+)
  • Gradle 8.4 configurado
  • Conocimientos básicos de Kotlin/Android
  • Acceso a cámara funcional

Índice de Contenidos

SecciónQué harásTiempo
Paso 1Configurar proyecto y dependencias10 min
Paso 2Implementación básica del SDK20 min
Paso 3Configurar permisos y manifiestos10 min
Paso 4Manejo de grabación y videos15 min
Paso 5Probar detección facial10 min

PASO 1: Configurar Proyecto y Dependencias

Objetivo

Configurar el entorno de desarrollo Android y añadir las dependencias necesarias del SDK.

Requisitos Técnicos

RequisitoVersión¿Obligatorio?
Android StudioIguana
minSdkVersion23 (Android 6.0+)
targetSdkVersion33
compileSdk35
Gradle8.4
Java/Kotlin18

1.1 Configuración build.gradle (Project)

buildscript {
    ext.kotlin_version = "1.9.22"
    ext.hilt_version = '2.46'
    repositories {
        google()
        mavenCentral()
        maven {
            url 'https://us-maven.pkg.dev/jaak-platform/jaak-android'
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:8.3.2'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
    }
}

1.2 Configuración build.gradle (Module: app)

plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-kapt'
    id 'dagger.hilt.android.plugin'
}

android {
    namespace 'com.jaak.visagesdk'
    compileSdk 35

    defaultConfig {
        minSdk 23
        targetSdk 33
        versionCode 1
        versionName "1.0"
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_18
        targetCompatibility JavaVersion.VERSION_18
    }

    buildFeatures {
        viewBinding = true
    }
}

dependencies {
    // Android Core
    implementation("androidx.core:core-ktx:1.15.0")
    implementation("androidx.appcompat:appcompat:1.7.0")
    implementation("androidx.constraintlayout:constraintlayout:2.2.0")
    implementation 'com.google.android.material:material:1.12.0'

    // Dependency Injection
    implementation("com.google.dagger:hilt-android:$hilt_version")
    kapt("com.google.dagger:hilt-android-compiler:$hilt_version")

    // JAAK Visage SDK
    implementation("com.jaak.visagesdk:jaakvisage-sdk:1.0.0-beta")
}

PASO 2: Implementación Básica del SDK

Objetivo

Crear la implementación base del JAAKVisage SDK con detección facial y grabación automática.

2.1 Application Class

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class JaakVisageSDKApp : Application()

2.2 MainActivity Básica

import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.jaak.visagesdk.databinding.ActivityMainBinding
import com.jaak.visagesdk.ui.adapter.VisageListener
import com.jaak.visagesdk.ui.view.VisageSDK
import com.jaak.visagesdk.utils.CameraFacing
import com.jaak.visagesdk.utils.Utils
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity(), VisageListener {

    private lateinit var binding: ActivityMainBinding
    private lateinit var visageSDK: VisageSDK

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        configureVisageSDK()

        binding.btnStartDetection.setOnClickListener {
            startFaceDetection()
        }
    }

    private fun configureVisageSDK() {
        visageSDK = VisageSDK(this, this)

        // Configuración recomendada para detección facial
        visageSDK.setCameraFacing(CameraFacing.FRONT) // Cámara frontal recomendada

        // Herramientas de desarrollo (opcional)
        visageSDK.setDebugMode(true)   // Métricas en tiempo real: FPS | MEM | FACE | CAM
        visageSDK.setConfigPanel(true) // Panel de configuración visible
    }

    private fun startFaceDetection() {
        // El SDK maneja automáticamente permisos de cámara
        visageSDK.startVisage() // Inicia detección facial y grabación automática
    }

    override fun onSuccessVisage(typeProcess: Int, uri: Uri?) {
        // typeProcess siempre es 2 (video con detección facial)
        uri?.let { videoUri ->
            val base64Video = Utils.videoCameraUriToBase64(videoUri) ?: ""

            Log.d("Visage", "Video capturado: $videoUri")
            Log.d("Visage", "Base64 length: ${base64Video.length}")

            if (base64Video.isNotEmpty()) {
                Toast.makeText(this, "Video facial procesado exitosamente", Toast.LENGTH_LONG).show()
                // Procesar video según necesidades
                processVideoForKYC(base64Video, videoUri)
            } else {
                Toast.makeText(this, "Error al procesar el video", Toast.LENGTH_SHORT).show()
            }
        }
    }

    override fun onErrorVisage(text: String) {
        Log.e("Visage", "Error: $text")

        val errorMessage = when {
            text.contains("permission", ignoreCase = true) -> "Permisos de cámara requeridos"
            text.contains("camera", ignoreCase = true) -> "Error de cámara - Verifica que no esté en uso"
            text.contains("cancelled", ignoreCase = true) -> "Proceso cancelado por el usuario"
            else -> "Error: $text"
        }

        Toast.makeText(this, errorMessage, Toast.LENGTH_LONG).show()
    }

    private fun processVideoForKYC(base64Video: String, originalUri: Uri) {
        // Crear payload para backend
        val kycData = mapOf(
            "facialVideo" to base64Video,
            "timestamp" to System.currentTimeMillis(),
            "source" to "visage_sdk_android",
            "videoUri" to originalUri.toString()
        )

        Log.d("Visage", "Procesando video para KYC: ${kycData.keys}")
        // Enviar a tu backend/API para análisis biométrico
    }
}

2.3 Layout XML (activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="JAAK Visage SDK Android"
        android:textSize="20sp"
        android:textStyle="bold"
        android:layout_marginBottom="32dp"
        app:layout_constraintBottom_toTopOf="@+id/btnStartDetection"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/btnStartDetection"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Iniciar Detección Facial"
        android:textSize="16sp"
        android:padding="16dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

PASO 3: Configurar Permisos y Manifiestos

Objetivo

Configurar correctamente los permisos y el manifiesto Android.

3.1 AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        tools:replace="android:maxSdkVersion"
        android:maxSdkVersion="28"/>

    <application
        android:name="com.jaak.visagesdk.JaakVisageSDKApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
        tools:replace="android:theme,android:name">

        <activity
            android:name="com.jaak.visagesdk.MainActivity"
            android:exported="true"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.jaak.visagesdk"
            android:exported="false"
            android:grantUriPermissions="true"
            tools:replace="android:authorities">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

    </application>
</manifest>

3.2 file_paths.xml

Crear archivo res/xml/file_paths.xml:

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images" path="Pictures/" />
    <external-path name="my_movies" path="Movies/" />
    <external-path name="my_files" path="Android/data/com.jaak.visagesdk/files/Jaak Visage SDK/" />
</paths>

PASO 4: Manejo de Grabación y Videos

Objetivo

Implementar el manejo completo de videos grabados y procesamiento avanzado.

4.1 Implementación Avanzada con Configuración

class AdvancedVisageActivity : AppCompatActivity(), VisageListener {

    private lateinit var binding: ActivityAdvancedBinding
    private lateinit var visageSDK: VisageSDK

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityAdvancedBinding.inflate(layoutInflater)
        setContentView(binding.root)

        setupAdvancedConfiguration()
    }

    private fun setupAdvancedConfiguration() {
        visageSDK = VisageSDK(this, this)

        // Configuración de cámara
        setupCameraSpinner()

        // Configurar botón principal
        binding.btnLaunchVisage.setOnClickListener {
            if (validateAndApplyConfiguration()) {
                visageSDK.startVisage()
            }
        }

        // Valores por defecto
        loadDefaultValues()
    }

    private fun setupCameraSpinner() {
        val cameraOptions = arrayOf("Cámara Frontal", "Cámara Trasera")
        val adapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, cameraOptions)
        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
        binding.spinnerCameraFacing.adapter = adapter
    }

    private fun loadDefaultValues() {
        binding.spinnerCameraFacing.setSelection(0) // Frontal por defecto
        binding.cbShowConfigPanel.isChecked = false
        binding.cbShowDebugMode.isChecked = false
    }

    private fun validateAndApplyConfiguration(): Boolean {
        try {
            // Configurar cámara
            val cameraFacing = if (binding.spinnerCameraFacing.selectedItemPosition == 0)
                CameraFacing.FRONT else CameraFacing.BACK

            // Aplicar configuración al SDK
            visageSDK.setCameraFacing(cameraFacing)
            visageSDK.setConfigPanel(binding.cbShowConfigPanel.isChecked)
            visageSDK.setDebugMode(binding.cbShowDebugMode.isChecked)

            return true
        } catch (e: Exception) {
            Toast.makeText(this, "Error en configuración: ${e.message}", Toast.LENGTH_SHORT).show()
            return false
        }
    }

    override fun onSuccessVisage(typeProcess: Int, uri: Uri?) {
        try {
            Log.d("AdvancedVisage", "Procesamiento exitoso - Tipo: $typeProcess")

            uri?.let { videoUri ->
                // Conversión optimizada a Base64 para videos de cámara
                val base64Video = Utils.videoCameraUriToBase64(videoUri)

                if (!base64Video.isNullOrEmpty()) {
                    Log.d("AdvancedVisage", "Video convertido: ${base64Video.length} caracteres")

                    // Procesar el video según necesidades
                    processCompleteVideo(base64Video, videoUri)

                    Toast.makeText(this, "Video facial procesado exitosamente", Toast.LENGTH_LONG).show()
                } else {
                    Log.e("AdvancedVisage", "Error: No se pudo convertir el video a Base64")
                    Toast.makeText(this, "Error al procesar el video", Toast.LENGTH_SHORT).show()
                }
            }
        } catch (e: Exception) {
            Log.e("AdvancedVisage", "Error procesando resultado: ${e.message}")
            Toast.makeText(this, "Error inesperado: ${e.message}", Toast.LENGTH_SHORT).show()
        }
    }

    private fun processCompleteVideo(base64Video: String, originalUri: Uri) {
        // Preparar datos completos para KYC
        val kycData = mapOf(
            "facialVideo" to base64Video,
            "timestamp" to System.currentTimeMillis(),
            "source" to "visage_sdk_android_v1",
            "deviceInfo" to getDeviceInfo(),
            "videoUri" to originalUri.toString(),
            "sessionId" to generateSessionId()
        )

        // Enviar a backend para análisis biométrico
        sendToKYCBackend(kycData)

        Log.d("AdvancedVisage", "Video procesado para KYC completamente")
    }

    private fun getDeviceInfo(): Map<String, String> {
        return mapOf(
            "model" to android.os.Build.MODEL,
            "manufacturer" to android.os.Build.MANUFACTURER,
            "sdkVersion" to android.os.Build.VERSION.SDK_INT.toString(),
            "appVersion" to packageManager.getPackageInfo(packageName, 0).versionName
        )
    }

    private fun generateSessionId(): String {
        return "visage_${System.currentTimeMillis()}_${(1000..9999).random()}"
    }

    override fun onErrorVisage(text: String) {
        Log.e("AdvancedVisage", "Error: $text")

        // Categorizar errores para mejor manejo
        when {
            text.contains("permission", ignoreCase = true) -> handlePermissionError(text)
            text.contains("camera", ignoreCase = true) -> handleCameraError(text)
            text.contains("cancelled", ignoreCase = true) -> handleUserCancellation(text)
            else -> handleGenericError(text)
        }
    }

    private fun handlePermissionError(text: String) {
        Toast.makeText(this, "Permisos de cámara requeridos para detección facial", Toast.LENGTH_LONG).show()
    }

    private fun handleCameraError(text: String) {
        Toast.makeText(this, "Error de cámara. Verifica que no esté siendo usada", Toast.LENGTH_LONG).show()
    }

    private fun handleUserCancellation(text: String) {
        Toast.makeText(this, "Proceso cancelado por el usuario", Toast.LENGTH_SHORT).show()
    }

    private fun handleGenericError(text: String) {
        Toast.makeText(this, "Error: $text", Toast.LENGTH_LONG).show()
    }
}

4.2 Utilidad de Análisis de Video

class VideoAnalyzer {

    companion object {
        fun analyzeVideoQuality(base64Video: String): VideoQualityReport {
            return VideoQualityReport(
                sizeInBytes = base64Video.length * 3 / 4, // Aproximación de tamaño
                base64Length = base64Video.length,
                estimatedDurationSeconds = estimateDuration(base64Video),
                qualityScore = calculateQualityScore(base64Video)
            )
        }

        private fun estimateDuration(base64Video: String): Float {
            // Estimación basada en tamaño (muy aproximada)
            val sizeInMB = (base64Video.length * 3 / 4) / (1024 * 1024)
            return sizeInMB * 2 // Aproximadamente 2 segundos por MB
        }

        private fun calculateQualityScore(base64Video: String): Float {
            // Score simple basado en tamaño del video
            val sizeInMB = (base64Video.length * 3 / 4) / (1024 * 1024)
            return when {
                sizeInMB > 5 -> 0.9f
                sizeInMB > 2 -> 0.7f
                sizeInMB > 1 -> 0.5f
                else -> 0.3f
            }
        }
    }
}

data class VideoQualityReport(
    val sizeInBytes: Int,
    val base64Length: Int,
    val estimatedDurationSeconds: Float,
    val qualityScore: Float
)

PASO 5: Probar Detección Facial

Objetivo

Verificar que todas las funcionalidades del SDK funcionan correctamente.

Lista de Verificación

ElementoEstadoDescripción
Permisos de cámaraApp solicita y obtiene permisos automáticamente
Detección facialIA detecta rostros en tiempo real
Grabación automáticaInicia grabación al detectar rostro correctamente
Conversión Base64Videos se convierten correctamente
Manejo de erroresErrores se manejan correctamente

Proceso de Prueba

Paso A: Instalar y Configurar

  1. Compilar aplicación en Android Studio
  2. Instalar en dispositivo físico (no emulador)
  3. Verificar permisos se solicitan automáticamente

Paso B: Probar Detección Básica

  1. Abrir aplicación y presionar "Iniciar Detección Facial"
  2. Posicionar rostro frente a la cámara frontal
  3. Esperar detección automática (esquinas verdes)
  4. Verificar inicio automático de grabación
  5. Revisar logs para confirmar procesamiento

Paso C: Probar Diferentes Condiciones

  1. Cambiar iluminación (luz natural, artificial)
  2. Probar distancias (cerca, lejos del dispositivo)
  3. Verificar modo debug (FPS, memoria, detección)
  4. Confirmar videos se guardan correctamente

Solución de Problemas

Problemas Comunes

ProblemaSolución
"SDK no se inicializa"Verificar Application class y dependencias
"Cámara no inicia"Verificar permisos y dispositivo físico
"No detecta rostros"Mejorar iluminación y usar cámara frontal
"Grabación falla"Verificar espacio de almacenamiento

Debug Avanzado

private fun enableAdvancedDebugging() {
    visageSDK.setDebugMode(true)  // Muestra: FPS | MEM | FACE | CAM
    visageSDK.setConfigPanel(true) // Muestra panel de estado

    Log.d("Visage", "=== INFORMACIÓN DEL DISPOSITIVO ===")
    Log.d("Visage", "Modelo: ${Build.MODEL}")
    Log.d("Visage", "Manufacturer: ${Build.MANUFACTURER}")
    Log.d("Visage", "SDK: ${Build.VERSION.SDK_INT}")
    Log.d("Visage", "RAM disponible: ${getAvailableMemory()}")
}

private fun getAvailableMemory(): String {
    val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
    val memoryInfo = ActivityManager.MemoryInfo()
    activityManager.getMemoryInfo(memoryInfo)
    return "${memoryInfo.availMem / (1024 * 1024)} MB"
}

Referencia Completa

Métodos del SDK

MétodoDescripciónEjemplo
VisageSDK(context, listener)Constructor del SDKVisageSDK(this, this)
startVisage()Inicia detección facial y grabaciónvisageSDK.startVisage()
setCameraFacing(facing)Configura cámara frontal/traseravisageSDK.setCameraFacing(CameraFacing.FRONT)
setDebugMode(boolean)Activa métricas en tiempo realvisageSDK.setDebugMode(true)
setConfigPanel(boolean)Muestra panel de configuraciónvisageSDK.setConfigPanel(true)

Configuraciones Disponibles

ConfiguraciónDescripciónCuándo usar
CameraFacing.FRONTCámara frontalRecomendado para rostros
CameraFacing.BACKCámara traseraCasos especiales
setDebugMode(true)Métricas en tiempo realDurante desarrollo
setConfigPanel(true)Panel de estado visiblePara debugging

Estructura de Respuesta

RespuestaDescripciónCuándo se genera
onSuccessVisage(typeProcess, uri)Video grabado exitosamentetypeProcess=2, uri del video
onErrorVisage(text)Error en el procesoCualquier fallo en detección/grabación

Solución de Problemas Avanzados

Problema: "Error de modelos ML Kit"

// Verificar compatibilidad de ML Kit
private fun checkMLKitCompatibility() {
    try {
        // ML Kit se inicializa automáticamente
        Log.d("Visage", "ML Kit disponible para detección facial")
    } catch (e: Exception) {
        Log.e("Visage", "Error ML Kit: ${e.message}")
        showError("Dispositivo no compatible con detección facial")
    }
}

Problema: "Videos no se procesan"

// Verificar conversión de videos
private fun debugVideoConversion(uri: Uri) {
    try {
        val base64Video = Utils.videoCameraUriToBase64(uri)

        if (base64Video.isNullOrEmpty()) {
            Log.e("Visage", "Error: Conversión a Base64 falló")
            Log.e("Visage", "URI: $uri")

            // Verificar si el archivo existe
            contentResolver.query(uri, null, null, null, null)?.use { cursor ->
                Log.d("Visage", "Archivo existe: ${cursor.count > 0}")
            }
        } else {
            Log.d("Visage", "Conversión exitosa: ${base64Video.length} caracteres")
        }
    } catch (e: Exception) {
        Log.e("Visage", "Error procesando video: ${e.message}")
    }
}

¿Necesitas Ayuda?

Información para soporte

  • Descripción del problema: Qué tipo de detección intentas vs qué sucede
  • Logs de Android Studio: Screenshots de errores en Logcat
  • Información del dispositivo: Modelo, versión Android, RAM
  • Configuración del SDK: Parámetros utilizados
  • Condiciones de prueba: Iluminación, distancia, orientación

Contacto de Soporte

Email: soporte@jaak.ai Incluir siempre: Logs Android, configuración build.gradle, versión SDK


Has implementado exitosamente el JAAKVisage SDK en tu aplicación Android. Tu app ahora puede detectar rostros y grabar videos automáticamente usando inteligencia artificial, generando archivos optimizados para procesos de verificación biométrica y KYC.