在iOS11中使用Core ML 和TensorFlow对手势进行智能识别

简介: 在计算机科学中,手势识别是通过数学算法来识别人类手势的一个议题。用户可以使用简单的手势来控制或与设备交互,让计算机理解人类的行为。 这篇文章将带领你实现在你自己的应用中使用深度学习来识别复杂的手势,比如心形、复选标记或移动设备上的笑脸。

在计算机科学中,手势识别是通过数学算法来识别人类手势的一个议题。用户可以使用简单的手势来控制或与设备交互,让计算机理解人类的行为。

这篇文章将带领你实现在你自己的应用中使用深度学习来识别复杂的手势,比如心形、复选标记或移动设备上的笑脸。我还将介绍和使用苹果的Core ML框架(iOS11中的新框架)。

b570ffa12918501701e3bcf98cf80ea4498e89a4


在屏幕上随便划动两下,手机就会对复杂的手势进行实时识别

这项技术使用机器学习来识别手势。本文中的一些内容是特定于iOS系统的,但是Android开发者仍然可以找到一些有用的信息。

完成项目的源代码:https://github.com/mitochrome/complex-gestures-demo

我们将构建什么?
在本教程结束时,我们将有一个设置,让我们可以选择完全自定义的手势,并在iOS应用中非常准确地识别它们。

  • 一个APP收集每个手势的一些例子(画一些复选标记或者心形,等等)。
  • 一些Python脚本用于训练机器学习算法(下面将会解释),以识别手势。我们将使用TensorFlow,稍后会讲到。
  • 这款APP可以使用自定义手势。记录用户在屏幕上的动作,并使用机器学习算法来找出它们所代表的手势。

    1fdbb61ee172e68e754fe1b2b77377590ec9634f

我们所画的手势将用于训练机器学习算法,我们将用Core ML来评估应用内(in-app)的算法

什么是机器学习算法?
机器学习算法从一组数据中学习,以便根据其他数据的不完整的信息作出推断。

在我们的例子中,数据是用户及其相关的手势类(“心形”、“复选标记”等)在屏幕上做出的划动。我们想要推断的是,在我们不知道手势类(不完整的信息)的情况下,用户所画出的东西是什么。

允许一种算法从数据中学习,称为“训练”。对数据进行建模的推理机器被恰当地称为“模型”。

什么是Core ML?
机器学习模型可能是复杂的,(尤其是在移动设备上)评估是非常缓慢的。在iOS 11中,苹果引入了Core ML,这是一种新的框架,使其快速并易于实现。对于Core ML,实现一个模型主要是为了在Core ML模型格式(.mlmodel)中保存它。

Core ML的详细介绍,请参阅:https://developer.apple.com/documentation/coreml

使用官方的Python包coremltools,可以方便地保存mlmodel文件。它有针对Caffe、Keras、LIBSVM、scikit-learn和XCBoost模型的转换器,以及当那些还没有足够能力(例如使用TensorFlow时)的低级别API。但要注意的是,coremltools目前需要Python的2.7版本。coremltools地址:https://pypi.python.org/pypi/coremltools

2029c17a513dbe3b98cac6f222056ef1f2dbdba9

支持的格式可以通过使用coremltools自动转换成Core ML模型。像TensorFlow这样的不支持格式需要更多的手动操作来完成。

注意:Core ML只支持在设备上评估模型,而不是训练新模型。

1.生成数据集
首先,让我们确保我们的机器学习算法有一些数据(手势)来学习。为了生成一个真实的数据集,我编写了一个名为“GestureInput”的iOS应用,用于在设备上输入手势。它允许你输入大量的笔画,然后预览所生成的图像,并将其添加到数据集中。你还可以修改相关的类(称为标签)并且删除示例。

当我想要改变它们显示的频率时(例如,当向现有的数据集添加一个新的类时),我将更改硬编码的值并重新编译。尽管看起来不是很漂亮,但很管用。

硬编码的值:https://github.com/mitochrome/complex-gestures-demo/blob/ddaef7401cf3024c2df0a0af5883bbf2e7fac12a/apps/GestureInput/Source/InputViewController.swift#L8

