65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Google ML Kit 在 Android 上进行人脸检测

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2019年6月26日

CPOL
viewsIcon

16681

通过使用 Firebase ML Kit,开发者为小型公司和个人节省了大量的时间和金钱,而这些时间和金钱本将花费在自己制作 ML 模型上。

大数据和机器学习 (ML) 技术使公司能够开发出复杂、优化的 AI 模型,以构建令人惊叹的新应用程序和服务,例如实时语言翻译器(它们利用输入来学习各种语言)以及在海量图像数据上训练的面部识别程序。

这一切对大公司来说都很好,但小型企业——甚至像你我这样的个人——该如何利用 ML 呢?入门的障碍包括对神经网络、决策树和其他 ML 算法的知识。直到最近,这意味着要付出很多努力去学习,甚至更多的努力去整合开发工具、代码和训练好的算法。

好消息来了!Google 最近推出了一款名为 ML Kit 的新 Firebase SDK,它提供了一种简单的方式供你在应用程序中使用 ML 技术。在本文中,我们将创建一个示例应用程序,让用户可以拍照,交给 ML Kit 进行人脸检测,并利用 ML Kit 提供的元数据来描绘面部特征。你将看到在自己的应用程序中利用 ML 技术是多么容易。

介绍 ML Kit

Firebase 是一个面向移动和 Web 应用程序的开发平台,它提供对 Google 基础设施的便捷访问,帮助快速构建应用程序。 ML Kit(目前处于测试阶段)提供代码和 API,可帮助你在移动应用程序中构建 ML 功能。

假设你想创建自己的 ML 驱动的应用程序。你将不得不创建自己的 ML 模型来处理你的输入(在本例中是图像),这是一个漫长而复杂的过程。例如,假设你想创建一个 ML 模型来识别苹果的图像。

  1. 确定如何表示图像。图像可以被视为像素的二维数组,因此,我们可以将图像转换为 RGB 值二维数组,然后将其输入到我们的 ML 算法中。
  2. 收集数据,在本例中是苹果的图像,我们将用它们来训练和测试我们的 ML 模型。
  3. 准备我们的数据。你可能需要查看所有收集到的图像,并确保图像清晰,包含不同类型的苹果,并确保它们处于不同的位置,以便我们在数据集中创建差异。
  4. 为了简单起见,我们将数据集分成两半,一半用于训练我们的模型,另一半用于测试我们模型的准确性。
  5. 现在是最困难的部分。我们需要选择(或发明)我们自己的 ML 模型,该模型将使用我们自己的算法来处理我们的图像,以识别可能有助于将其识别为对象的图像特征。例如,检测红色像素和圆形物体可能是图像包含苹果的一个好指标。
  6. 开始用我们的图像训练我们的模型,这样模型就会根据你的数据逐渐建立一个“方程式”,你可以用它来确定任何图像是否为苹果。

在步骤 5 和 6 中花费了大量时间,我们在其中尝试微调模型以识别更好的特征,然后测试它对准确性的影响。上面我们讨论的是创建模型所需工作的一个非常简单、高层级的列表。创建准确可靠的 ML 模型还需要许多其他步骤。

ML Kit 预装了 ML 功能,包括图像中的文本识别、人脸检测、条形码和 QR 码扫描、对象检测、标记和跟踪、语言识别和翻译,以及智能回复(你可能在 Gmail 中看到过)。它还提供 AutoML 模型推理和自定义模型推理,允许你训练自己的 ML 模型。

对于大多数功能,SDK 使用你的手机来应用图像识别。然而,一些功能还可以选择向 Google 的 Cloud API 发出请求,以处理更复杂的处理。

理解面部识别

在本文中,我们将使用 ML Kit 对象检测功能来实现一个面部检测应用程序。但是,在我们开始之前,应该区分面部检测和面部识别。

面部检测非常直接。它只是意味着我们的 ML 模型将图像中的某物识别为人脸。

面部识别将其提升到一个新的水平,并允许我们识别个体面孔和面部特征。面部识别要复杂得多,因为我们现在必须创建一个能够识别面孔并识别模型特定面部特征细节以识别特定个人的 ML 模型。创建训练数据涉及大量工作,实现所有特征细节识别的工作量更大。

