糖尿病康复,内容丰富有趣,生活中的好帮手!
糖尿病康复 > 使用解码逻辑创建YOLO Core ML对象检测器(四)

使用解码逻辑创建YOLO Core ML对象检测器(四)

时间:2019-12-30 22:26:42

相关推荐

使用解码逻辑创建YOLO Core ML对象检测器(四)

目录

介绍

缩小模型

构建YOLO解码器

下一步

总目录

将ONNX对象检测模型转换为iOS Core ML(一)

解码Core ML YOLO对象检测器(二)使用数组操作解码YOLO Core ML对象检测(三)使用解码逻辑创建YOLO Core ML对象检测器(四)为iOS Vision盒子架构建Core ML管道(五)使用实时摄像头预览的iOS对象检测(六)使用YOLO Core ML模型构建对象检测iOS应用(七)

在本文中,我们将YOLO v2结果的解码包含到Core ML模型中。

下载源5.4 KB

介绍

本系列假定您熟悉Python、Conda和ONNX,并且具有使用Xcode开发iOS应用程序的经验。我们将使用macOS 10.15 +、Xcode 11.7+和iOS 13+运行代码。

缩小模型

为了节省iOS设备上的内存而不会对模型的性能造成负面影响,我们应该将其权重从32位精度降低到16位精度。请注意,当模型在iOS设备的GPU或神经引擎上执行时(应该如此),无论如何它总是以16位浮点数运行。只有在CPU上运行时,32位精度才能有所作为。

让我们开始吧:

import osimport coremltools as ctimport numpy as npmodel_converted = ct.models.MLModel('./models/yolov2-coco-9.mlmodel')model_converted = ct.models.neural_network.quantization_utils.quantize_weights(model_converted, nbits=16, quantization_mode='linear')model_converted.save('./models/yolov2-16.mlmodel')

构建YOLO解码器

我们有两个选择:将解码器层添加到现有模型或创建单独的模型,然后使用管道将两者连接。让我们选择后一个选项。

我们将从创建一个新NeuralNetworkBuilder实例并映射新解码器模型的输入和输出开始:

from coremltools.models import datatypesinput_features = [ (spec.description.output[0].name, datatypes.Array(1, 425, 13, 13)) ]output_features = [ ('all_scores', datatypes.Array(1, 845, 80)),('all_boxes', datatypes.Array(1, 845, 4)) ]builder = ct.models.neural_network.NeuralNetworkBuilder(input_features, output_features, disable_rank5_shape_mapping=True)builder.spec.description.input[0].ParseFromString(spec.description.output[0].SerializeToString())

接下来,我们定义计算所需的常量:

GRID_SIZE = 13CELL_SIZE = 1 / GRID_SIZE BOXES_PER_CELL = 5NUM_CLASSES = 80ANCHORS_W = np.array([0.57273, 1.87446, 3.33843, 7.88282, 9.77052]).reshape(1, 1, 5)ANCHORS_H = np.array([0.677385, 2.06253, 5.47434, 3.52778, 9.16828]).reshape(1, 1, 5)CX = np.tile(np.arange(GRID_SIZE), GRID_SIZE).reshape(1, 1, GRID_SIZE**2, 1)CY = np.tile(np.arange(GRID_SIZE), GRID_SIZE).reshape(1, GRID_SIZE, GRID_SIZE).transpose()CY = CY.reshape(1, 1, GRID_SIZE**2, 1)

注意上面的CELL_SIZE值。要将我们的模型与Vision框架一起使用,我们需要将包围盒坐标从图像像素缩放到[0-1]范围。

要将定义的常数用于计算,我们将它们添加到网络中:

builder.add_load_constant_nd('CX', output_name='CX', constant_value=CX, shape=CX.shape)builder.add_load_constant_nd('CY', output_name='CY', constant_value=CY, shape=CY.shape)builder.add_load_constant_nd('ANCHORS_W', output_name='ANCHORS_W', constant_value=ANCHORS_W, shape=ANCHORS_W.shape)builder.add_load_constant_nd('ANCHORS_H', output_name='ANCHORS_H', constant_value=ANCHORS_H, shape=ANCHORS_H.shape)

现在,我们准备为我们的Core ML模型添加图层。在大多数情况下,这将直接转换上一篇文章中的代码,并尽可能使用相同的变量/节点名称。有时,Core ML怪癖会强制执行一些小的更改。请参阅代码下载以获取完整的解决方案,因为为了提高可读性,此处将不包含一些显而易见的代码序列。

我们从对应于先前(矢量化)实现的前两个转换的层开始:

