[控场AI]
· 5 分钟阅读· 2,535 字

CoreML模型没问题,是照片太大了:端侧AI的图像管线陷阱

CoreML模型没问题,是照片太大了:端侧AI的图像管线陷阱

端侧视觉计数不准时,先别换模型——分块推理和正确的合并策略往往就能把召回率从14提升到173。

文章记录了一个端侧计算机视觉的典型故障:开发者用小型 YOLO/CoreML 模型在设备端统计牲畜数量,一张约 250 只羊的 1200 万像素照片却只检测出 14 只。问题不在模型本身,而在于整幅高分辨率照片被直接缩放至模型输入尺寸,远处的小目标仅剩几个像素,检测器无从恢复。修复方案是无聊但有效的分块推理:保留全帧推理,同时叠加 3×3、25% 重叠的切片,把切片坐标映射回原图,丢弃被切片边缘切断的框,并用类无关 NMS 合并结果。同一张照片的检测数从 14 提升到 173,且近距离简单场景的计数未被虚增。文章强调合并步骤才是隐藏的坑——分块场景下仅靠 IoU 不够,还需 intersection-over-smaller 与类无关合并。核心教训是:在责怪模型前先检查你实际喂给了它什么像素,端侧 AI 中图像管线往往是最廉价有效的优化点。

一个普通的计算机视觉故障:模型按要求工作,但图像管线让任务无法完成。

CoreML模型与图像管线

一位开发者分享了他的经历:应用需要在设备端用一个小型YOLO/CoreML模型统计牲畜数量,无网络、无账户、无服务端兜底。这种约束有用,但意味着手机端管线必须诚实地面对模型实际看到的东西。

250只羊,模型只数出14只

最初的代码路径看起来合理:

let request = VNCoreMLRequest(model: model)
request.imageCropAndScaleOption = .scaleFit
handler.perform([request])

一张图进去,检测结果出来。

然后用一张真实的牧场照片测试——画面里大约有250只羊。计数返回14。

这不是模型的问题。一张1200万像素的照片被压缩进了模型输入。等羊群到达网络时,远处的羊每只只有几个像素。检测器已经没有什么聪明的东西可以恢复了。

分块推理:不换模型,换输入尺寸

修复方案很无聊,但比换模型好得多:

  1. 先跑一遍完整图像
  2. 在同一张图上跑重叠的切片
  3. 把切片相对坐标的检测框映射回图像坐标
  4. 丢弃被内部切片边缘切断的框
  5. 用类无关NMS合并剩余检测

关键点是,分块改变了模型看到的物体尺寸,但没有改变模型本身。

let regions = [CGRect(x: 0, y: 0, width: 1, height: 1)]
    + Self.tiles(aspect: aspect, across: 3, overlap: 0.25)

for region in regions {
    request.regionOfInterest = region
}

在一张4:3的照片上,这变成12个切片加一次全帧推理。推理次数更多,但仍在设备端,而且对于静态照片仍然只需要不到一秒。

同一张牧场照片从14个检测提升到173个。不完美,但突然有用了。近距离的母鸡照片保持稳定,这一点同样重要:修复在尺度是问题的地方提升了召回率,却没有在容易计数的场景中虚增数量。

什么是 regionOfInterest

VNCoreMLRequest 的 regionOfInterest(ROI)是 Vision 框架提供的一个属性,取值是归一化坐标(0~1)表示的 CGRect,默认 .zero 表示整幅图像。设置后,Vision 会先把该矩形区域从原图中裁剪出来,再按 imageCropAndScaleOption 缩放并送入模型。这意味着每次推理实际输入的像素尺寸可能不同,但模型本身(输入张量形状、权重)完全不变。正因为 ROI 只是改变了「馈送给模型的像素」,分块推理才成为不触及模型结构的廉价优化手段。需要注意的是,坐标归一化基于原图方向,旋转或镜像的图像要先统一方向,否则 ROI 会切错位置。

imageCropAndScaleOption 的取舍

imageCropAndScaleOption 决定 Vision 如何把图像适配模型要求的输入尺寸,常见取值有 .centerCrop、.scaleFit、.scaleFill 和 .scaleFitRotate90CCW。.centerCrop 保持长宽比但裁掉边缘,容易在宽幅照片边缘丢目标;.scaleFit 保留整幅画面但会在短边补灰边,等效缩小物体;.scaleFill 会拉伸变形。文章中 1200 万像素照片缩放入模型(通常是 416×416 或 640×640)时,单只远处的羊可能只占几个像素,这属于典型的「极端尺度下采样」问题,而非模型能力不足。选择 .scaleFit 配合分块,可以让每个切片内的物体占更多像素,本质上是在输入层补偿尺度损失。

合并步骤才是隐藏的坑

不明显的部分是合并步骤。

默认的仅基于IoU的NMS在分块时不够用。一个部分头部框可能位于整个动物框内部,IoU很低,所以还需要基于intersection-over-smaller进行合并。同时把合并做成类无关的,因为同一只动物可能在不同推理中以两个标签返回。数动物,不是数标签争论。

这个经验很简单:在责怪模型之前,检查你实际给了它什么像素。

对于端侧AI,模型往往是系统中最不灵活的部分。围绕它的图像管线才是廉价优化隐藏的地方。

IoU 与 intersection-over-smaller(IoS)

NMS 常用 IoU(交并比)衡量两个框的重叠:交叠面积除以并集面积。但当一个框被另一个框完全包含时——比如分块场景中一个「部分头部」框落在「整只动物」框内部——交叠面积虽大,并集却等于大框,IoU 会明显偏小,导致本应合并的框被保留下来。intersection-over-smaller(IoS)改成交叠面积除以较小框的面积,只要小框大部分落在大框内,IoS 就接近 1,从而正确触发合并。分块推理时同一目标可能在相邻切片中分别以局部框和全局框出现,IoS 正是为此类「包含式重叠」设计的补充判据。把它与类无关合并结合,即可避免同一动物因标签或尺度不一致被重复计数。

类无关 NMS(class-agnostic NMS)

标准 NMS 通常按类别分别执行,只有当两个框类别相同且 IoU 超阈值时才抑制。但在分块推理中,同一目标在不同切片、不同上下文下可能被模型赋予不同标签(例如「羊」与「动物」),按类独立过滤就会把同一实体保留成多个框。类无关 NMS 忽略类别、对全部候选框统一做重叠抑制,再决定保留哪个标签。对以「计数」为目标的场景,这一策略至关重要:计数的对象是物理实体,而不是分类争议。代价是可能抑制掉一些类别不同但空间重叠的真实目标,因此需要根据任务权衡阈值和是否保留类别信息。

分享:

相关推荐