Aplicación Android profesional que implementa reconocimiento de texto (OCR) en tiempo real usando la cámara del dispositivo, diseñada específicamente para escaneo de tickets de compra.
- ✅ OCR en tiempo real usando ML Kit Text Recognition (on-device)
- ✅ Preview de cámara con CameraX
- ✅ Overlay visual mostrando texto reconocido y bounding boxes
- ✅ Captura inteligente de frames para procesamiento posterior
- ✅ 100% on-device - sin APIs cloud ni servicios de pago
- ✅ Clean Architecture con separación clara de capas
- ✅ Inyección de dependencias con Hilt
- ✅ UI moderna con Jetpack Compose
El proyecto sigue Clean Architecture con MVVM:
com.amartinez.tickeslens
├── data
│ └── ocr
│ └── MlKitTextRecognizer.kt # Implementación OCR con ML Kit
├── domain
│ ├── model
│ │ └── RecognizedTextBlock.kt # Modelo de dominio
│ └── usecase
│ └── AnalyzeImageUseCase.kt # Caso de uso de análisis
├── presentation
│ ├── camera
│ │ ├── CameraScreen.kt # UI principal con Compose
│ │ ├── CameraOverlay.kt # Overlay de texto reconocido
│ │ └── CameraViewModel.kt # ViewModel con StateFlow
│ └── permission
│ └── CameraPermissionHandler.kt # Manejo de permisos
└── di
└── AppModule.kt # Configuración Hilt
| Componente | Tecnología | Versión |
|---|---|---|
| Lenguaje | Kotlin | 2.1.0 |
| UI | Jetpack Compose | 2025.06.01 BOM |
| Cámara | CameraX | 1.4.2 |
| OCR | ML Kit Text Recognition | 16.0.1 |
| Arquitectura | MVVM + Clean Architecture | - |
| DI | Hilt | 2.56.2 |
| Permisos | Accompanist Permissions | 0.37.3 |
| Concurrencia | Kotlin Coroutines + StateFlow | - |
CameraX ImageAnalysis
↓
AnalyzeImageUseCase (Domain)
↓
MlKitTextRecognizer (Data)
↓
RecognizedTextBlock (Domain Model)
↓
CameraViewModel → StateFlow
↓
CameraScreen + CameraOverlay (UI)El CameraViewModel implementa una estrategia de backpressure para evitar sobrecarga:
private var isAnalyzing = false
fun analyzeFrame(imageProxy: ImageProxy) {
if (isAnalyzing) {
imageProxy.close() // Descartar frame si ya hay uno procesándose
return
}
isAnalyzing = true
// ... procesar frame
}El CameraOverlay escala correctamente las coordenadas de los bounding boxes:
val scaleX = screenWidth / imageWidth.toFloat()
val scaleY = screenHeight / imageHeight.toFloat()
// Aplicar transformación a cada rectángulo- Android SDK: Mínimo API 24 (Android 7.0)
- Cámara: Dispositivo con cámara trasera
- Permisos:
android.permission.CAMERA
git clone https://github.com/alexmartinezmoron/ticketlens.git
cd ticketlens- Android Studio Hedgehog (2023.1.1) o superior
- Kotlin plugin actualizado
El proyecto usa Gradle Version Catalog (libs.versions.toml):
./gradlew sync./gradlew assembleDebug
# O presionar Run en Android Studio- Iniciar la app - Solicita permiso de cámara automáticamente
- Apuntar a texto - La cámara reconoce texto en tiempo real
- Ver overlay - Rectángulos verdes muestran texto detectado
- Capturar ticket - Presionar botón flotante para captura de alta calidad
- Separation of Concerns: Cada capa tiene responsabilidad única
- Dependency Rule: Las dependencias apuntan hacia el dominio
- Independence: UI y frameworks son detalles intercambiables
- Single Responsibility: Una clase, una razón para cambiar
- Open/Closed: Abierto a extensión, cerrado a modificación
- Liskov Substitution: Interfaces bien definidas
- Interface Segregation: Interfaces específicas y pequeñas
- Dependency Inversion: Depende de abstracciones, no implementaciones
- ✅ Null safety en toda la codebase
- ✅ Coroutines para operaciones asíncronas
- ✅ StateFlow para manejo de estado reactivo
- ✅ Cierre correcto de recursos (ImageProxy)
- ✅ Manejo de ciclo de vida con Lifecycle-aware components
- ✅ No bloqueo del hilo principal
- ✅ Composables pequeños y reutilizables
- Captura de alta calidad con ImageCapture
- Procesamiento post-captura mejorado
- Extracción estructurada de datos de tickets
- Integración con backend/API
- Clasificación con fastText
- Historial de tickets escaneados
- Modo nocturno con flash automático
- Exportación de datos (PDF, JSON)
# Unit tests
./gradlew test
# Instrumented tests
./gradlew connectedAndroidTestEl proyecto usa ExperimentalGetImage de CameraX. Configurado en build.gradle.kts:
kotlinOptions {
freeCompilerArgs += listOf(
"-opt-in=androidx.camera.core.ExperimentalGetImage"
)
}ML Kit maneja automáticamente la rotación de imagen:
val inputImage = InputImage.fromMediaImage(
imageProxy.image!!,
imageProxy.imageInfo.rotationDegrees
)- Backpressure strategy:
STRATEGY_KEEP_ONLY_LATEST - Single-threaded executor para análisis secuencial
- Cierre inmediato de ImageProxy no usados
- Coroutines en
Dispatchers.Defaultpara OCR
- Fork el proyecto
- Crea una rama feature (
git checkout -b feature/amazing-feature) - Commit cambios (
git commit -m 'Add amazing feature') - Push a la rama (
git push origin feature/amazing-feature) - Abre un Pull Request
Este proyecto está bajo la Licencia MIT - ver el archivo LICENSE para detalles.
Alexander Martinez
- GitHub: https://github.com/alexmartinezmoron/
- LinkedIn: https://www.linkedin.com/in/alexander-martinez-moron/
- Google ML Kit por el motor OCR on-device
- Jetpack Team por CameraX y Compose
- Dagger/Hilt team por la excelente DI
- Accompanist por utilities de Compose
⭐ Si este proyecto te fue útil, considera darle una estrella en GitHub