购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.3 使用Flutter开发人脸检测应用程序

本书在第1章“移动设备深度学习简介”中已经阐释了卷积神经网络(CNN)的工作原理,在第2.2节“图像处理简介”中又介绍了图像处理的底层方式,有了这些基础知识之后,我们就可以学习使用Firebase ML Kit中的预训练模型来检测给定图像中的人脸。

本示例将使用Firebase ML Kit人脸检测API来检测图像中的人脸。Firebase Vision Face Detection API的主要功能如下。

识别并返回检测到的每个人脸的眼睛、耳朵、脸颊、鼻子和嘴巴等面部特征的坐标。

获取检测到的人脸轮廓和面部特征。

检测面部表情,例如一个人是在微笑还是闭着一只眼睛。

获取视频帧中检测到的每个人脸的标识符。此标识符在跨帧调用之间保持一致,可用于对视频流中的特定人脸执行图像处理。

让我们从第一步添加所需的依赖项开始。

2.3.1 添加pub依赖项

我们需要从添加pub依赖项开始。依赖项(dependency)是特定功能工作所需的外部包。应用程序所需的所有依赖项都在pubspec.yaml文件中指定。对于每个依赖项,都应该提及包的名称。通常在其后跟随一个版本号,指定要使用的软件包的版本。此外还需要提供软件包的来源,它告诉pub如何找到该软件包,如果该来源需要任何描述,也可以包含在内。

注意:

要获得特定软件包的信息,可访问以下网址。

https://pub.dartlang.org/packages

用于此项目的依赖项如下。

firebase_ml_vision:这是一个Flutter插件,添加了对Firebase ML Kit功能的支持。

image_picker:这是一个Flutter插件,可以使用相机拍照并从Android或iOS图像库中选择图像。

以下是在包含依赖项后,pubspec.yaml文件的dependencies部分的内容。

为了使用添加到pubspec.yaml文件中的依赖项,我们还需要安装它们,可以通过在终端中运行以下命令来完成。

flutter pub get

也可以单击Get Packages(获取包),该命令位于pubspec.yaml文件顶部操作功能区的右侧。一旦安装了所有依赖项,即可将它们导入项目。

接下来,让我们看看本章示例应用程序的基本功能。

2.3.2 构建应用程序

现在我们来构建应用程序。本章示例应用程序名为Face Detection,它将包含两个屏幕。第一个屏幕有一个文本标题,并包含两个按钮:一个按钮是Gallery(图库),允许用户从设备的图片库中选择图像;另一个按钮是Camera(相机),可使用相机拍摄新图像。

在此之后,用户被引导到第二个屏幕,该屏幕将显示为人脸检测选择的图像,突出显示检测到的人脸。图2-5显示了该应用程序的流程。

图2-5

该应用程序的小部件(Widget)树如图2-6所示。

图2-6

现在让我们详细讨论每个小部件的创建和实现。

2.3.3 创建第一个屏幕

现在我们创建第一个屏幕。第一个屏幕的用户界面将包含一个文本标题Pick Image(选择图像)和两个按钮——Camera(相机)和Gallery(图库)。可以将其视为包含文本标题的列和包含两个按钮的行,如图2-7所示。

图2-7

下文将构建这些元素中的每一个元素,即所谓的小部件(Widget),然后将它们放在一个Scaffold下。

注意:

在英文中,Scaffold(脚手架)是指提供某种支撑的结构或平台。就Flutter而言,Scaffold可以被认为是设备屏幕上的主要结构,所有次要组件——在本示例中就是小部件——都可以放在一起。

在Flutter中,每个用户界面组件都是一个小部件。它们是Flutter框架中的中心类层次结构。如果你以前使用过Android Studio,则可以将小部件视为TextView或Button或任何其他视图组件。

在创建第一个屏幕时,主要的操作如下。

构建行标题。

使用按钮小部件构建行。

创建整个用户界面。

2.3.4 构建行标题

现在来构建行标题。首先在face_detection_home.dart文件中创建一个有状态小部件FaceDetectionHome。FaceDetectionHomeState将包含构建本示例应用程序的第一个屏幕所需的所有方法。

让我们定义一个名为buildRowTitle()的方法来创建文本标题。

该方法用于创建带有标题的小部件(使用title字符串参数传递的值)。该文本通过使用Center()水平对齐到中心,并使用EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0)提供水平填充(8.0)和垂直填充(16.0)。它包含一个子项,用于创建带有标题的Text。将文本的排版样式修改为textTheme.headline以更改文本的默认大小、粗细和间距。

注意:

Flutter使用逻辑像素(logical pixel)作为度量单位,表示它和与设备无关像素(device-independent pixel,dp)是一样的。此外,每个逻辑像素中的设备像素数可以用devicePixelRatio表示。为简单起见,我们将仅使用数字术语来讨论宽度、高度和其他可测量的属性。

2.3.5 使用按钮小部件构建行

