
С чего бы начать?
Библиотека от компании 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" // для работы с CameraX1) Можем создать простой пример использования. Используем 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@addListener3) Далее добавим выбор доступной камеры - сперва основная, потом фронтальная
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пасибо за внимание!