34c029ec1063996a7b8f079ddc48a4e1e54a4baf


为机器学习算法生成数据

项目的自述文件解释了如何修改手势类的集合,包括复选标记、x标记、“涂鸦”(在上下移动时快速的侧向运动)、圆形、U形、心形、加号、问号、大写A、大写B、笑脸和悲伤的表情。还包括一个样本数据集,你可以将它传输到你的设备上。

样本数据集:https://github.com/mitochrome/complex-gestures-demo/tree/ddaef7401cf3024c2df0a0af5883bbf2e7fac12a/sample_data

输出训练
GestureInput中的“Rasterize”按钮将用户画的图案转换为图像,并将其保存到一个名为data.trainingset的文件中。这些图像就是我们要输入的算法。

缩放并翻译用户的手势(“绘画”)来适应一个固定大小的方框,然后将其转换为灰度图像。这有助于让我们的手势独立地识别用户的手势位置和大小。它还最小化了代表空白空间的图像像素的数量。参考:https://hackernoon.com/a-new-approach-to-touch-based-mobile-interaction-ba47b14400b0

64be3e9769fa54da20dc41109d110f532b55a172


将用户画出的图案转换成一个灰度图像来输入我们的机器学习算法

请注意,我仍然在另一个文件中存储每次笔画的触摸位置的原始时间序列。这样,我就可以改变手势在未来转换成图像的方式,甚至可以使用非基于图像的方法来识别,而不用再画出所有的手势。手势输入在它的container文档文件夹中保存数据集。从你的设备上获取数据的最简单方法是通过Xcode下载container。

下载地址:https://stackoverflow.com/questions/6121613/browse-the-files-created-on-a-device-by-the-ios-application-im-developing-on-w/28161494#28161494

2.训练一个神经网络
目前,最先进的图像分类机器学习算法是卷积神经网络(CNNs)。我们将用TensorFlow训练一个CNNs,并在我们的APP中使用它。

我的神经网络是基于“Deep MNIST for Experts”的TensorFlow教程所使用的。

教程地址:https://www.tensorflow.org/get_started/mnist/pros

我用来训练和导出模型的一组脚本在一个叫做“gesturelearner”的文件夹中。

文件夹地址:https://github.com/mitochrome/complex-gestures-demo/tree/master/gesturelearner

我将讨论典型的用例,但是它们有一些额外的以virtualenv开头的命令行选项可能是有用的:

cd /path/to/gesturelearner 

# Until coremltools supports Python 3, use Python 2.7. 

virtualenv -p $(which python2.7) venv 

pip install -r requirements.txt

准备数据集
首先,我使用filter.py将数据集分成15%的“测试集”和85%的“训练集”。

# Activate the virtualenv. 

source /path/to/gesturelearner/venv/bin/activate 

# Split the data set. 

python /path/to/gesturelearner/filter.py --test-fraction=0.15 

data.trainingset

训练集当然是用来训练神经网络的。测试集的目的是为了说明神经网络的学习是如何对新数据进行归纳的。

我选择把15%的数据放在测试集中,如果你只有几百个手势例子,那么15%的数字将是一个相当小的数字。这意味着测试集的准确性只会让你对算法的表现有一个大致的了解。

训练
在把我的自定义.trainingset格式变为TensorFlow喜欢的TFRecords格式之后,我使用train.py来训练一个模型。我们给神经网络提供了有力的分类,它在未来会遇到新的手势。

train.py列印出它的进程,然后定期保存一个TensorFlow Checkpoint文件,并在测试集上测试它的准确性(如果指定的话)。

# Convert the generated files to the TensorFlow TFRecords format. 

python /path/to/gesturelearner/convert_to_tfrecords.py 

data_filtered.trainingset 

python /path/to/gesturelearner/convert_to_tfrecords.py 

data_filtered_test.trainingset 

# Train the neural network. 

python /path/to/gesturelearner/train.py --test-

file=data_filtered_test.tfrecords data_filtered.tfrecords

