前言

现在大部分人的手机自带相册都会有智能分类的功用,会将手机里的相片按照人脸、场景、地点、相片风格等特色进行分类,方便用户检查。

运用ML Kit 完成相册智能场景分类 (一)

这种分类大概率是运用了机器学习的功用,经过特定的模型对图片进行扫描,依据扫描的成果进行聚类展现。经过之前 Android 机器学习组件-图画标签初探 一文,咱们现已学会了运用 Android 官方供给的图画标签组件,辨认单个图画内包括信息的功用。咱们以此为基础进行扩展,对多张图片进行辨认,依据辨认成果把具有相同(或相似)标签的图片聚合在一起,就能够完成图片场景分类的功用了。

图片选择器完成场景分类

日常运用 App 的进程中,当咱们想在社交媒体上发布图片,无论是斗图仍是想分享一张收藏了很久的图片时,都会遇到找一张图片找良久,在不同的相册目录里来回翻找,要花费很长的时刻的场景。因为现在大多数 App 的相册都是依据文件夹进行了简略的归类,关于图片的特征信息没有进行利用,因此也就无法依据图片的特征进行检索。

这方面做的比较好的仍是微信,微信图片选择器的归类非常丰富,除了常规的文件夹分类,还有收藏这个类目,最实用的仍是图片对图片信息特征的运用,依据时刻、地点、人物、智能场景等特征对图片进行了分类,还能够依据这些场景进行查找。

运用ML Kit 完成相册智能场景分类 (一)

运用ML Kit 完成相册智能场景分类 (一)

依据 ML Kit 图画标签组件完成图片聚类展现

依据前文Android 机器学习组件-图画标签初探咱们知道

  • image-labeling 组件会依据输入的图片信息回来图画包括的多个标签信息,并依据标签的可信度依次回来。
  • 每一次对图片进行处理都是异步进行的,因为模型从图片中获取信息是一个耗时的进程。

因此,如果需求对很多的图片进行图画标签的辨认,需求经过线程池去处理。这儿先用手机相册的一切图片测验一下作用。

获取一切标签

    fun getLabel(context: Context, uris: List<Uri>, callback: (() -> Unit)? = null) {
        val cachedThreadPool = Executors.newFixedThreadPool(4)
        var count = 0
        val start = System.currentTimeMillis()
        uris.forEach {
            cachedThreadPool.submit {
                // 获取图画标签
                getLabel(context, it) {
                    count++
                    if (count >= uris.size) {
                        Log.d(
                            TAG,
                            "total cost ${(System.currentTimeMillis() - start) / 1000f} seconds on ${
                                uris.size
                            } picture"
                        )
                    }
                }
            }
        }
    }

这儿咱们经过线程池去处理每一张图片的辨认进程,因为辨认的进程中会存在失利的状况,因此经过任务计数的方法来确认一切图片辨认完成,咱们看一下输出。

11:15:42.237 ImageLabelHelper         D  total cost 9.563 seconds on 122 picture

在 Pixel 2 的手机上,122 张图片耗时 9 秒,这个耗时仍是挺长的。

获取图画标签

    fun getLabel(context: Context, uri: Uri, callback: () -> Unit) {
        val image: InputImage
        var categories = HashMap<Int, ArrayList<Uri>>()
        try {
            image = InputImage.fromFilePath(context, uri)
            labeler?.process(image)?.addOnSuccessListener { labels ->
                for (label in labels) {
                    val index = label.index
                    var list = categories[index]
                    if (list == null) {
                        list = ArrayList()
                        list.add(uri)
                    }
                    // 将图片的包括信息按找标签索引进行聚类,
                    if (categories[index] == null) {
                        categories[index] = list
                    } else {
                        categories[index]?.add(uri)
                    }
                    callback()
                    // 获取第一个就回来,用置信度最高的那个即可
                    break
                }
            }?.addOnFailureListener { e ->
                callback()
                Log.e(TAG, e.stackTraceToString())
            }
        } catch (e: Exception) {
            callback()
            e.printStackTrace()
        }
    }

关于任意一张图片,获取到标签信息后

  • 将依据标签索引进行聚类处理,一切归于同一个标签的图片都归类到一个列表里
  • 只获取第一个标签即可,因为这个标签基本上就能够代表这个图片的内容。

依据标签信息对图片进行聚类

    fun getLabel(context: Context, uris: List<Uri>, callback: (() -> Unit)? = null) {
        val cachedThreadPool = Executors.newFixedThreadPool(4)
        var count = 0
        val start = System.currentTimeMillis()
        uris.forEach {
            cachedThreadPool.submit {
                getLabel(context, it) {
                    count++
                    if (count >= uris.size) {
                        convertToLabels(context)
                        callback?.invoke()
                    }
                }
            }
        }
    }
    private fun convertToLabels(context: Context) {
        val result = ArrayList<Labels>(categories.keys.size)
        val tags = JsonUtil.readJsonStr(context, "tags.json")
        val tagsMap = JSONObject.parseObject(tags)
        categories.forEach {
            val key = it.key
            val list = it.value
            val realKey: String = tagsMap.getString(key.toString())
            val labels = Labels(realKey, list)
            result.add(labels)
        }
        labelList.clear()
        labelList.addAll(result)
        Log.e(TAG, result.toString())
    }    

当咱们获取到一切的标签索引之后,就能够依据标签索引来映射图片的实在信息了。这儿 tags.json 的信息能够参阅 标签索引 ,当前手机里图片的归类信息如下(截取部分)。

[Labels(label=团队, subs=[content://media/external/images/media/100064]), Labels(label=手机, subs=[content://media/external/images/media/3224]), Labels(label=摩天轮, subs=[content://media/external/images/media/101122, content://media/external/images/media/3227]), Labels(label=符号, subs=[content://media/external/images/media/5569]), Labels(label=有趣, subs=[content://media/external/images/media/5305, content://media/external/images/media/3332]), Labels(label=新闻, subs=[content://media/external/images/media/100343]), Labels(label=牛仔裤, subs=[content://media/external/images/media/100341]), Labels(label=玩具, subs=[content://media/external/images/media/5487, content://media/external/images/media/97544, content://media/external/images/media/4262, content://media/external/images/media/2182]))]
... 

依据这个信息咱们就能够展现一个简略版别的智能分类场景下的相册列表了。

运用ML Kit 完成相册智能场景分类 (一)

从成果来看,image-labeling 组件的辨认作用仍是挺不错的,关于图画的主要特征能够做出比较好的辨认。 有了图画标签信息,就能够针对这些进行图片选择,依据用户输入的文本进行标签的过滤了。

上述相关实例完整代码能够参阅 Matisse

小结

在没有类似 image-labeling 这类组件的时分,咱们对图画这种媒介的聚类只能依据图片在紧缩编码时带着的信息进行处理。比方拍摄时刻、拍摄地点、图片格式等这种传统信息进行划分和聚类展现。可是,有了 image-labeling 这样的机器学习组件,咱们就能够获取一些本来只有人的肉眼能够辨认的信息了。并且最重要的一点是,这些组件能够离线直接在本地运用,不用将图片上传到服务器,也避免了用户隐私的问题。

依据以上思路咱们基本能够做出一个图片智能分类的 demo 了。分类作用是否足够精确,其实很大程度上依靠所用的模型,模型训练的作用越好,辨认的作用也就越好。

可是除了这个问题,其实还有很多细节需求处理,比方用图画标签组件进行图画辨认很耗时,那么每次用户翻开图片选择器的时分都要从头全量扫码吗 ? 把图画标签信息存储下来,相册里的图片有变动又该怎么处理?下一篇咱们接着解决这些问题。