7.12. 后处理器¶
检测网络并不直接输出框。它输出一个或多个张量,其布局取决于模型所训练的架构——对于 YOLO 系列检测器是一个二维的候选预测张量,对于 MediaPipe 检测器是一对 (boxes, scores) 张量,对于姿态网络是一串扁平的关键点坐标列表。应用程序无法直接读取其中任何一种;它真正想要的东西——一个框列表、一个关键点列表、一份按类划分的明细——必须从原始张量中解码出来。
这个解码器就是后处理器。ml.postprocessing 模块按其来源生态系统对它们进行分组。
7.12.1. Darknet¶
ml.postprocessing.darknet 解码源自最初 YOLO 时代的模型。YOLO v2 引入了网格和锚点这两个概念,后来的大多数检测器都以某种形式继承了它们,因此 v2 的布局是最清晰的入门起点。
YOLO v2 首先将输入图像划分为一个粗略的网格——对于标准的 416 像素输入是 13×13 的布局,对于更小的模型则更小——并训练网络使每个网格单元负责检测中心落在其内部的任何物体。输出张量的空间布局与输入的布局相对应:图像中每个单元在输出中对应一个位置。
在每个网格单元处,网络并不会凭空预测出一个框。它会从若干预先选定的参考形状中挑选,这些形状称为锚点——固定的 (width, height) 对,通过对训练集中框的尺寸进行离线聚类得出,从而覆盖模型预期会遇到的典型物体。网络在每个单元的任务是,针对每个锚点预测:框中心在该单元内的小幅偏移、对锚点宽高的缩放、一个物体性得分(该处存在任何物体的可能性),以及一个按类的概率向量。因此,一个采用默认 5 个锚点和 20 个类别的 13×13 网格每次推理会输出 13 * 13 * 5 * (4 + 1 + 20) = 21,125 个数值。
YoloV2 负责解码该布局:它遍历各单元,对每个锚点应用其偏移和缩放以还原出绝对框坐标,将物体性与类别概率组合为按类得分,进行阈值过滤,并将存活下来的框推送给 NMS。当模型针对自定义锚点表训练时,该类接收 anchors= 构造函数参数,否则会回退到内置默认值。针对特定类别集合调优的变体也在同一子模块中提供。
7.12.2. Ultralytics¶
ml.postprocessing.ultralytics 解码较新一代的 YOLO。YoloV8 读取一个列主序的输出,其中每一列是一个锚点预测,包含框坐标和一个按类的得分向量——早期 YOLO 输出携带的物体性通道在 v8 中已被去除,类别得分独立存在。YOLOv8 详解 会逐张量地讲解解码过程。较旧的 Ultralytics 时代版本也在同一子模块中提供,以支持针对其布局训练的模型。
7.12.3. MediaPipe¶
ml.postprocessing.mediapipe 解码 Google 的轻量级端侧系列。BlazeFace 是 hello-blazeface 中介绍的人脸检测器:一个快速的基于锚点的检测器,每张人脸输出框和六个特征点坐标,以 (box, score, keypoints) 元组返回,其中特征点附加在各个框上,而不是作为单独的输出列表。同系列的手部检测、特征点和姿态模型与其一同提供,并遵循相同的附加关键点返回结构。
7.12.4. 选择一个¶
正确的后处理器由模型所训练的架构决定,而不是由应用程序的需求决定。一个 YOLOv8 .tflite 只有通过 YoloV8 才能正确解码;一个 BlazeFace .tflite 只能通过 BlazeFace。选择后处理器是选择模型的一部分。当某个模型的架构没有对应的内置后处理器时,编写你自己的 也很简单。
分类网络是个例外。它们的单一输出张量本身就是应用程序所需要的——一个按类划分的得分列表——无需后处理器。正确的做法是加载模型时不带 postprocess=,并将预测结果作为一个扁平的 ndarray 读取,正如 张量 I/O 所介绍的那样。