训练应该很快,在一分钟内达到98%的准确率,在大约10分钟后完成。

fa846fbddc02deeb0ff1d6a1e6f6b56da0396588


训练神经网络

如果你在训练中退出了train.py,你可以稍后重新启动,它将加载checkpoint文件以获取它所处的位置,它还可以选择从哪里加载模型以及保存它的位置。

用不平衡数据训练
如果你的手势比其他手势有更多的例子,那么网络就会倾向于学会以牺牲其他手势为代价来识别更好的手势。有几种不同的方法来应对这个问题:

  • 神经网络是通过最小化与制造错误相关的成本函数来训练的。为了避免忽略某些类,你可以增加错误分类的成本。
  • 包含一些较少代表性(less-represented)的手势的副本,这样你的所有手势的数量都是相等的。
  • 删除一些更有代表性(more-represented)的手势的例子。

我的代码并不是开箱即用的,但是它们应该相对容易实现。

输出到Core ML
Core ML没有一个用于将TensorFlow模型转换为Core ML的ML模型的“转换器”。这就给我们提供了两种把我们的神经网络转换成一个ML模型的方法:

  • 使用一个用于构建神经网络的API的coremltools.模型包。

模型包地址:https://pypi.python.org/pypi/coremltools
API地址:https://apple.github.io/coremltools/generated/coremltools.models.neural_network.html

  • 由于MLModel说明是基于Google的protocol buffers,所以你可以跳过coremltools,然后直接在任何编程语言中使用protobuf。Google的protocol buffers

地址:https://developers.google.com/protocol-buffers/

到目前为止,除了在现有的转换器的内部代码之外,在web上似乎没有找到任何方法的例子。下面是我使用coremltools的示例的精简版:

01
1 from coremltools.models import MLModel
02
2 from coremltools.models.neural_network import NeuralNetworkBuilder
03
3 import coremltools.models.datatypes as datatypes
04
4
05
  
06
5 # ...
07
6
08
  
09
7 def make_mlmodel(variables):
10
8     # Specify the inputs and outputs (there can be multiple).
11
9     # Each name corresponds to the input_name/output_name of a layer in the network so
12
10     # that Core ML knows where to insert and extract data.
13
11     input_features = [('image', datatypes.Array(1, IMAGE_HEIGHT, IMAGE_WIDTH))]
14
12     output_features = [('labelValues', datatypes.Array(NUM_LABEL_INDEXES))]
15
13     builder = NeuralNetworkBuilder(input_features, output_features, mode=None)
16
14
17
  
18
15     # The "name" parameter has no effect on the function of the network. As far as I know
19
16     # it's only used when Xcode fails to load your mlmodel and gives you an error telling
20
17     # you what the problem is.
21
18     # The input_names and output_name are used to link layers to each other and to the
22
19     # inputs and outputs of the model. When adding or removing layers, or renaming their
23
20     # outputs, always make sure you correct the input and output names of the layers
24
21     # before and after them.
25
22     builder.add_elementwise(name='add_layer',
26
23                             input_names=['image'], output_name='add_layer', mode='ADD',
27
24                             alpha=-0.5)
28
25
29
  
30
26     # Although Core ML internally uses weight matrices of shape
31
27     # (outputChannels, inputChannels, height, width) (as can be found by looking at the
32
28     # protobuf specification comments), add_convolution takes the shape
33
29     # (height, width, inputChannels, outputChannels) (as can be found in the coremltools
34
30     # documentation). The latter shape matches what TensorFlow uses so we don't need to
35
31     # reorder the matrix axes ourselves.
36
32     builder.add_convolution(name='conv2d_1', kernel_channels=1,
37
33                             output_channels=32, height=3, width=3, stride_height=1,
38
34                             stride_width=1, border_mode='same', groups=0,
39
35                             W=variables['W_conv1'].eval(), b=variables['b_conv1'].eval(),
40
36                             has_bias=True, is_deconv=False, output_shape=None,
41
37                             input_name='add_layer', output_name='conv2d_1')
42
38
43
  