接下来要做的是使用按钮小部件构建行。在放置文本标题之后,现在可创建一行,以包含两个按钮,使用户能够从图库中选择图像或使用相机拍摄新图像。

具体操作步骤如下。

(1)首先定义createButton()以创建包含所有必需属性的按钮。

该方法在提供8.0的水平填充后返回一个小部件,即RaisedButton。按钮的颜色设置为blue,按钮文本的颜色设置为white。将splashColor设置为blueGrey,以指示该按钮被点击时将产生涟漪效果。

onPressed中的代码片段在按钮被按下时执行。在这里,我们调用onPickImageSelected(),下文将会定义它。

按钮内显示的文本设置为imgSource,这里可以是Gallery或Camera。

此外,整个代码段都包装在Expanded()中,以确保创建的按钮完全占据所有可用空间。

(2)现在使用buildSelectImageRowWidget()方法构建一行,其中包含两个按钮,对应两个图像源。

在上面的代码片段中,调用了之前定义的createButton()方法,将Camera和Gallery添加为图像源按钮,并将它们添加到该行的children小部件列表中。

(3)现在,让我们定义onPickImageSelected()。该方法使用image_picker库将用户引导到图库或相机以获取图像。

首先,使用if-else块将imageSource设置为相机或图库。如果传入的值为Camera,则图片文件的来源被设置为ImageSource.camera;否则,它被设置为ImageSource.gallery。

一旦确定了图像的来源,pickImage()将用于选择正确的图像来源。如果来源是Camera,则用户将被引导到相机拍摄图像;否则,将指示他们从图库中选择图片。

为了在pickImage()未成功返回图像时处理异常,对该方法的调用包含在try-catch块中。如果发生异常,则通过调用showSnackBar()将执行定向到catch块和Snackbar,并在屏幕上显示错误消息,如图2-8所示。

在成功选择图像并且file变量具有所需的uri后,用户将转到下一个屏幕,也就是FaceDetectorDetail(在创建第二个屏幕时将讨论它),并使用Navigator.push()传递当前上下文,同时将所选文件放入构造函数。在FaceDetectorDetail屏幕上,使用选定的图像填充图像占位符(image holder),并显示有关检测到的人脸的详细信息。

图2-8

2.3.6 创建整个用户界面

现在来创建整个用户界面,所有已创建的小部件都放在build()方法中,该方法在FaceDetectorHomeState类中重写。

在以下代码片段中,为应用程序的第一个屏幕创建了最终的Scaffold。

通过在appBar内设置标题,将工具栏的文本设置为Face Detection。此外,通过将centerTitle设置为true,文本会与中心对齐。

接下来,脚手架的主体是一列小部件。第一个是文本标题,下一个则是一行按钮。

注意:

完整的代码在FaceDetectorHome.dart中,其网址如下。

https://github.com/PacktPublishing/Mobile-Deep-Learning-Projects/blob/master/Chapter2/flutter_face_detection/lib/face_detection_home.dart

2.3.7 创建第二个屏幕

接下来,我们将创建第二个屏幕。在成功获取用户选择的图片后,即可转到应用程序的第二个屏幕,在该屏幕中将显示已选择的图片。此外,还需要使用Firebase ML Kit标记在图像中检测到的面部。

首先,我们需要在新的Dart文件face_detection.dart中创建一个名为FaceDetection的有状态小部件。

创建第二个屏幕的操作如下。

获取图像文件。

分析图像以检测人脸。

标记检测到的人脸。

在屏幕上显示最终图像。

2.3.8 获取图像文件

首先,需要将选中的图像传递到第二个屏幕进行分析。可以使用FaceDetection()构造函数来做到这一点。

注意:

构造函数(constructor)是用于初始化类变量的特殊方法。它们与类具有相同的名称。构造函数没有返回类型,并在创建类的对象时自动调用。

声明一个file变量并使用参数化构造函数对其进行初始化,如下所示。

File file;
FaceDetection(File file){
    this.file = file;
}

接下来,我们将分析图像以检测人脸。

2.3.9 分析图像以检测人脸

现在可以创建一个FirebaseVision人脸检测器的实例,通过它可以分析图像并检测人脸。

具体操作步骤如下。

(1)首先在FaceDetectionState类中创建一个全局的faces变量,如以下代码所示。

List<Face> faces;

(2)现在定义一个detectFaces()方法,在其中实例化FaceDetector。

首先为使用FirebaseVisionImage.fromFile()方法选择的图像文件创建一个名为visionImage的FirebaseVisionImage实例。

接下来,使用FirebaseVision.instance.faceDetector()方法创建FaceDetector实例,并将其存储在名为faceDetector的变量中。

现在使用之前创建的FaceDetector实例faceDetector调用processImage(),并将图像文件作为参数传入。该方法调用将返回一个检测到的人脸列表,该列表存储在名为detectFaces的列表变量中。