ML Kit 提供了广泛的人脸检测功能。在本文中,我们将特别探讨人脸轮廓检测功能。除了仅识别图像中人脸的存在之外,ML Kit 的人脸检测还包括检测特定面部特征(眼睛、耳朵、脸颊、鼻子和嘴巴)的位置、识别表情、跟踪视频帧中的人脸以及在视频流中进行实时检测。要了解更多信息,请参阅 Firebase 文档中的 人脸检测 指南。

硬件和软件要求

现在我们对机器学习有了一些了解,让我们来看看我们的硬件和软件。在我们构建的应用程序中,我们将只进行人脸检测。但是,我们将完全在设备上使用 Arm CPU 驱动的智能手机进行。由于我们在本地进行,因此不会将任何数据发送到 Google Cloud 的 API 服务。

直接在手机上运行代码可以提供高性能的提升。如果代码在 Google Cloud 上运行,可能会由于网络带宽和延迟限制而成为瓶颈。在本地运行的另一个原因是它对用户隐私更好,因为不会将人脸的任何详细信息传输出设备。

我们将使用 Kotlin 在 Android Studio 中创建我们的应用程序,并将在三星 Galaxy S10 上进行测试。我们选择 Kotlin 是因为它允许我们利用底层的 Java 功能,并具有更简洁的语法。

我选择 Galaxy S10 作为我们的测试手机。虽然这项技术应该适用于任何处理器,但为了获得最佳性能,你需要一台至少配备 Cortex-A75 CPU 或更好的 Cortex-A76 CPU 的设备。虽然 Cortex-A75 已经表现出色,但 Cortex-A76 在机器学习工作负载方面效率更高,速度可达四倍。

除了创建 ML 优化的硬件,Arm 还与 Google 合作优化 Arm 驱动的 Android 设备上的 ML 性能。Arm 的软件工程师一直在努力将 Arm Compute LibraryArm NN 后端与 Android NN API 集成。ML Kit 构建在 Tensorflow Lite 之上,而 Tensorflow Lite 在底层使用 Android NN。

创建一个 Android Studio 项目

现在你对正在使用的硬件优化有了更多的了解,让我们回到软件方面,并最终开始创建我们的应用程序!要使用 ML Kit,我们需要 安装 SDK

首先,让我们创建我的 Android Studio 项目。我将把新项目命名为 ML Kit

教程,它将使用 Kotlin,而不是 Java。

现在 创建一个 Firebase 项目,你的应用程序将连接到该项目以访问 ML Kit。

将你的应用程序注册到你的 Firebase 项目。你需要设置你的应用程序 ID,可以在位于应用模块的 build.gradle 中找到。

接下来,你将被要求下载一个 google-services.json 文件,并将其添加到 Project 视图中应用模块的根目录。此 JSON 文件是安全过程的一部分,以确保你的应用程序有权访问你的 Firebase 项目。

现在我们可以开始向位于我们应用程序和应用模块根目录的 build.gradle 文件添加依赖项了。

第一个依赖项,我们想添加到根目录的 build.gradle 文件中的 google-services 如下所示:

dependencies {
    …
    classpath 'com.google.gms:google-services:4.2.0'
    // NOTE: Do not place your application dependencies here; 
    // they belong in the individual module build.gradle files
}

接下来,在应用级别的 build.gradle 中,我们需要添加我们的 google-services 和我们想要使用的 ML Kit SDK 服务。在本例中,服务是面部模型。

dependencies {
    …
    implementation 'com.google.firebase:firebase-core:16.0.9'
    implementation 'com.google.firebase:firebase-ml-vision:20.0.0'
    implementation 'com.google.firebase:firebase-ml-vision-face-model:17.0.2'
}

apply plugin: 'com.google.gms.google-services'

最后,在编辑了两个 Gradle 文件后,我们需要重新同步,这样 Gradle 才能下载你刚刚添加的所有依赖项。

完成此操作后,我们就拥有了创建面部检测应用程序所需的一切!

创建应用程序

