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

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

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

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

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

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

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

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

我们所画的手势将用于训练机器学习算法,我们将用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

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

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

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

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

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

为机器学习算法生成数据

项目的自述文件解释了如何修改手势类的集合,包括复选标记、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

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

请注意,我仍然在另一个文件中存储每次笔画的触摸位置的原始时间序列。这样,我就可以改变手势在未来转换成图像的方式,甚至可以使用非基于图像的方法来识别,而不用再画出所有的手势。手势输入在它的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分钟后完成。

训练神经网络

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

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

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

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

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

到目前为止,除了在现有的转换器的内部代码之外,在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=6664, 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

时间: 2024-12-31 02:49:00

在iOS11中使用Core ML 和TensorFlow对手势进行智能识别的相关文章

iOS11新特性 之 Core ML [机器学习]

https://developer.apple.com/documentation/coreml#topics 看到今年的WWDC推出一系列机器学习框架,激动的不行.其实iOS10就有 Accelerate and BNNS.都是更基础的API. iOS11搭载了封装更好用的Core ML, 而且在Core ML还有封装了更为强大的图像识别处理库Vision, 文本处理的NSLinguisticTagger Core ML Model 是的,iPhone上将会出现大批的App在客户端本地跑机器学

iOS 开发中使用 Core Data 应避免的十个错误

Core Data是苹果针对Mac和iOS平台开发的一个框架主要用来储存数据.对很多开发者来说Core Data比较容易入手但很难精通如果没有正确的学习方法你将很难真正理解它更不用说精通了.很多开发者常常在这方面犯一些错误而这篇文章列出了 开发者在iOS开发过程中使用Core Data常见的一些错误并对如何避免这些错误进行了分析. 1.不了解关键术语 对于iOS开发者来说会使用Core Data是一项必备技能. 没有它很多app都不会存在.当在互联网上四处搜索Core Data学习教程你很容易被

在 Swift Playgrounds 中使用 Core Data 模型

本文讲的是在 Swift Playgrounds 中使用 Core Data 模型, 你能在 Xcode 的 Swift Playgrounds 中使用 Core Data 模型么?当然可以! 在2015年, http://www.learncoredata.com的作者 Jeremiah Jessel,写了篇文章 detailing how you can use the Core Data framework inside a playground.从建立 Core Data 堆栈到在代码中

xlistview-安卓Xlistview中每个订单都有两个button 怎么进行识别呢

问题描述 安卓Xlistview中每个订单都有两个button 怎么进行识别呢 大家懂我的意思吗? 每个订单的名称元素都是一样的 textview可已根据 list.get(position).getClientNum() 进行区分 但是button好像不行啊 求指导! 解决方案 ID不一样就可以区分吧,findViewById()方法获取不到当前的button吗?可以在Xlistview自定义的adapter中实现onclicklistener事件处理 解决方案二: 在adapter里面分别写

rfid-请问Android开发中一般带有NFC功能的手机能否实现NFID识别入场证id信息

问题描述 请问Android开发中一般带有NFC功能的手机能否实现NFID识别入场证id信息 请问Android开发中一般带有NFC功能的手机能否实现NFID识别入场证id信息 是否需要相应的物理模块? 比如在应用中实现读取员工卡的id来实现某个app的登陆功能. 解决方案 已解决. 从硬件厂家要了驱动接口

c语言-在同一行中,跟在其他字符后面输出EOF,系统不识别,当EOF单独占一行时,才被识别?

问题描述 在同一行中,跟在其他字符后面输出EOF,系统不识别,当EOF单独占一行时,才被识别? #include int main() { int i; for (i = 0; getchar()!= EOF; i++); printf("There are %d chars",i); return 0; } 解决方案 识别啊,必须按回车才读,按ctrl+c,退出. 解决方案二: 你一行的输入时全部放在缓冲区里面的 只有按下回车才能连接

C#中调用Matlab人工神经网络算法实现手写数字识别

手写数字识别实现 设计技术参数:通过由数字构成的图像,自动实现几个不同数字的识别,设计识别方法,有较高的识别率 关键字:二值化  投影  矩阵  目标定位  Matlab                                                               手写数字图像识别简介: 手写阿拉伯数字识别是图像内容识别中较为简单的一个应用领域,原因有被识别的模式数较少(只有0到9,10个阿拉伯数字).阿拉伯数字笔画少并且简单等.手写阿拉伯数字的识别采用的方法相对于

CPU中Turbo Core是什么

  Turbo Core是AMD针对英特尔睿频加速技术提出的一种新技术,其功能可以理解为自动超频,当开启Turbo Core之后,CPU会根据当前的任务量自动调整CPU主频,从而重任务时发挥最大的性能,轻任务时发挥最大节能优势. Turbo Core于与Turbo Boost(睿频加速)的异同 AMD的Turbo Core技术与英特尔的Turbo Boost技术有着异曲同工之妙,虽然其运作流程不同,但是都是为了在TDP的允许范围内,尽可能的提高运行中核心的频率,以达到提升CPU工作效率的目的.因

iOS开发之集成iOS9中的Core Spotlight Framework搜索App的内容

Spotlight在iOS9上做了一些新的改进, 也就是开放了一些新的API, 通过Core Spotlight Framework你可以在你的app中集成Spotlight.集成Spotlight的App可以在Spotlight中搜索App的内容,并且通过内容打开相关页面.因为接到开发任务,老大说让在App中支持Spotlight, 于是又搞了搞苹果的官方文档.可以说,集成Spotlight不算复杂,官网上讲的也挺明白的,今天博客就通过一个Demo来集成一下Spotlight. 苹果官方有关C