【代码吸猫】使用 Google MLKit 进行图像识别

本文涉及的产品
图像搜索,7款服务类型 1个月
简介: 【代码吸猫】使用 Google MLKit 进行图像识别

本文带大家在 Android 上体验 MLKit 的以下功能:

  • 1. 图像识别(Image Labeling)
  • 2. 目标检测(Object Detection)
  • 3. 目标追踪(Object Tracking)


1. 图像识别(Image Labeling)

图像识别是计算机视觉的一个重要领域,简单说就是帮你提取图片中的有效信息。 MLKit 提供了 ImageLabeling 功能,可以识别图像信息并进行分类标注。

比如输入一张包含猫的图片,ImageLabeling 能识别出图片中的猫元素,并给出一个猫的标注,除了最显眼的猫 ImageLabeling还能识别出花、草等图片中所有可识别的事物,并分别给出出现的概率和占比,识别的结果以 List<ImageLabel> 返回。 基于预置的默认模型,ImageLabeling可以对图像元素进行超过 400 种以上的标注分类,当然你可以使用自己训练的模型扩充更多分类。

默认模型当前支持的分类标注:

developers.google.com/ml-kit/visi…

Android 中引入 MLKit 的 ImageLabeliing 很简单,在 gradle 中添加相关依赖即可

implementation 'com.google.mlkit:image-labeling:17.0.5'

接下来写一个 Android 的 Demo 来展示使用效果。我们使用 Compose 为 Demo 写一个简单的 UI:

@Composable
fun MLKitSample() {
    Column {
        var imageLabel by remember { mutableStateOf("") }
        //Load Image
        val context = LocalContext.current
        val bmp = remember(context) {
            context.assetsToBitmap("cat.png")!!
        }
        Image(bitmap = bmp.asImageBitmap(), contentDescription = "")
        val coroutineScope = rememberCoroutineScope()
        Button(
            onClick = {
                //TODO : 图像识别具体逻辑,见后文
            }) 
        {  Text("Image Labeling") }
        Text(imageLabel, Modifier.fillMaxWidth(), textAlign = TextAlign.Center)
    }
}

将图片资源放入 /assets,并加载为 Bitmap

fun Context.assetsToBitmap(fileName: String): Bitmap? =
    assets.open(fileName).use {
        BitmapFactory.decodeStream(it)
    }

点击 Button 后,对 Bitmap 进行识别,获取识别后的信息更新 imageLabelimage.png

看一下 onClick 内的内容:

val labeler = ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS)
val image = InputImage.fromBitmap(bmp, 0)
labeler.process(image).addOnSuccessListener { labels : List<ImageLabel> ->
    // Task completed successfully
    imageLabel = labels.scan("") { acc, label ->
        acc + "${label.text} : ${label.confidence}\n"
    }.last()
}.addOnFailureListener {
    // Task failed with an exception
}

首先创建 ImageLabeler 处理器,InputImage.fromBitmap 将 Bitmap 处理为 ImageLabeler 可接受的资源类型,处理结果通过 Listener 返回。

处理成功,返回 ImageLabel 的列表,ImageLabel 代表每一个种类的标注信息,图像经识别后获得一组这样de 标注,包含每一种类的名字以及其出现概率,这些信息可以在图像检索等场景中作为权重使用。


2. 目标检测(Object Detection)

目标检测也是计算机视觉的一个基础研究方向。这里需要注意 “检测” 和 “识别” 的区别:

  • 检测(Detecting):关注的是 Where is,即目标在哪里
  • 识别(Lebeling):关注的是 What is,即目标是什么

ImageLebeling 可以识别图像中的事物分类,但是无法确定哪个事物在哪里。而目标检测可以确定有几个事物分别在哪里,但是事物的分类信息不清晰。

ObjectDetection 虽然也提供了一定的识别能力,但是其默认的模型文件只能识别有限的几个种类,无法像 ImageLebeling 那样精确分类。想要识别更准确的信息需要借助额外的模型文件。但是我们可以将上述两套 API 配合使用,各取所长以达到目标检测的同时进行准确的识别和分类。

image.png

首先添加 ObjectDetection 依赖

implementation 'com.google.mlkit:object-detection:16.2.7'

