目录
总目录
- 解码Core ML YOLO对象检测器(二)
- 使用数组操作解码YOLO Core ML对象检测(三)
- 使用解码逻辑创建YOLO Core ML对象检测器(四)
- 为iOS Vision盒子架构建Core ML管道(五)
- 使用实时摄像头预览的iOS对象检测(六)
- 使用YOLO Core ML模型构建对象检测iOS应用(七)
在这里,我们将使用数组操作(向量化)来摆脱循环,从而对YOLO Core ML模型进行解码。了解它是如何工作的,将使我们在下一篇文章中将此逻辑添加到Core ML模型中。
介绍
本系列假定您熟悉Python、Conda和ONNX,并且具有使用Xcode开发iOS应用程序的经验。我们将使用macOS 10.15 +、Xcode 11.7+和iOS 13+运行代码。
解码YOLO输出的正确方式
如果您以前使用过神经网络或数组,那么您很可能会在上一篇文章中看到我们在细胞和盒子(cy、cx和cx)上的循环。根据经验,如果在处理数组时需要循环,那么这样做是错误的。在这种特殊情况下,这是有意的,因为这些循环使得更容易掌握底层逻辑。向量化的实现通常很简短,但乍一看并不容易理解。
请注意,本文代码下载中的笔记本包含了以前的(基于循环)解决方案和新的解决方案。
首先从矢量化解码开始,我们需要一个在2D数组上工作的新softmax函数:
def softmax_2d(x, axis=1):
x_max = np.max(x, axis=axis)[:, np.newaxis]
e_x = np.exp(x - x_max)
x_sum = np.sum(e_x, axis=axis)[:, np.newaxis]
return e_x / x_sum
接下来,为了摆脱cy、cx以及b循环,我们需要一些常量数组:
ANCHORS_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, GRID_SIZE**2, 1)
CY = np.tile(np.arange(GRID_SIZE), GRID_SIZE).reshape(1, GRID_SIZE, GRID_SIZE).transpose()
CY = CY.reshape(1, GRID_SIZE**2, 1)
现在,该ANCHORS数组分为两个:ANCHORS_W和ANCHORS_H。
CX和CY数组包含所有的cx和cy值组合,嵌套循环执行期间先前生成的。设置这些阵列的形状是为了简化后续操作。
现在我们准备实现矢量化解码函数:
def decode_preds_vec(raw_preds: []):
num_classes = len(COCO_CLASSES)
raw_preds = np.transpose(raw_preds, (0, 2, 3, 1))
raw_preds = raw_preds.reshape((1, GRID_SIZE**2, BOXES_PER_CELL, num_classes + 5))
decoded_preds = []
tx = raw_preds[:,:,:,0]
ty = raw_preds[:,:,:,1]
tw = raw_preds[:,:,:,2]
th = raw_preds[:,:,:,3]
tc = raw_preds[:,:,:,4]
x = ((CX + sigmoid(tx)) * CELL_SIZE).reshape(-1)
y = ((CY + sigmoid(ty)) * CELL_SIZE).reshape(-1)
w = (np.exp(tw) * ANCHORS_W * CELL_SIZE).reshape(-1)
h = (np.exp(th) * ANCHORS_H * CELL_SIZE).reshape(-1)
box_confidence = sigmoid(tc).reshape(-1)
classes_raw = raw_preds[:,:,:,5:5 + num_classes].reshape(GRID_SIZE**2 * BOXES_PER_CELL, -1)
classes_confidence = softmax_2d(classes_raw, axis=1)
box_class_idx = np.argmax(classes_confidence, axis=1)
box_class_confidence = classes_confidence.max(axis=1)
combined_box_confidence = box_confidence * box_class_confidence
decoded_boxes = np.stack([
box_class_idx,
combined_box_confidence,
x,
y,
w,
h]).transpose()
return sorted(list(decoded_boxes), key=lambda p: p[1], reverse=True)
首先,为了使计算更简单,我们通过将425个值与编码的框坐标和类置信度移动到最后一个维度来转置raw_preds数组。然后,将其形状从(1, 13, 13, 425)重塑为(1, 13*13, 5, 85)。这样,忽略第一个位置的批处理(始终等于0),尺寸顺序与cy(13),cx(13)和box(5)上的先前循环匹配。
请注意,我们必须使用形状(1, 13*13, 5, 85),而不是更明确的形状(1, 13, 13, 5, 85),这是因为Core ML具有某些数组等级限制。这意味着某些操作会导致尺寸过大的数组出现异常。此外,考虑到“隐藏的”内部序列维,在Core ML中使用数组不是很直观。
在NumPy数组上工作时,我们可以使用“较长”的形状(1, 13, 13, 5, 85),但是,为了使操作可以轻松转换为Core ML,我们必须将维数减少1,因此形状(1, 13*13, 5, 85)。
现在,与前一个版本相比的主要变化是如何获取tx、ty、tw、th和tc值,以及classes_raw。与其在单个单元格中读取对应于单个框的单独值,不如在一个步骤中获得具有所有对应值的数组。它支持以下“单步”数组操作,这些操作使所有计算都非常高效,尤其是在针对数组计算优化的芯片(例如GPU或神经引擎)上执行时。
decoded_preds_vec = decode_preds_vec(preds)
annotate_image(image, decoded_preds_vec)
这是另一个例子。
下一步
现在,我们获得了与以前的基于循环的解决方案相同的结果。它使我们准备好将检测解码直接包括在Core ML模型中。最终,这将使我们能够使用Vision框架的对象检测功能,从而大大简化了iOS应用程序的Swift代码。
https://www.codeproject.com/Articles/5286801/Decoding-a-YOLO-Core-ML-Object-Detector-Using-Arra