目录
介绍
了解YOLO v2输出
准备YOLO输出解码
解码YOLO输出
下一步
总目录
将ONNX对象检测模型转换为iOS Core ML(一)
- 解码Core ML YOLO对象检测器(二)
- 使用数组操作解码YOLO Core ML对象检测(三)
- 使用解码逻辑创建YOLO Core ML对象检测器(四)
- 为iOS Vision盒子架构建Core ML管道(五)
- 使用实时摄像头预览的iOS对象检测(六)
- 使用YOLO Core ML模型构建对象检测iOS应用(七)
- 下载带循环解码-690.3 KB
本系列假定您熟悉Python、Conda和ONNX,并且具有使用Xcode开发iOS应用程序的经验。我们将使用macOS 10.15 +、Xcode 11.7+和iOS 13+运行代码。
了解YOLO v2输出YOLO v2接受固定分辨率的416 x 416输入图像,这些图像被分成13 x 13的网格。根据此模型进行的预测将返回形状为(1, 425, 13, 13)的单个数组。第一个维度代表批次(对于我们的目的而言并不重要),后两个维度对应于13 x 13的网格。但是我们在前一篇文章中看到的每个单元格中的425值是什么呢?
这些值包含有关检测到的对象的置信度得分和相应边界框坐标的编码信息:
[x1,y1,w1,h1,s1,c011,c021,c031,…,c791,c801,x2,y2,…,x5,y5,w5,h5,s5,c015,…c795,c805],
其中:
- i ——给定网格单元内的边界框索引(值:1-5)
- x i,y i,w i,h i ——box i的框的坐标(分别为x,y,宽度和高度)
- s i ——给定单元格包含对象的置信度分数
- c01 i -c80 i —— COCO数据集中包含的80个对象类别中每个类别的置信度得分。
快速检查:每个单元格5个框乘以85个值(四个坐标,每个单元格一个置信度得分+每个对象类80个置信度得分)精确等于425。
准备YOLO输出解码我们需要几个常量:
GRID_SIZE = 13
CELL_SIZE = int(416 / GRID_SIZE)
BOXES_PER_CELL = 5
ANCHORS = [[0.57273, 0.677385],
[1.87446, 2.06253],
[3.33843, 5.47434],
[7.88282, 3.52778],
[9.77052, 9.16828]]
GRID_SIZE反映YOLO如何将图像拆分为单元格,CELL_SIZE描述每个单元格的宽度和高度(以像素为单位),而BOXES_PER_CELL是模型每个单元格考虑的预定义框数。
该ANCHORS数组包含用于计算每个单元格中五个框中每个框的坐标的因子。请注意,不同的YOLO版本使用各种锚点,因此您始终需要检查用于模型训练的值。以上值在原始YOLO存储库(yolov2.cfg文件)中找到。
我们还需要从所附的coco_names.txt文件中为检测到的对象加载标签:
with open('./models/coco_names.txt', 'r') as f:
COCO_CLASSES = [c.strip() for c in f.readlines()]
COLO_CLASSES列表的前几个元素是:
['person', 'bicycle', 'car', 'motorbike', 'aeroplane', 'bus', 'train', 'truck', 'boat', 'traffic light', ...]
解码YOLO输出
我们的YOLO v2模型返回的是“原始”神经网络输出,未通过激活函数进行归一化。为了使它们有意义,我们将需要两个附加功能:
def sigmoid(x):
k = np.exp(-x)
return 1 / (1 + k)
def softmax(x):
e_x = np.exp(x - np.max(x))
return e_x / e_x.sum()
无需详细说明:对于任何输入,sigmoid返回0-1范围内的值,而softmax返回任何输入向量的规范化值,其值之和为1
现在我们可以编写我们的主要解码功能:
def decode_preds(raw_preds: []):
num_classes = len(COCO_CLASSES)
decoded_preds = []
for cy in range(GRID_SIZE):
for cx in range(GRID_SIZE):
for b in range(BOXES_PER_CELL):
box_shift = b*(num_classes + 5)
tx = float(raw_preds[0, box_shift , cy, cx])
ty = float(raw_preds[0, box_shift + 1, cy, cx])
tw = float(raw_preds[0, box_shift + 2, cy, cx])
th = float(raw_preds[0, box_shift + 3, cy, cx])
ts = float(raw_preds[0, box_shift + 4, cy, cx])
x = (float(cx) + sigmoid(tx)) * CELL_SIZE
y = (float(cy) + sigmoid(ty)) * CELL_SIZE
w = np.exp(tw) * ANCHORS[b][0] * CELL_SIZE
h = np.exp(th) * ANCHORS[b][1] * CELL_SIZE
box_confidence = sigmoid(ts)
classes_raw = raw_preds[0, box_shift + 5:box_shift + 5 + num_classes, cy, cx]
classes_confidence = softmax(classes_raw)
box_class_idx = np.argmax(classes_confidence)
box_class_confidence = classes_confidence[box_class_idx]
combined_confidence = box_confidence * box_class_confidence
decoded_preds.append([box_class_idx, combined_confidence, x, y, w, h])
return sorted(decoded_preds, key=lambda p: p[1], reverse=True)
首先,该函数在每个网格单元(cy、cx和b)中的框上进行迭代,以解码每个边界框的后续值(假设批处理中有单个图像,因此我们使用raw_preds[0,…])。原始tx,ty,tw、th和ts值模型被用来计算返回的边界框坐标(中心x,中心y,宽度和高度), box_confidence(置信一个给定的框包含的对象)和class_confidence(具有归一化的向量80个COCO类别中每个类别的信心)。配备了这些值后,我们将计算使用当前框(box_class_idx及其combined_confidence)检测到的最可能物体的类别。
在完成所有计算之后,该方法将返回一个由置信度得分按降序排序的解码值列表。
让我们看看它是否适用于Open Images数据集中的图像:
image = load_and_scale_image('https://c2.staticflickr.com/4/3393/3436245648_c4f76c0a80_o.jpg')
cml_model = ct.models.MLModel('./models/yolov2-coco-9.mlmodel')
preds = cml_model.predict(data={'input.1': image})['218']
decoded_preds = decode_preds(preds)
print([p[:3] for p in decoded_preds[:2]])
模型似乎对图片包含一个人和一条狗很有信心。我们应该检查这是否正确:
import copy
def annotate_image(image, preds, min_score=0.5, top=10):
annotated_image = copy.deepcopy(image)
draw = ImageDraw.Draw(annotated_image)
w,h = image.size
colors = ['red', 'orange', 'yellow', 'green', 'blue', 'white']
for class_id, label, score, xc, yc, w, h in decoded_preds[:top]:
if score < min_score:
continue
x0 = xc - (w / 2)
y0 = yc - (h / 2)
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
annotate_image(image, decoded_preds)
不错...但是重复的盒子怎么了?没有。这是YOLO工作方式的副作用——多个盒子(总共425个盒子)可能检测到同一物体。我们可以通过设置一个最小的置信度得分来稍微改善这一点。不过,暂时不要担心。我们将很快使用称为非最大抑制的算法来解决这个问题。
下一步我们已经成功解码了YOLO v2输出,这使我们可以可视化模型的预测。如果您不喜欢我们代码中的循环,那是对的。这不是我们应该对数组执行计算的方式。我们以这种方式构造代码只是为了了解解码过程。在接下来的文章中,我们将使用数组完成相同的操作。这将使我们能够将解码逻辑直接包括在模型中。
https://www.codeproject.com/Articles/5286800/Decoding-a-Core-ML-YOLO-Object-Detector