为iOS Vision盒子架构建Core ML管道(五)

目录

介绍

去掉多余的盒子

构建管道

对管道的预测

下一步


总目录 

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

在这里,我们将构建一个管道来构成一个完整的端到端对象检测模型,准备在带有Vision盒子架的iOS应用程序中使用它。

介绍

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

去掉多余的盒子

在将所有模型包装到单个管道中之前,需要解决的最后一件事。上次进行预测时,我们的模型得出以下结果。

预测是正确的,但是为每个单元格和盒子生成的独立检测导致冗余和重叠的盒子。对我们来说幸运的是,有一种解决此类问题的行之有效的方法:非最大抑制算法。由于它已经实现并且可以在Core ML中作为模型层或专用模型使用,因此在此不再详细描述。足以理解该算法接受检测列表(具有置信度得分的盒子和类),并且仅返回与最大置信度相对应的盒子,而没有多余的重叠盒子。目前, iOS Vision框架只能正确识别nonMaximumSuppresion模型(而非图层)的输出,因此我们会坚持下去。

让我们从上次完成的地方开始——使用创建的model_decoder实例——查看您下载的源代码。

现在,我们继续进行以下操作(借用本文中的代码):

nms_spec = ct.proto.Model_pb2.Model()
nms_spec.specificationVersion = 3
nms = nms_spec.nonMaximumSuppression
nms.confidenceInputFeatureName = "all_scores"
nms.coordinatesInputFeatureName = "all_boxes"
nms.confidenceOutputFeatureName = "scores"
nms.coordinatesOutputFeatureName = "boxes"
nms.iouThresholdInputFeatureName = "iouThreshold"
nms.confidenceThresholdInputFeatureName = "confidenceThreshold"

现在我们可以定义基本参数:

nms.iouThreshold = 0.5
nms.confidenceThreshold = 0.4
nms.pickTop.perClass = True

labels = np.loadtxt('./models/coco_names.txt', dtype=str, delimiter='\n')
nms.stringClassLabels.vector.extend(labels)

iouThreshold参数的值在[01]范围内。它确定何时可以将单个类的两个盒子视为冗余。值为1表示仅将完全相同的盒子视为重叠和冗余,而值为0表示即使没有任何实际重叠的盒子也可以视为冗余。有理由认为该值应在01之间。

confidenceThreshold参数使我们可以过滤出置信度得分低于配置值的检测结果。如果将该pickTop.perClass值设置为False,则即使它们引用了不同的类,这些盒子也可能被视为重叠和冗余的,因此对于多类检测,通常需要将其设置为True。最后,将标签添加到模型中,因此我们不必在iOS应用程序中按类ID查找标签。

现在,我们可以将model_decoder输出映射到我们的新模型输入:

for i in range(2):
    decoder_output = model_decoder._spec.description.output[i].SerializeToString()

    nms_spec.description.input.add()
    nms_spec.description.input[i].ParseFromString(decoder_output)

    nms_spec.description.output.add()
    nms_spec.description.output[i].ParseFromString(decoder_output)

nms_spec.description.output[0].name = 'scores'
nms_spec.description.output[1].name = 'boxes'

output_sizes=[80, 4]
for i in range(2):
    ma_type = nms_spec.description.output[i].type.multiArrayType
    ma_type.shapeRange.sizeRanges.add()
    ma_type.shapeRange.sizeRanges[0].lowerBound = 0
    ma_type.shapeRange.sizeRanges[0].upperBound = -1
    ma_type.shapeRange.sizeRanges.add()
    ma_type.shapeRange.sizeRanges[1].lowerBound = output_sizes[i]
    ma_type.shapeRange.sizeRanges[1].upperBound = output_sizes[i]
    del ma_type.shape[:]

让我们保存非最大抑制模型:

model_nms = ct.models.MLModel(nms_spec)
model_nms.save('./models/yolov2-nms.mlmodel')

构建管道

放置所有模型(model_convertedmodel_decodermodel_nms)后,我们可以建立将它们绑定在一起的管道:

input_features = [ ('input.1', datatypes.Array(1,1,1)), # Placeholder
                   ('iouThreshold', datatypes.Double()),
                   ('confidenceThreshold', datatypes.Double())
                 ]
output_features = [ 'scores', 'boxes' ]

pipeline = ct.models.pipeline.Pipeline(input_features, output_features)
pipeline.spec.specificationVersion = 3

pipeline.add_model(model_converted)
pipeline.add_model(model_decoder)
pipeline.add_model(model_nms)

最后要做的是用实际模型的输入和输出替换管道的输入和输出占位符,然后保存管道:

pipeline.spec.description.input[0].ParseFromString(model_converted._spec.description.input[0].SerializeToString())
pipeline.spec.description.output[0].ParseFromString(model_nms._spec.description.output[0].SerializeToString())
pipeline.spec.description.output[1].ParseFromString(model_nms._spec.description.output[1].SerializeToString())

model_pipeline = ct.models.MLModel(pipeline.spec)
model_pipeline.save("./models/yolov2-pipeline.mlmodel")

对管道的预测

因为我们的管道返回的数据格式与之前使用的格式略有不同(盒子和类的置信度在两个数组中而不是单个数组),所以我们需要更新annotate_image函数:

def annotate_image(image, preds):
    annotated_image = copy.deepcopy(image)
    draw = ImageDraw.Draw(annotated_image)

    w,h = image.size
    colors = ['red', 'orange', 'yellow', 'green', 'blue', 'white']

    boxes = preds['boxes']
    scores = preds['scores']
    
    for i in range(len(scores)):
        class_id = int(np.argmax(scores[i]))
        score = scores[i, class_id]
        
        xc, yc, w, h = boxes[i]
        xc = xc * 416
        yc = yc * 416
        w = w * 416
        h = h * 416
        
        x0 = xc - (w / 2)
        y0 = yc - (h / 2)
        label = labels[class_id]
        color = ImageColor.colormap[colors[class_id % len(colors)]]

        draw.rectangle([(x0, y0), (x0 + w, y0 + h)], width=2, outline=color)
        draw.text((x0 + 5, y0 + 5), "{} {:0.2f}".format(label, score), fill=color)
    
    return annotated_image

现在,我们可以返回打开图像数据集,以查看完成的模型如何在我们喜欢的图像上工作:

image = load_and_scale_image('https://c2.staticflickr.com/4/3393/3436245648_c4f76c0a80_o.jpg')
preds = model_pipeline.predict(data={'input.1': image})
annotate_image(image, preds)

 

还有几个样本。

下一步

我们终于有了完整的模型,没有多余的检测结果。在接下来的文章中,我们将开始对将使用该模型的iPhone应用程序的工作。

https://www.codeproject.com/Articles/5286803/Building-a-Core-ML-Pipeline-for-the-iOS-Vision-Fra

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值