
С чего бы начать?
Библиотека от компании Google ML Kit предлагает набор встроенных API, которые могут работать как на самом девайсе, так и в облаке.
ML Kit - это мощный инструмент для работы с камерой в Android и IOS приложениях.
Возможности ML Kit

Категория |
API |
Описание |
Работа с изображениями |
Face Detection |
Обнаружение лиц, черт лица, направлений взгляда и т.д. |
Text Recognition |
Распознание текста, с возможность фильтровать и получать необходимый результат |
|
Barcode Scanning |
Сканирование QR-кодов и штрихкодов |
|
Image Labeling |
Определение объектов на изображении |
|
Object Detection & Tracking |
Обнаружение и отслеживание объектов |
|
Работа с текстом |
Smart Reply |
Генерация ответов на сообщения |
Translate |
Перевод текста в реальном времени |
|
Language Indetification |
Определение языка текста |
Как дела c Jetpack Compose?
С Jetpack Compose библиотека ML Kit отлично дружит и настоятельно рекомендую использовать в этой связке.
С чего начать?
Для примера я возьму две зависимости - одна для сканирования QR/штрихкодов, другая - для распознавания текста.
mlkit = { group = "com.google.mlkit", name = "barcode-scanning", version = "17.2.0" }
mlkit-text = { group = "com.google.mlkit", name = "text-recognition", version = "16.0.0" }
implementation "androidx.camera:camera-core:1.4.0" // для работы с CameraX
1) Можем создать простой пример использования. Используем AndroidView, которая позволит нам работать с PreviewView в камере.
@Composable
fun ScannerScreen(
scanType: ScanType,
onCodeScanned: ((String) -> Unit)? = null // для передачи результата на определенный экран
) {
val lifecycleOwner = LocalLifecycleOwner.current
val context = LocalContext.current
val analysisExecutor = remember { CoroutineScope(Dispatchers.Default) }
val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
DisposableEffect(Unit) {
onDispose {
val cameraProvider = cameraProviderFuture.get()
cameraProvider.unbindAll()
}
}
Box(modifier = Modifier.fillMaxSize()) {
AndroidView(
factory = { ctx ->
val previewView = PreviewView(ctx).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
scaleType = PreviewView.ScaleType.FILL_CENTER
}
val cameraProviderFuture = ProcessCameraProvider.getInstance(ctx)
cameraProviderFuture.addListener({
val cameraProvider = cameraProviderFuture.get()
val previewUseCase = Preview.Builder().build().also {
it.surfaceProvider = previewView.surfaceProvider
}
val permissionGranted = ContextCompat.checkSelfPermission(
ctx, Manifest.permission.CAMERA
) == PackageManager.PERMISSION_GRANTED
val barcodeScanner = BarcodeScanning.getClient(
BarcodeScannerOptions.Builder()
.setBarcodeFormats(Barcode.FORMAT_QR_CODE)
.build()
)
if (!permissionGranted) {
// Нет доступа к камере
return@addListener
}
val selector = when {
cameraProvider.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) -> CameraSelector.DEFAULT_BACK_CAMERA
cameraProvider.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) -> CameraSelector.DEFAULT_FRONT_CAMERA
else -> {
return@addListener
}
}
val analysisUseCase = ImageAnalysis.Builder()
.setTargetResolution(Size(1280, 720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.build()
.also { analysis ->
analysis.setAnalyzer(
{ runnable -> analysisExecutor.launch { runnable.run() } }
) { imageProxy ->
val mediaImage = imageProxy.image
if (mediaImage != null) {
val rotation = imageProxy.imageInfo.rotationDegrees
val inputImage = InputImage.fromMediaImage(mediaImage, rotation)
when (scanType) {
ScanType.ScanQr -> {
barcodeScanner.process(inputImage)
.addOnSuccessListener { barcodes ->
barcodes.firstOrNull { barcode ->
val box = barcode.boundingBox
box != null
}?.rawValue?.let { qrCode ->
onCodeScanned?.let { it(qrCode) }
}
}
.addOnCompleteListener {
imageProxy.close()
}
}
ScanType.ScanCard -> {
val textRecognizer = TextRecognition.getClient(
TextRecognizerOptions.DEFAULT_OPTIONS
)
textRecognizer.process(inputImage)
.addOnSuccessListener { visionText ->
if (!visionText.isNullOrEmpty()) {
onCodeScanned?.let {
it(visionText)
}
}
}
.addOnCompleteListener {
imageProxy.close()
}
}
ScanType.ScanText -> {
val textRecognizer = TextRecognition.getClient(
TextRecognizerOptions.DEFAULT_OPTIONS
)
textRecognizer.process(inputImage)
.addOnSuccessListener { visionText ->
val text = visionText.text
if (!text.isNullOrEmpty()) {
onCodeScanned?.let { result ->
result(text)
}
}
}
.addOnCompleteListener {
imageProxy.close()
}
}
ScanType.None -> {}
}
} else {
imageProxy.close()
}
}
}
try {
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
selector,
previewUseCase,
analysisUseCase
)
} catch (exception: Exception) {
// Ошибка привязки камеры
}
}, ContextCompat.getMainExecutor(ctx))
previewView
},
modifier = Modifier.fillMaxSize()
)
when (scanType) {
ScanType.ScanQr -> { CameraScanQrOverlay() }
ScanType.ScanCard -> { CameraScanCardOverlay() }
ScanType.ScanText -> { CameraScanTextOverlay() }
ScanType.None -> {}
}
}
}
sealed class ScanType() {
data object ScanQr: ScanType()
data object ScanText: ScanType()
data object ScanCard: ScanType()
data object None: ScanType()
}
Что здесь происходит? Создаем PreviewView для отображения изображения с камеры (CameraX), далее в LayoutParams устанавливаем ширину и высоту для контейнера.
PreviewView.ScaleType.FILL_CENTER - отвечает за то, чтобы заполнить PreviewView, сохраняя при этому центрирование.
Какие есть варианты PreviewView.ScaleType?
FILL_START
FILL_CENTER
FILL_END
-
FIT_CENTER
Детальный пример PreviewView.ScaleType
2) Далее получаем CameraProvider, который отвечает за получение и настройки камеры
val cameraProviderFuture = ProcessCameraProvider.getInstance(ctx)
cameraProviderFuture.addListener({ ... }, ContextCompat.getMainExecutor(ctx))
addListener позволяет безопасно получить cameraProvider, когда он будет доступен.
2.1) Так же можете добавить проверку разрешения камеры. При необходимости связать с навигацией и в случае чего либо возвращать пользователя на предыдущий экран или показывать соответствующее диалоговое окно
val permissionGranted = ContextCompat.checkSelfPermission(...) == PackageManager.PERMISSION_GRANTED
if (!permissionGranted) return@addListener
3) Далее добавим выбор доступной камеры - сперва основная, потом фронтальная
val selector = when {
cameraProvider.hasCamera(CameraSelector.DEFAULT_BACK_CAMERA) -> ...
cameraProvider.hasCamera(CameraSelector.DEFAULT_FRONT_CAMERA) -> ...
else -> return@addListener
}
4) Создаем image analysis use case для анализа кадров с камеры
val analysisUseCase = ImageAnalysis.Builder()
.setTargetResolution(Size(1280, 720))
.setBackpressureStrategy(...)
.build()
4.1) Обязательно! Если mediaImage == null, то освобождаем ресурсы камеры!
Почему это так важно?
Предотвращение утечек памяти
Избежание сбоев и зависания
Освобождения ресурсов камеры
if (mediaImage != null) {
val inputImage = InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
scanner.process(inputImage)
.addOnSuccessListener { barcodes ->
// Обработка кодов
}
.addOnFailureListener {
// Обработка ошибок
}
.addOnCompleteListener {
// Важно! Закрываем imageProxy после обработки
imageProxy.close()
}
} else {
imageProxy.close()
}
}
5) Установка анализатора, анализатор передает кадры на обработку.
analysis.setAnalyzer({ runnable -> ... }) { imageProxy -> ... }
image proxy - объект изображения
6) Привязка use-cases к жизненому циклу. Удаляем все предыдущие use cases (unbindAll())
cameraProvider.unbindAll()
cameraProvider.bindToLifecycle(
lifecycleOwner,
selector,
previewUseCase,
analysisUseCase
)
7) Обрабатываем overlay UI по типу сканирования. Также вы можете передавать Rect области сканирования из самих Overlay в ScannerScreen.
when (scanType) {
ScanType.ScanQr -> CameraScanQrOverlay()
ScanType.ScanCard -> CameraScanCardOverlay()
ScanType.ScanText -> CameraScanTextOverlay()
ScanType.None -> {}
}
Почему не zxing?
При всех своих плюсах в простоте и быстрой реализации Zxing имеет свои плюсы и минусы. Да, ML Kit зависит от Google сервисов, но при этом это современное и надежное решение с больших спектром настроек, если для вас критично, чтобы приложение имело малый размер и быстрое внедрение в проект, то собственно вам подойдет Zxing, но если вы хотите, чтобы решение было гибкое и современное, то однозначно ML Kit.
Итоги
Библиотека ML Kit имеет ряд преимуществ: высокая точность и распознание даже при плохом освещении, широкая поддержка форматов, работает оффлайн, поддерживается Google и активно обновляется, легко кастомизировать UI в связке c Jetpack Compose, можно объединить с другими ML Kit модулями.
Надеюсь статья привнесла пользу Вам, попытался изложить в кратной форме, если есть вопросы, то буду рад ответить на них. Cпасибо за внимание!