为了构建应用程序,在使用时我们将有五个步骤:

  1. 单击一个按钮,使我们的应用程序能够拍照。
  2. 将图片添加到应用程序中。
  3. 单击另一个按钮,使用 ML Kit 检测图像中人脸的位置。
  4. 显示带有图形叠加点的图像,显示 ML Kit 认为人脸所在的位置。
  5. 显示一条消息,显示 ML Kit 认为该人微笑的可能性。

最后,我们将得到类似这样的东西。

对于这个应用程序,我们希望通过 Google 的 Camera API 获取图像。为了简单起见,我选择使用内置的 Android 相机应用 来拍照,而不是创建自己的相机应用程序。

这个示例应用程序由四个文件组成:Activity_main.xmlMainActivity.ktGraphicOverlay.javaFaceContourGraphic.kt。为了简单起见,后两个文件是从 GitHub 上的 Firebase for Android 的快速入门示例 存储库借用的。(请注意,我用于此示例的代码版本已在 2019 年 3 月 14 日的存储库中更新。由于存储库未来的更新可能会引入破坏性更改,我将提供下面确切版本的链接。)

Activity_main.xml 设置应用程序。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:id="@+id/happiness"
            android:layout_alignParentBottom="true"
            android:layout_marginBottom="82dp"
    />
    <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            tools:layout_editor_absoluteY="27dp"
            tools:layout_editor_absoluteX="78dp"
            android:id="@+id/imageView"/>
    <com.example.mlkittutorial.GraphicOverlay
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/graphicOverlay"
            android:layout_alignParentStart="true"
            android:layout_alignParentTop="true"
            android:layout_marginStart="0dp"
            android:layout_marginTop="0dp"/>
    <Button
            android:text="Take Picture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            tools:layout_editor_absoluteY="421dp"
            android:onClick="takePicture"
            tools:layout_editor_absoluteX="10dp"
            android:id="@+id/takePicture"
            android:visibility="visible"
            android:enabled="true"
            android:layout_marginBottom="16dp"
            android:layout_alignParentStart="true"
            android:layout_marginStart="61dp"
            android:layout_alignParentBottom="true" />
    <Button
            android:text="Detect Face"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentEnd="true"
            android:layout_marginEnd="61dp"
            android:layout_alignParentBottom="true"
            android:id="@+id/detectFace"
            android:layout_marginBottom="16dp"
            android:onClick="detectFace"
            android:visibility="visible"
            android:enabled="false"/>
</RelativeLayout>

不必太担心数字,我的 UI 位置大多是凭感觉估算的,所以如果你愿意,可以自由修改它们。

需要注意的是,我们创建了自己的自定义视图 GraphicOverlay。稍后我们将详细讨论此视图,它负责绘制用户面部特征的坐标。

MainActivity.kt 提供了应用程序的代码。

package com.example.mlkittutorial

import android.content.Intent
import android.content.res.Resources
import android.graphics.Bitmap
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.provider.MediaStore
import android.view.View
import com.google.firebase.ml.vision.FirebaseVision
import com.google.firebase.ml.vision.common.FirebaseVisionImage
import com.google.firebase.ml.vision.face.FirebaseVisionFace
import com.google.firebase.ml.vision.face.FirebaseVisionFaceDetectorOptions
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    private val requestImageCapture = 1
    private var cameraImage: Bitmap? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    /** Receive the result from the camera app */
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (requestCode == requestImageCapture && resultCode == RESULT_OK && data != null && data.extras != null) {
            val imageBitmap = data.extras.get("data") as Bitmap

            // Instead of creating a new file in the user's device to get a full scale image
            // resize our smaller imageBitMap to fit the screen
            val width = Resources.getSystem().displayMetrics.widthPixels
            val height = width / imageBitmap.width * imageBitmap.height
            cameraImage = Bitmap.createScaledBitmap(imageBitmap, width, height, false)

            // Display the image and enable our ML facial detection button
            imageView.setImageBitmap(cameraImage)
            detectFace.isEnabled = true
        }
    }

    /** Callback for the take picture button */
    fun takePicture(view: View) {
        // Take an image using an existing camera app
        Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
            takePictureIntent.resolveActivity(packageManager)?.also {
                startActivityForResult(takePictureIntent, requestImageCapture)
                happiness.text = ""
                graphicOverlay.clear()
            }
        }
    }

    /** Callback for the detect face button */
    fun detectFace(view: View) {
        // Build the options for face detector SDK
        if (cameraImage != null) {
            val image = FirebaseVisionImage.fromBitmap(cameraImage as Bitmap)
            val builder = FirebaseVisionFaceDetectorOptions.Builder()
            builder.setContourMode(FirebaseVisionFaceDetectorOptions.ALL_CONTOURS)
            builder.setClassificationMode(FirebaseVisionFaceDetectorOptions.ALL_CLASSIFICATIONS)
            val options = builder.build()

            // Send our image to be detected by the SDK
            val detector = FirebaseVision.getInstance().getVisionFaceDetector(options)
            detector.detectInImage(image).addOnSuccessListener { faces ->
                displayImage(faces)
            }
        }
    }

    /** Draw a graphic overlay on top of our image */
    private fun displayImage(faces: List<FirebaseVisionFace>) {
        graphicOverlay.clear()
        if (faces.isNotEmpty()) {
            // We will only draw an overlay on the first face
            val face = faces[0]
            val faceGraphic = FaceContourGraphic(graphicOverlay, face)
            graphicOverlay.add(faceGraphic)
            happiness.text = "Smile Probability: " + (face.smilingProbability * 100) + "%"
        } else {
            happiness.text = "No face detected"
        }
    }
}