44
39     builder.add_activation(name='relu_1', non_linearity='RELU', input_name='conv2d_1',
45
40                            output_name='relu_1', params=None)
46
41
47
  
48
42     builder.add_pooling(name='maxpool_1', height=2, width=2, stride_height=2,
49
43                         stride_width=2, layer_type='MAX', padding_type='SAME',
50
44                         input_name='relu_1', output_name='maxpool_1')
51
45
52
  
53
46     # ...
54
47
55
  
56
48     builder.add_flatten(name='maxpool_3_flat', mode=1, input_name='maxpool_3',
57
49                         output_name='maxpool_3_flat')
58
50
59
  
60
51     # We must swap the axes of the weight matrix because add_inner_product takes the shape
61
52     # (outputChannels, inputChannels) whereas TensorFlow uses
62
53     # (inputChannels, outputChannels). Unlike with add_convolution (see the comment
63
54     # above), the shape add_inner_product expects matches what the protobuf specification
64
55     # requires for inner products.
65
56     builder.add_inner_product(name='fc1',
66
57                               W=tf_fc_weights_order_to_mlmodel(variables['W_fc1'].eval())
67
58                                 .flatten(),
68
59                               b=variables['b_fc1'].eval().flatten(),
69
60                               input_channels=6*6*64, output_channels=1024, has_bias=True,
70
61                               input_name='maxpool_3_flat', output_name='fc1')
71
62
72
  
73
63     # ...
74
64
75
  
76
65     builder.add_softmax(name='softmax', input_name='fc2', output_name='labelValues')
77
66
78
  
79
67     model = MLModel(builder.spec)
80
68
81
  
82
69     model.short_description = 'Model for recognizing a variety of images drawn on screen with one\'s finger'
83
70
84
  
85
71     model.input_description['image'] = 'A gesture image to classify'
86
72     model.output_description['labelValues'] = 'The "probability" of each label, in a dense array'
87
73
88
  
89
74     return model

使用它:

# Save a Core ML .mlmodel file from the TensorFlow checkpoint

model.ckpt. 

python /path/to/gesturelearner/save_mlmodel.py model.ckpt

完整的代码:https://github.com/mitochrome/complex-gestures-demo/blob/ddaef7401cf3024c2df0a0af5883bbf2e7fac12a/gesturelearner/gesturelearner/graph.py#L113

必须编写这种转换代码的一个副作用是,我们将整个网络描述为两个位置(TensorFlow代码位置和转换代码位置)。每当我们更改TensorFlow图时,我们就必须同步转换代码以确保我们的模型正确地导出。

希望将来苹果能开发出一种更好的输出TensorFlow模型的方法。而在Android上,你可以使用官方的Tensorflow API。

此外,谷歌还将发布一款名为TensorFlow Lite的移动优化版本的TensorFlow。