请注意,processImage()返回的是Face类型的列表。Face是一个对象,其属性包含检测到的人脸的特征。Face对象具有以下属性。

getLandmark。

hashCode。

hasLeftEyeOpenProbability。

hasRightEyeOpenProbability。

headEulerEyeAngleY。

headEylerEyeAngleZ。

leftEyeOpenProbability。

rightEyeOpenProbability。

smilingProbability。

现在使用for循环遍历人脸列表。我们可以使用detectedFaces[i].smilingProbability获得第i个人脸的smileProbablity值。

我们将它存储在一个名为sleepProbablity的变量中,并使用print()将其值打印到控制台。最后,将全局faces列表的值设置为detectedFaces。

注意:

添加到detectFaces()方法中的async修饰符允许该方法异步执行,这意味着创建一个与主执行线程不同的单独线程。async方法在回调机制上工作,以在执行完成后返回由它计算的值。

为了确保在用户转到第二个屏幕时立即检测到人脸,可以重写initState()并从它内部调用detectFaces()。

@override
void initState() {
    super.initState();
    detectFaces();
}

initState()是小部件创建后调用的第一个方法。

2.3.10 标记检测到的人脸

接下来,我们需要标记检测到的人脸。在检测到图像中存在的所有人脸之后,可通过以下步骤在其周围绘制矩形框。

(1)首先需要将图像文件转换为原始字节。为此,可以定义一个loadImage方法,如下所示。

loadImage()方法将图像文件作为输入。然后使用file.readAsByte()将文件的内容转换为字节并将结果存储在数据中。接下来,调用decodeImageFromList(),它用于将单个图像帧从字节数组加载到Image对象中,并将最终结果值存储在图像中。我们从之前定义的detectFaces()内部调用此方法。

(2)现在定义一个名为FacePainter的CustomPainter类来在所有检测到的人脸周围绘制矩形框。具体代码如下所示。

首先定义3个全局变量:image、faces和rects。

Image类型的image用于获取图像文件的字节格式。

faces是检测到的Face对象List。

image和faces都在FacePainter构造函数中初始化。现在我们遍历人脸,同时使用faces[i].boundingBox获取每个人脸的边界矩形并将其存储在rects列表中。

(3)重写paint()以使用矩形绘制Canvas,具体代码如下所示。

在上面的代码中,首先创建了Paint类的一个实例来描述绘制Canvas的样式,这个Canvas画布其实就是要处理的图像。由于需要在图像上绘制矩形边框,因此将style设置为PaintingStyle.stroke,以仅绘制形状的边缘。

接下来,将strokeWidth(即矩形边框的宽度)设置为8。另外,将边框的color设置为red以绘制红色边框。

最后,使用cavas.drawImage()绘制图像。遍历rects列表中检测到的人脸的每个矩形,并使用canvas.drawRect()绘制矩形。

2.3.11 在屏幕上显示最终图像

成功检测到人脸并在其周围绘制矩形后,便可以在屏幕上显示最终图像。我们首先为第二个屏幕构建最终的Scaffold。可以重写FaceDetectionState中的build()方法以返回Scaffold,具体代码如下所示。

在上面的代码中,首先为屏幕创建appBar,并提供了一个标题:Face Detection。

接下来,指定Scaffold的body。我们首先检查image的值(image存储了所选图像的字节数组)。直到它为空时,我们才能确定检测人脸的过程正在进行。因此,我们使用CircularProgressIndicator()。

一旦检测人脸的过程结束,用户界面就会更新以显示SizedBox(该SizedBox与所选图像具有相同宽度和高度)。SizedBox的child属性设置为CustomPaint,它使用之前创建的FacePainter类在检测到的人脸周围绘制矩形边框。

注意:

完整代码在face_detection.dart中,其网址如下。

https://github.com/PacktPublishing/Mobile-Deep-Learning-Projects/blob/master/Chapter2/flutter_face_detection/lib/face_detection.dart

2.3.12 创建最终的应用程序

最后,我们还需要创建最终的Material Design风格的应用程序(Material Design是Google 2014年发布的面向Android移动设备和桌面平台的设计语言规范)。我们将创建main.dart文件,它可以提供整个代码的执行点。我们还需要创建一个名为FaceDetectorApp的无状态小部件,它用于返回指定标题、主题和主屏幕的MaterialApp。

现在可以通过传入FaceDetectorApp()的实例来定义main()方法,以执行整个应用程序,代码如下所示。

void main() => runApp(new FaceDetectorApp());

注意:

完整代码在main.dart中,其网址如下。

https://github.com/PacktPublishing/Mobile-Deep-Learning-Projects/blob/master/Chapter2/flutter_face_detection/lib/main.dart Im5M3Or1DeMp+ES00Ubp07ahwdJ8t+Mf694XHB7k+xW9W449/x7ISpPPD7skcTp8

点击中间区域
呼出菜单
上一章
目录
下一章
×