builder.add_transpose('yolo_trans_node', axes=(0,2,3,1), input_name='218', output_name=‘yolo_transp')builder.add_reshape_static('yolo_reshap', input_name='yolo_transp',output_name='yolo_reshap',output_shape=(1, GRID_SIZE**2, BOXES_PER_CELL, NUM_CLASSES + 5))

当我们使用NeuralNetworkBuilder实例创建一个新层时,我们需要为该节点及其节点指定一个唯一的名称output_name(分别在上面的第一个操作中为“yolo_trans_node”和“yolo_transp”)。该input_name值必须与现有output_name值相对应(在这种情况下为“218”,这是我们转换后的YOLO v2模型的输出)。

要提取编码的框和置信度值,我们需要分割输入数组:

builder.add_split_nd('split_boxes_node', input_name='yolo_reshap',output_names=['tx', 'ty', 'tw', 'th', 'tc', 'classes_raw'], axis=3,split_sizes=[1, 1, 1, 1, 1, 80])

该操作将raw_preds数组切片为上一篇文章中的tx、ty、tw、th,tc和classes_raw数组。

不幸的是,代码的其余部分将更加冗长,因为每个基本算术运算都需要一个单独的节点。这导致了来自矢量化解码器的简单行的情况:

x = ((CX + sigmoid(tx)) * CELL_SIZE).reshape(-1)

变成:

builder.add_reshape_static('tx:1', input_name='tx', output_name='tx:1', output_shape=(1,169,5))builder.add_activation('tx:1_sigm', non_linearity='SIGMOID', input_name='tx:1', output_name='tx:1_sigm')builder.add_add_broadcastable('tx:1_add', input_names=['CX', 'tx:1_sigm'], output_name='tx:1_add')builder.add_elementwise('x', input_names=['tx:1_add'], output_name='x', mode='MULTIPLY', alpha=CELL_SIZE)

请注意,为了使代码更短、更易读,我们在输出shape参数中使用显式值“169”代替GRID_SIZE**2和“5”代替BOXES_PER_CELL。这同样适用于“80”,而不是其他一些地方的NUM_CLASSES字面量。当然,在适当而灵活的解决方案中,我们应该坚持使用字面量。

计算y需要相同的运算。然后,我们有一个非常相似的代码来计算边界框的宽度(w):

builder.add_reshape_static('tw:1', input_name='tw', output_name='tw:1', output_shape=(1,169,5))builder.add_unary('tw:1_exp', input_name='tw:1', output_name='tw:1_exp', mode='exp')builder.add_multiply_broadcastable('tw:1_mul', input_names=['tw:1_exp', 'ANCHORS_W'], output_name='tw:1_mul')builder.add_elementwise('w', input_names=['tw:1_mul'], output_name='w', mode='MULTIPLY', alpha=CELL_SIZE)

h的后续的计算非常相似(除了使用ANCHORS_H而不是ANCHORS_W常量之外)。

最后,我们解码box_confidence和classes_confidence值:

builder.add_reshape_static('tc:1', input_name='tc', output_name='tc:1', output_shape=(1,169*5,1))builder.add_activation('box_confidence', non_linearity='SIGMOID', input_name='tc:1', output_name='box_confidence')builder.add_reshape_static('classes_raw:1', input_name='classes_raw', output_name='classes_raw:1', output_shape=(1,169*5,80))builder.add_softmax_nd('classes_confidence', input_name='classes_raw:1', output_name='classes_confidence', axis=-1)

在先前文章中描述的YOLO v2预测解码中,我们为每个框返回了一个最可能的类。Vision框架希望我们为每个盒子返回80个类别中每个类别的置信度:

builder.add_multiply_broadcastable('combined_classes_confidence', input_names=['box_confidence', 'classes_confidence'],output_name=‘combined_classes_confidence')

现在,我们拥有了所需的所有值。接下来,让我们将Vision框架的这些值格式化为两个数组:一个数组包含所有边界框的坐标(每个盒子四列),第二个数组具有针对每个盒子/类组合计算的置信度(包含80列)每盒)。

这不是一项艰巨的任务,但是因为我们需要将每个转换作为单独的操作来处理,所以它又导致了冗长的代码:

builder.add_reshape_static('x:1', input_name='x', output_name='x:1', output_shape=(1,169*5,1))builder.add_reshape_static('y:1', input_name='y', output_name='y:1', output_shape=(1,169*5,1))builder.add_reshape_static('w:1', input_name='w', output_name='w:1', output_shape=(1,169*5,1))builder.add_reshape_static('h:1', input_name='h', output_name='h:1', output_shape=(1,169*5,1))builder.add_stack('all_boxes:0', input_names=['x:1', 'y:1', 'w:1', 'h:1'], output_name='all_boxes:0', axis=2)builder.add_reshape_static('all_boxes', input_name='all_boxes:0', output_name='all_boxes',output_shape=(1,169*5, 4))builder.add_reshape_static('all_scores', input_name='combined_classes_confidence', output_name='all_scores',output_shape=(1,169*5, 80))

格式化了all_scores与all_boxes数组后,我们可以映射这些数组模型的输出和保存模型本身:

builder.set_output(output_names= ['all_scores', 'all_boxes'],output_dims= [(845,80), (845,4)])model_decoder = ct.models.MLModel(builder.spec)model_decoder.save('./models/yolov2-decoder.mlmodel')

下一步

有很多代码,但我们最终做到了。现在,我们有了一个可以解码YOLO v2预测的Core ML模型。但是,没有链接到YOLO的输出,我们将无法使用它。在接下来的文章中,我们将创建一个Core ML管道是我们的终端到终端的模式。

/Articles/5286802/Creating-a-YOLO-Core-ML-Object-Detector-with-Decod

如果觉得《使用解码逻辑创建YOLO Core ML对象检测器(四)》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。