接下来在上面例子中,增加一个 Button 用于点击后的目标检测

@Composable
fun MLKitSample() {
    Column(Modifier.fillMaxSize()) {
        val detctedObject = remember { mutableStateListOf<DetectedObject>() }
        //Load Image
        val context = LocalContext.current
        val bmp = remember(context) {
            context.assetsToBitmap("dog_cat.jpg")!!
        }
        Canvas(Modifier.aspectRatio(
            bmp.width.toFloat() / bmp.height.toFloat())) {
            drawIntoCanvas { canvas ->
                canvas.withSave {
                    canvas.scale(size.width / bmp.width)
                    canvas.drawImage( // 绘制 image
                        image = bmp.asImageBitmap(), Offset(0f, 0f), Paint()
                    )
                    detctedObject.forEach {
                        canvas.drawRect( //绘制目标检测的边框
                            it.boundingBox.toComposeRect(),
                            Paint().apply {
                                color = Color.Red.copy(alpha = 0.5f)
                                style = PaintingStyle.Stroke
                                strokeWidth = bmp.width * 0.01f
                            })
                        if (it.labels.isNotEmpty()) {
                            canvas.nativeCanvas.drawText( //绘制物体识别信息
                                it.labels.first().text,
                                it.boundingBox.left.toFloat(),
                                it.boundingBox.top.toFloat(),
                                android.graphics.Paint().apply {
                                    color = Color.Green.toArgb()
                                    textSize = bmp.width * 0.05f
                                })
                        }
                    }
                }
            }
        }
        Button(
            onClick = {
                //TODO : 目标检测具体逻辑,见后文
            }) 
        {  Text("Object Detect") }
    }
}

由于我们要在图像上绘制目标边界的信息,所以这次采用 Canvas 绘制 UI,包括以下内容:

  • drawImage:绘制目标图片
  • drawRect:MLKit 检测成功后会返回 List<DetectedObject> 信息,基于 DetectedObject 绘制目标边界
  • drawText:基于 DetectedObject 绘制目标的分类标注

点击 Button 后进行目标检测,具体实现如下:

val options =
    ObjectDetectorOptions.Builder()
        .setDetectorMode(ObjectDetectorOptions.SINGLE_IMAGE_MODE)
        .enableMultipleObjects()
        .enableClassification()
        .build()
val objectDetector = ObjectDetection.getClient(options)
val image = InputImage.fromBitmap(bmp, 0)
objectDetector.process(image)
    .addOnSuccessListener { detectedObjects ->
        // Task completed successfully
        coroutineScope.launch {
            detctedObject.clear()
            detctedObject.addAll(getLabels(bmp, detectedObjects).toList())
        }
    }
    .addOnFailureListener { e ->
        // Task failed with an exception
        // ...
    }

通过 ObjectDetectorOptions 我们对检测处理进行配置。可使用 Builder 进行多个配置:

  • setDetectorMode : ObjectDetection 有多种目标检测方式,这里使用的是最简单的一种 SINGLE_IMAGE_MODE 即针对单张图片的检测。此外还有针对视频流的检测等其他方式,后文介绍。
  • enableMultipleObjects:可以只检测最突出的事物或是检测所有可事物,我们这里启动多目标检测,检测所有可检测的事物。
  • enableClassification: ObjectDetection 在图像识别上的能力有限,默认模型只能识别 5 个种类,且都是比较宽泛的分类,比如植物、动物等。enableClassification 可以开启图像识别能力。开启后,其识别结果会存入 DetectedObject.labels。由于这个识别结果没有意义,我们在例子中会替换为使用 ImageLebeling 识别后的标注信息

基于 ObjectDetectorOptions 创建 ObjectDetector 处理器,传入图片后开始检测。getLabels 是自定义方法,基于 ImageLebeling 添加图像识别信息。检测的最终结果更新至 detctedObject 这个 MutableStateList,刷新 Compose UI。

private fun getLabels(
    bitmap: Bitmap,
    objects: List<DetectedObject>
) = flow {
    val labeler = ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS)
    for (obj in objects) {
        val bounds = obj.boundingBox
        val croppedBitmap = Bitmap.createBitmap(
            bitmap,
            bounds.left,
            bounds.top,
            bounds.width(),
            bounds.height()
        )
        emit(
            DetectedObject(
                obj.boundingBox,
                obj.trackingId,
                getLabel(labeler, croppedBitmap).map {
                    //转换为 DetectedObject.Label
                    DetectedObject.Label(it.text, it.confidence, it.index)
                })
        )
    }
}

