目录
总目录
- 解码Core ML YOLO对象检测器(二)
- 使用数组操作解码YOLO Core ML对象检测(三)
- 使用解码逻辑创建YOLO Core ML对象检测器(四)
- 为iOS Vision盒子架构建Core ML管道(五)
- 使用实时摄像头预览的iOS对象检测(六)
- 使用YOLO Core ML模型构建对象检测iOS应用(七)
在这里,我们将构建一个管道来构成一个完整的端到端对象检测模型,准备在带有Vision盒子架的iOS应用程序中使用它。
介绍
本系列假定您熟悉Python、Conda和ONNX,并且具有使用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参数的值在[0,1]范围内。它确定何时可以将单个类的两个盒子视为冗余。值为1表示仅将完全相同的盒子视为重叠和冗余,而值为0表示即使没有任何实际重叠的盒子也可以视为冗余。有理由认为该值应在0到1之间。
该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_converted、model_decoder和model_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