注释应该是自给自足的。然而,代码的核心逻辑始于 takePicture()

  1. 当用户单击“拍照”按钮时,我们会运行 takePicture() 中的代码,其中我们会触发一个意图来启动 Android 的相机应用程序。这会拍一张照片并将数据返回给我们。
  2. 我们在 onActivityResult() 中收回图像,在那里我们将图像调整大小以适应我们的屏幕,并启用“检测人脸”按钮。
  3. 当用户单击“检测人脸”按钮时,我们会运行 detectFace(),在那里我们获取之前的图像,然后将其发送到 ML Kit 来检测用户的人脸。当人脸检测成功后,我们会得到一个面部信息列表,我们在 displayImage() 中进行处理。
  4. displayImage() 中,事情会变得有点棘手。我们只取图像中的第一张脸,并使用一个名为 FaceContourGraphic 的类创建一个 Graphic,我们将其添加到我们的 GraphicOverlay 中(稍后将详细介绍这两个类),以绘制我们面部特征的位置。

为了完成这个应用程序,我们需要在我们的相机图像之上绘制一个叠加层。ML Kit 返回一个 Face 对象,它提供 轮廓点 的 2D 坐标,我们可以在此绘制用户面部特征的位置。

虽然我们现在有了用户面部特征位置的信息,但 ML Kit 并没有提供实际绘制这些点的解决方案。对我们来说幸运的是,在这个 ML Kit 示例 中,有一些示例代码我们可以利用来绘制这些轮廓点。

解决这个谜题的第一个关键是 Firebase Android 示例存储库中的 GraphicOverlay.java。此类只是一个视图容器,它将绘制我们可以添加到相机图像之上的图形。

实际的坐标绘制是在 FaceContourGraphic.kt 中完成的,它也来自 Firebase 示例存储库。此类接受 ML Kit 提供的 Face 对象,然后使用坐标点在 GraphicOverlay 的画布上绘制轮廓点。

有了这些,我们就拥有了一个功能齐全的应用程序,该应用程序可以从相机捕获图像,然后使用 ML Kit 提供给我们的数据,在我们的人脸特征之上绘制轮廓点!

你可能还会注意到,应用程序中的执行速度非常快,最多只需几秒钟。令人印象深刻!

结论

在当今时代,能够在应用程序中利用 ML 功能可以为你带来巨大的竞争优势。不幸的是,并非每个人都具备将训练好的 ML 模型集成到其 AI 驱动的应用程序中的技能。

Google 通过提供一套工具来帮助小型公司填补这一空白,使开发人员能够在常见场景中使用 ML,例如图像和文本识别。

通过使用 ML Kit,开发者为小型公司和个人节省了大量的时间和金钱,而这些时间和金钱本将花费在自己制作 ML 模型上。收集数据、实现算法然后训练模型的过程可能需要数月时间,但通过使用 ML Kit,你可以在短短 20 分钟内获得结果!

© . All rights reserved.