首先根据 DetectedObject 的边框信息 boundingBox 将 Bitmap 分解为小图片,然后对其调用 getLabel 获取标注信息补充进 DetectedObject 实例(这里实际是重建了一个实例)

getLabel 中的 ImageLebeling 是一个异步过程,为了调用方便,定义为一个挂起函数:

suspend fun getLabel(labeler: ImageLabeler, image: Bitmap): List<ImageLabel> =
    suspendCancellableCoroutine { cont ->
        labeler.process(InputImage.fromBitmap(image, 0))
            .addOnSuccessListener { labels ->
                // Task completed successfully
                cont.resume(labels)
            }
    }

3. 目标追踪(Object Tracking)

目标追踪就是通过对视频逐帧进行 ObjectDetection ,以达到连续捕捉的效果。接下来的例子中我们启动一个相机预览,对拍摄到图像进行 ObjectTracking。

image.png

我们使用 CameraX 启动相机,因为 CameraX 封装的 API 更易用。

implementation "androidx.camera:camera-camera2:1.0.0-rc01"
implementation "androidx.camera:camera-lifecycle:1.0.0-rc01"
implementation "androidx.camera:camera-view:1.0.0-alpha20"
implementation "com.google.accompanist:accompanist-permissions:0.16.1"

如上,引入 CameraX 相关类库,同时引入 accompanist-permissions 用来动态申请相机权限。

CameraX 的预览需要使用 androidx.camera.view.PreviewView,我们通过 AndroidView 集成到 Composable 中,AndroidView 上方覆盖 Canvas ,Canvas 绘制目标边框。

整个 UI 布局如下:

val detectedObjects = mutableStateListOf<DetectedObject>()
Box {
    CameraPreview(detectedObjects)
    Canvas(modifier = Modifier.fillMaxSize()) {
        drawIntoCanvas { canvas ->
            detectedObjects.forEach {
                canvas.scale(size.width / 480, size.height / 640)
                canvas.drawRect( //绘制边框
                    it.boundingBox.toComposeRect(),
                    Paint().apply {
                        color = Color.Red
                        style = PaintingStyle.Stroke
                        strokeWidth = 5f
                    })
                canvas.nativeCanvas.drawText( // 绘制文字
                    "TrackingId_${it.trackingId}",
                    it.boundingBox.left.toFloat(),
                    it.boundingBox.top.toFloat(),
                    android.graphics.Paint().apply {
                        color = Color.Green.toArgb()
                        textSize = 20f
                    })
            }
        }
    }
}

detectedObjects 是 ObjectDetection 逐帧实时检测的结果。CameraPreview 中集成了相机预览的 AndroidView,并实时更新 detectedObjects 。drawRect 和 drawText 在前面例子中也出现过,但需要注意这里 drawText 绘制的是 trackingId 。 视频的 ObjectDetection 会为 DetectedObject 添加 trackingId 信息, 视频目标的边框位置会不断变换,但是 trackingId 是不变的,这便于在多目标中更好地锁定个体。

@Composable
private fun CameraPreview(detectedObjects: SnapshotStateList<DetectedObject>) {
    val lifecycleOwner = LocalLifecycleOwner.current
    val context = LocalContext.current
    val cameraProviderFuture = remember { ProcessCameraProvider.getInstance(context) }
    val coroutineScope = rememberCoroutineScope()
    val objectAnalyzer = remember { ObjectAnalyzer(coroutineScope, detectedObjects) }
    AndroidView(
        factory = { ctx ->
            val previewView = PreviewView(ctx)
            val executor = ContextCompat.getMainExecutor(ctx)
            val imageAnalyzer = ImageAnalysis.Builder()
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
                .build()
                .also {
                    it.setAnalyzer(executor, objectAnalyzer)
                }
            cameraProviderFuture.addListener({
                val cameraProvider = cameraProviderFuture.get()
                val preview = Preview.Builder().build().also {
                    it.setSurfaceProvider(previewView.surfaceProvider)
                }
                val cameraSelector = CameraSelector.Builder()
                    .requireLensFacing(CameraSelector.LENS_FACING_BACK)
                    .build()
                cameraProvider.unbindAll()
                cameraProvider.bindToLifecycle(
                    lifecycleOwner,
                    cameraSelector,
                    preview,
                    imageAnalyzer
                )
            }, executor)
            previewView
        },
        modifier = Modifier.fillMaxSize(),
    )
}