3.在应用内识别手势
最后,让我们把我们的模型放到一个面向用户的APP中,这个项目的一部分是手势识别(GestureRecognizer。

项目地址:https://github.com/mitochrome/complex-gestures-demo/tree/ddaef7401cf3024c2df0a0af5883bbf2e7fac12a/apps

一旦你有了一个mlmodel文件,就可以将它添加到Xcode中的一个目标。你将需要运行Xcode 9。

Xcode 9将编译任何向目标添加的mlmodel文件,并为它们生成Swift类。我将我的模型命名为GestureModel,因此Xcode生成了GestureModel, GestureModelInput和GestureModelOutput这三个类。

我们需要将用户的手势转换成GestureModel接受的格式。这意味着要将这个手势转换成灰度图像,就像我们在步骤1中所做的那样。然后,Core ML要求我们将灰度值数组转换为多维数组类型,MLMultiArray。

MLMultiArray:https://developer.apple.com/documentation/coreml/mlmultiarray

01
1 /**
02
2  * Convert the `Drawing` into a binary image of format suitable for input to the
03
3  * GestureModel neural network.
04
4  *
05
5  * - returns: If successful, a valid input for GestureModel
06
6  */
07
7 func drawingToGestureModelFormat(_ drawing: Drawing) -> MLMultiArray? {
08
8     guard let image = drawing.rasterized(), let grays = imageToGrayscaleValues(image: image) else {
09
9         return nil
10
10     }
11
11     
12
12     guard let array = try? MLMultiArray(
13
13         shape: [
14
14             1,
15
15             NSNumber(integerLiteral: Int(image.size.width)),
16
16             NSNumber(integerLiteral: Int(image.size.height))
17
17         ],
18
18         dataType: .double
19
19         ) else {
20
20             return nil
21
21     }
22
22     
23
23     let doubleArray = array.dataPointer.bindMemory(to: Float64.self, capacity: array.count)
24
24     
25
25     for i in 0 ..< array.count {
26
26         doubleArray.advanced(by: i).pointee = Float64(grays[i]) / 255.0
27
27     }
28
28     
29
29     return array
30
30 }

MLMultiArray就像一个围绕一个原始数组的包装器(wrapper),它告诉了Core ML它包含什么类型以及它的形状(例如维度)是什么。有了一个MLMultiArray,我们可以评估我们的神经网络。

01
1 /**
02
2  * Convert the `Drawing` into a grayscale image and use a neural network to compute
03
3  * values ("probabilities") for each gesture label.
04
4  *
05
5  * - returns: An array that has at each index `i` the value for
06
6  * `Touches_Label.all[i]`.
07
7  */
08
8 func predictLabel(drawing: Drawing) -> [Double]? {
09
9     // Convert the user's gesture ("drawing") into a fixed-size grayscale image.
10
10     guard let array = drawingToGestureModelFormat(drawing) else {
11
11         return nil
12
12     }
13
13     
14
14     let model = GestureModel.shared
15
15     
16
16     // The GestureModel convenience method prediction(image:) wraps our image in
17
17     // a GestureModelInput instance before passing that to prediction(input:).
18
18     // Both methods return a GestureModelOutput with our output in the
19
19     // labelValues property. The names "image" and "labelValues" come from the
20
20     // names we gave to the inputs and outputs of the .mlmodel when we saved it.
21
21     guard let labelValues = try? model.prediction(image: array).labelValues else {
22
22         return nil
23
23     }
24
24     
25
25     // Convert the MLMultiArray labelValues into a normal array.
26
26     let dataPointer = labelValues.dataPointer.bindMemory(to: Double.self, capacity: labelValues.count)
27
27     return Array(UnsafeBufferPointer(start: dataPointer, count: labelValues.count))
28
28 }

我使用了一个GestureModel的共享实例,因为每个实例似乎都要花费很长的时间来分配。事实上,即使在创建实例之后,这个模型第一次评估的速度也很慢。当应用程序启动时,我用一个空白图像对网络进行评估,这样用户在开始做手势时不会看到延迟。

避免手势冲突
由于我使用的一些手势类彼此包含(笑脸与U形嘴相包含,x标记与上升的对角相包含),所以当用户想要绘制更复杂的图形时,可能会贸然地识别出更简单的手势。

为了减少冲突,我使用了两个简单的规则:

  • 如果一个手势能构成更复杂的手势的一部分,那么就可以暂时延迟它的识别,看看用户是否能做出更大的手势。
  • 考虑到用户的笔画数,一个还未被完全画出的手势(例如,一张笑脸需要至少画三笔:一张嘴巴和两只眼睛)是不能被识别的。

结语
就是这样!有了这个设置,你可以在大约20分钟内给你的iOS应用添加一个全新的手势(输入100张图片,训练达到99.5+%的准确率,并且把模型导出)。

要查看这些片段是如何组合在一起的,或者在你自己的项目中使用它们的话,请参阅完整的源代码:https://github.com/mitochrome/complex-gestures-demo

本文为编译作品,转载请注明出处。更多内容关注公众号:atyun_com

目录
相关文章
|
6月前
|
算法 计算机视觉 iOS开发
iOS 实时图像处理技术:使用 Core Image 和 Metal 进行高效滤镜应用
【4月更文挑战第8天】 在移动设备上实现高效的图像处理功能是现代应用程序开发中的一个关键需求。苹果的iOS平台提供了Core Image和Metal两大技术,它们为开发者提供了强大的工具来实现复杂的图像处理任务。本文将探讨如何使用Core Image进行基础图像处理,并结合Metal的性能优势,开发出一个自定义的实时图像滤镜。我们将通过创建一个能够动态调整参数并且具有实时反馈效果的滤镜来演示这一过程。
|
6月前
|
算法 计算机视觉 iOS开发
iOS 实时图像处理技术:Core Image 框架的应用
【4月更文挑战第8天】 在移动设备上实现高效的图像处理功能,对于提升用户体验和扩展应用程序能力至关重要。苹果公司的iOS平台提供了强大的Core Image框架,它允许开发者以高效和直观的方式执行复杂的图像处理任务。本文将深入探讨Core Image框架的关键特性,并通过实例演示如何在iOS应用中集成实时图像处理功能,不仅提高性能,同时保持了电池寿命的优化。我们将重点讨论面部识别、滤镜应用和性能优化等关键技术点,为读者提供一份全面的iOS图像处理指南。
|
6月前
|
存储 数据建模 iOS开发
iOS设备功能和框架: 什么是 Core Data,它在 iOS 中的作用是什么?
iOS设备功能和框架: 什么是 Core Data,它在 iOS 中的作用是什么?
100 1
|
6月前
|
定位技术 iOS开发
iOS设备功能和框架: 如何使用 Core Location 获取设备的位置信息?
iOS设备功能和框架: 如何使用 Core Location 获取设备的位置信息?
75 0
|
6月前
|
机器学习/深度学习 算法 TensorFlow
文本分类识别Python+卷积神经网络算法+TensorFlow模型训练+Django可视化界面
文本分类识别Python+卷积神经网络算法+TensorFlow模型训练+Django可视化界面
121 0
文本分类识别Python+卷积神经网络算法+TensorFlow模型训练+Django可视化界面
|
6月前
|
机器学习/深度学习 Dart TensorFlow
TensorFlow Lite,ML Kit 和 Flutter 移动深度学习:6~11(5)
TensorFlow Lite,ML Kit 和 Flutter 移动深度学习:6~11(5)
150 0
|
3月前
|
人工智能 自然语言处理 云计算
iOS迎来AI升级:揭秘Apple全新“智能”系统
iOS迎来AI升级:揭秘Apple全新“智能”系统
iOS迎来AI升级:揭秘Apple全新“智能”系统
|
3月前
|
API UED 开发者
如何在Uno Platform中轻松实现流畅动画效果——从基础到优化,全方位打造用户友好的动态交互体验!
【8月更文挑战第31天】在开发跨平台应用时,确保用户界面流畅且具吸引力至关重要。Uno Platform 作为多端统一的开发框架,不仅支持跨系统应用开发,还能通过优化实现流畅动画,增强用户体验。本文探讨了Uno Platform中实现流畅动画的多个方面,包括动画基础、性能优化、实践技巧及问题排查,帮助开发者掌握具体优化策略,提升应用质量与用户满意度。通过合理利用故事板、减少布局复杂性、使用硬件加速等技术,结合异步方法与预设缓存技巧,开发者能够创建美观且流畅的动画效果。
80 0
|
4月前
|
Android开发 数据安全/隐私保护 iOS开发
探索未来:安卓与iOS在智能穿戴设备领域的较量
随着科技的飞速发展,智能穿戴设备已逐渐成为我们日常生活的一部分。从健康监测到通讯交流,它们正以惊人的速度改变着我们的生活方式。本文将深入探讨安卓和iOS这两大操作系统在智能穿戴领域的现状、竞争以及未来发展趋势,揭示它们如何通过创新技术满足用户需求,并预测未来可能的发展方向。
43 0
|
6月前
|
机器学习/深度学习 PyTorch TensorFlow
iOS设备功能和框架: 什么是 Core ML?如何在应用中集成机器学习模型?
iOS设备功能和框架: 什么是 Core ML?如何在应用中集成机器学习模型?
178 0