CameraPreview 主要是关于 CameraX 的使用,本文不会逐行说明 CameraX 的使用,只关注与主题相关的代码: CameraX 可以设置 ImageAnalyzer 用于对视频帧进行解析,这正是用于我们的需求,这里自定义了 ObjectAnalyzer 做目标检测。

最后看一下 ObjectAnalyzer 的实现

class ObjectAnalyzer(
    private val coroutineScope: CoroutineScope,
    private val detectedObjects: SnapshotStateList<DetectedObject>
) : ImageAnalysis.Analyzer {
    private val options = ObjectDetectorOptions.Builder()
        .setDetectorMode(ObjectDetectorOptions.STREAM_MODE)
        .build()
    private val objectDetector = ObjectDetection.getClient(options)
    @SuppressLint("UnsafeExperimentalUsageError")
    override fun analyze(imageProxy: ImageProxy) {
        val frame = InputImage.fromMediaImage(
            imageProxy.image,
            imageProxy.imageInfo.rotationDegrees
        )
        coroutineScope.launch {
            objectDetector.process(frame)
                .addOnSuccessListener { detectedObjects ->
                    // Task completed successfully
                    with(this@ObjectAnalyzer.detectedObjects) {
                        clear()
                        addAll(detectedObjects)
                    }
                }
                .addOnFailureListener { e ->
                    // Task failed with an exception
                    // ...
                }
                .addOnCompleteListener {
                    imageProxy.close()
                }
        }
    }
}

ObjectAnalyzer 中获取相机预览的视频帧对其进行 ObjectDetection,检测结果更新至 detectedObjects 。注意此处 ObjectDetectorOptions 设置为 STREAM_MODE 专门处理视频检测。虽然把每一帧都当做 SINGLE_IMAGE_MODE 处理理论上也是可行的,但只有 STREAM_MODE 的检测结果才带有 trackingId 的值,而且 STREAM_MODE 下的边框位置经过防抖处理,位移更加顺滑。


最后

本文为了参加平台的活动,以喵为例介绍了 MLKit 图像识别的能力, MLKit 还有很多实用功能,比如人脸检测相较于 Android 自带的 android.media.FaceDetector 无论性能还是识别率都有质的飞跃。此外国内不少 AI 大厂也有很多不错的解决方案,比如旷视 。相信随着 AI 技术的发展,未来在移动端上的应用场景也会越来越多。

本文代码:github.com/vitaviva/Je…

目录
相关文章
|
6月前
|
传感器 编解码 数据处理
Open Google Earth Engine(OEEL)——哨兵1号数据的黑边去除功能附链接和代码
Open Google Earth Engine(OEEL)——哨兵1号数据的黑边去除功能附链接和代码
127 0
|
1月前
|
机器学习/深度学习 算法 算法框架/工具
深度学习在图像识别中的应用及代码示例
【9月更文挑战第32天】本文将深入探讨深度学习在图像识别领域的应用,包括其原理、技术、优势以及挑战。我们将通过一个简单的代码示例,展示如何使用深度学习技术进行图像识别。无论你是初学者还是有经验的开发者,都可以从中获得启发和帮助。让我们一起探索这个充满无限可能的领域吧!
68 8
|
2月前
|
机器学习/深度学习 算法框架/工具 Python
深度学习在图像识别中的应用及其代码实现
【9月更文挑战第24天】本文将探讨深度学习在图像识别领域的应用,并展示如何通过代码实现这一过程。我们将介绍深度学习的基本原理,以及它在图像识别中的优势和挑战。然后,我们将通过一个简单的代码示例,展示如何使用深度学习进行图像识别。最后,我们将讨论深度学习在未来图像识别中的潜力和可能的发展方向。
|
3月前
|
机器学习/深度学习 算法 机器人
【2023年第十三届APMCM亚太地区大学生数学建模竞赛】A题 水果采摘机器人的图像识别 Python代码解析
本文介绍了2023年第十三届APMCM亚太地区大学生数学建模竞赛A题的Python代码实现,详细阐述了水果采摘机器人图像识别问题的分析与解决策略,包括图像特征提取、数学模型建立、目标检测算法使用,以及苹果数量统计、位置估计、成熟度评估和质量估计等任务的编程实践。
85 0
【2023年第十三届APMCM亚太地区大学生数学建模竞赛】A题 水果采摘机器人的图像识别 Python代码解析
|
3月前
|
机器学习/深度学习 算法 算法框架/工具
深度学习在图像识别中的应用及代码实现
【8月更文挑战第3天】深度学习技术在图像识别领域取得了显著的成果,通过构建深度神经网络模型,实现了对复杂图像数据的高效处理和准确识别。本文将介绍深度学习在图像识别中的原理、关键技术及应用实例,并通过代码示例展示如何利用深度学习框架进行图像识别任务的实现。
|
3月前
|
安全 Apache 数据安全/隐私保护
你的Wicket应用安全吗?揭秘在Apache Wicket中实现坚不可摧的安全认证策略
【8月更文挑战第31天】在当前的网络环境中,安全性是任何应用程序的关键考量。Apache Wicket 是一个强大的 Java Web 框架,提供了丰富的工具和组件,帮助开发者构建安全的 Web 应用程序。本文介绍了如何在 Wicket 中实现安全认证,
43 0
|
3月前
|
机器学习/深度学习 数据采集 TensorFlow
从零到精通:TensorFlow与卷积神经网络(CNN)助你成为图像识别高手的终极指南——深入浅出教你搭建首个猫狗分类器,附带实战代码与训练技巧揭秘
【8月更文挑战第31天】本文通过杂文形式介绍了如何利用 TensorFlow 和卷积神经网络(CNN)构建图像识别系统,详细演示了从数据准备、模型构建到训练与评估的全过程。通过具体示例代码,展示了使用 Keras API 训练猫狗分类器的步骤,旨在帮助读者掌握图像识别的核心技术。此外,还探讨了图像识别在物体检测、语义分割等领域的广泛应用前景。
26 0
|
5月前
|
编解码
视觉智能开放平台操作报错合集之遇到错误代码"InternalError.Mapping",该怎么办
在使用视觉智能开放平台时,可能会遇到各种错误和问题。虽然具体的错误代码和消息会因平台而异,但以下是一些常见错误类型及其可能的原因和解决策略的概述,包括但不限于:1. 认证错误、2. 请求参数错误、3. 资源超限、4. 图像质量问题、5. 服务不可用、6. 模型不支持的场景、7. 网络连接问题,这有助于快速定位和解决问题。
|
6月前
|
机器学习/深度学习 数据可视化 计算机视觉
【YOLOv8改进】MCA:用于图像识别的深度卷积神经网络中的多维协作注意力 (论文笔记+引入代码)
YOLO目标检测专栏介绍了YOLO的创新改进和实战案例,包括多维协作注意力(MCA)机制,它通过三分支架构同时处理通道、高度和宽度注意力,提高CNN性能。MCA设计了自适应组合和门控机制,增强特征表示,且保持轻量化。该模块适用于各种CNN,实验证明其在图像识别任务上的优越性。此外,文章还展示了如何在YOLOv8中引入MCA层的代码实现和相关任务配置。
|
6月前
|
Web App开发 人工智能 JavaScript
从零写一个基于油猴脚本的 Google 辅助插件(文末附完整代码)
这是一个关于如何使用JavaScript和油猴脚本为Google搜索结果添加快捷键的功能介绍。作者首先阐述了想通过快捷键选择搜索结果的需求,然后选择了油猴插件作为开发平台。实现步骤包括:获取搜索结果列表、在结果前添加序号以及监听键盘事件触发点击。最后,作者还扩展了通过快捷键平滑滚动页面的功能,并分享了完整代码的GitHub链接。
95 0
从零写一个基于油猴脚本的 Google 辅助插件(文末附完整代码)

热门文章

最新文章