目标检测核心概念解析:Anchor Boxes、YOLO与候选区域的关系 目标检测入门课程里有一段连续三节的内容几乎每个认真学下来的人都会被绕晕先讲 Anchor Boxes再讲 YOLO 算法紧跟着又冒出候选区域Region Proposals。听起来像三件事实际上一头连着“怎么定候选”一头连着“怎么判类别”还有一头连着“怎么精修框”。我在几个检测项目的训练和调参里反反复复跟这三套概念打交道今天干脆把它们之间的来龙去脉和工程经验一次性理清楚。这篇文章不追求复述讲义而是站在“要落地实现一个检测器”的角度把概念背后的动机、公式里的坑、训练时的注意点都补上。适合正在系统学目标检测的初学者也适合背过概念但一到项目里就不知道该动哪个参数的工程师。1. 先给三张概念图对好位Anchor Boxes、YOLO、候选区域到底各管哪一段1.1 目标检测的输出本质上是一张“框表”和目标分类不同目标检测的输出不是单一的类别标签而是一组数量不确定的检测结果每个结果包含“框住物体的矩形区域”和“这个区域的类别”。这句话说起来简单但它直接决定了整个算法架构怎么设计。因为数量不确定你不能像分类网络那样输出一个固定向量让网络直接告诉你结果就完事了。几乎所有检测算法都做了同一件事把输出设计成一张比较大的特征张量每个位置对应一个或多个“可能包含物体的区域”网络对每个区域预测类别、置信度和边界框偏移量。换句话说检测问题的关键是怎么把连续、数量不定的物体分布离散化成网络能够通过回归解决的固定格式。Anchor Boxes 和候选区域其实都是在做“候选”YOLO 是在做“一次性输出”它们不是互相排斥的三个门派而是同一套检测流程里不同环节的组件。理解这一层之后再去读网络结构或者开源代码你就会发现一个统一视角任何检测模型都要回答三个问题——哪里可能有物体它是什么类别框精确到什么程度。Anchor Boxes、YOLO、候选区域分别在这三个问题里扮演不同角色。1.2 三条路线的分工不同我把三者放在项目落地的角度重新定位一下方便你建立整体认知。Anchor Boxes 是“形状先验”它做的是把框的连续空间离散化YOLO 是“单阶段推理框架”它做的是让整个检测流程只过一次前向候选区域则是“两阶段检测的第一步”它先用较廉价的方式找出可能含物体的区域再交给后续网络慢慢判断。概念核心作用在检测流程中的位置典型搭配Anchor Boxes提供预设的形状模板定义回归基准标签分配、预测解码YOLO 系、Faster R-CNN 的 RPNYOLO单阶段端到端回归一次前向产生全部检测框分类和回归一步完成直接用 Anchor Boxes 做候选候选区域先用低开销方式筛选“可能是物体的区域”两阶段检测的第一阶段与 CNN 分类器、RoI 池化搭配这里有个特别容易混淆的点值得强调Anchor Boxes 并不是 YOLO 的专利候选区域路线的 Faster R-CNN 里RPN 网络同样也要放 Anchor Boxes。也就是说不能简单地把“锚框”划分给 YOLO把“候选区域”划分给 R-CNN。候选区域是思路Anchor Boxes 是实现候选的一种手段。后面我会重点展开这个关系。2. Anchor Boxes把“框的连续空间”切成离散的模板2.1 为什么固定窗口行不通从滑窗说起在 Anchor Boxes 出现以前最朴素的想法是滑动窗口准备一堆不同大小的窗口在全图上从左到右、从上到下扫一遍每个窗口里的内容送入分类器判断有没有物体。这个方法逻辑上没问题但工程上完全跑不通。第一个问题是数量爆炸假设要覆盖 10 种尺寸、5 种宽高比的窗口再考虑不同步长一张图可能产生数万个候选窗口每个窗口都要单独过一遍特征提取器算力直接起飞。第二个问题是形状失配滑窗是固定形状的如果物体是细长的杆子或者扁平的车辆固定比例窗口要么只框住物体的一部分要么把大量背景一起框进来正负样本都极其难做。Anchor Boxes 的思路聪明在哪儿它不在全图遍历而是把图片划分成统一的网格在特征图的每个位置预设几个不同大小和宽高比的“模板框”。这些模板框不需要覆盖整张图因为物体中心落在哪个网格就由那个网格负责。这样一来候选数量从“全图窗口组合”降为“网格数 × 每格模板数”。以常见的输入尺寸为例比如 416×416 的图经过 32 倍降采样得到 13×13 特征图每个位置放 3 个 Anchor Boxes候选先验也就是 13×13×3 共 507 个这比滑动窗口的遍历组合少了好几个数量级。2.2 一个格子能管几件事多个 Anchor 的标签分配那么训练时Anchor Boxes 怎么和真实标注框对应上这可能是初学者最容易卡住的地方。原则其实非常清晰对某个真实框遍历它所在网格的所有 Anchor Boxes计算每个 Anchor 与真实框的 IoU把真实框分配给 IoU 最大的那个 Anchor反过来一个 Anchor 也只能负责一个真实框。这个分配关系一旦定了该 Anchor 负责的坐标回归目标、类别标签就都明确了。为什么需要每个网格放多个 Anchor而不是一个因为一张图上可能存在两个物体中心恰好落在同一个网格里的情况。课程里最经典的例子就是行人和汽车行人站在路边汽车停在旁边两者中心在某一层特征图的同一个格子里。如果每个格子只允许输出一个目标网络就不得不在“行人”和“汽车”之间二选一检测上限被大大限制。但是如果预设一个竖长形状的 Anchor一个宽扁形状的 Anchor两个物体就可以各占一个同时被预测出来。这里我再补充一个工程细节分配 Anchor 时不一定只看 IoU有些实现还会加入“尺度匹配”的约束比如真实框和 Anchor 的宽高比超过某个阈值就直接忽略。因为 IoU 大是一个整体结果并不保证形状匹配一个正方形 Anchor 和尺寸相近的竖长框也可能有不错的 IoU但如果宽高比偏离太多网络学起来会很吃力。所以不少训练管线里先按宽高比粗筛再在剩余候选里选 IoU 最高的这样分配更稳定。2.3 预测时的解码公式Anchor 只是先验最终框靠回归Anchor Boxes 本身不会移动它只是定义了初始形状和位置。网络在每个位置要预测的是“相对该 Anchor 的偏移量”。以 YOLO v3 风格的解码方式为例bx sigmoid(tx) cx by sigmoid(ty) cy bw pw * exp(tw) bh ph * exp(th)这里cx, cy是该网格左上角在整张图上的坐标pw, ph是 Anchor 的宽和高tx, ty, tw, th是网络预测的四个原始值最终bx, by, bw, bh才是解码后的边界框。两个关键设计都值得展开。第一中心偏移tx, ty必须经过 sigmoid 压缩到 0 到 1 之间。为什么要这样因为网格负责的物体中心必须落在当前网格内部如果不加约束让网络随意预测偏移训练早期输出稍微一波动中心点就可能飘到十万八千里外梯度信号会被拉得极不稳定。加上 sigmoid 之后中心点最多只能在当前网格内移动网络要学的任务简单了很多。第二宽高偏移用exp(tw)转换保证解码后的宽高永远是正数而且既可以比 Anchor 小也可以比 Anchor 大。这是因为物体尺寸本身跨度可能非常大在指数空间里回归一个倍数关系比直接回归绝对像素值更符合数据分布。这个解码过程在几乎所有 YOLO 系代码里都能看到你自己实现或改写模型时也建议直接沿用这套公式不要轻易改成直接回归绝对坐标。早期 YOLO v1 就是直接回归网格内的坐标偏移训练时非常容易发散后来加入 Anchor 并改成这种受约束的偏移预测稳定性才真正解决。2.4 工程中 Anchor 怎么定聚类和人工经验的取舍Anchor 的尺寸和宽高比不会自己从天上掉下来。最常见也最可靠的做法是统计你自己数据集的标注框用 k-means 聚类得到一组典型形状。这里有个细节聚类距离不能用欧氏距离而要用 IoU 相关的距离比如d 1 - IoU(box, centroid)。原因很好理解欧氏距离对大尺寸框的偏差惩罚很大会让聚类结果偏向中等大小的框而 IoU 距离是尺度无关的聚出来的形状更符合“比例”上的规律。我个人的经验是如果你的数据集只有几千张甚至更少完全可以直接从数据分布里观察几个感性参数有没有明显细长物体有没有宽扁物体小目标占比高不高。根据这三类观察手工预设 3 到 6 组 Anchor 往往也够用。等模型跑通、评估流程建立起来之后再回头用聚类优化。Anchor 数量不是越多越好每层特征图放 3 个在很多项目里都是合理选择放多了会显著增加正负样本不平衡的难度收敛更慢而且带来极多“重复检测”的后处理压力。3. YOLO 算法一次前向把全图候选框全部输出3.1 能称得上“只看一次”的关键输出张量设计YOLO 的哲学是全图一次前向直接得到所有检测框所以关键就在输出张量的结构设计。整张图被划分成 S×S 的网格每个网格预置 B 个 Anchor Boxes网络最终输出的特征张量形状就是 S×S×(B×(5C))。5 对应的是中心偏移和宽高四个值再加一个置信度C 是类别数量。这个张量的每一个切片都直接对应着某个网格、某个 Anchor 的解码结果。你可以把训练结束后运行的网络想象成一个全图扫描器卷积层在整张图上共享计算所有位置的预测都通过同一次前向完成不需要额外再切块、再缩放、再单独跑分类器。这也是它能做到高帧率的关键。对比滑动窗口时代同一个分类器要跑几万次YOLO 等于把所有窗口上的运算合并成了一整张图的卷积计算自然快几个数量级。在使用上这个输出张量有两条解码路径。训练时你把它和真实框算损失并反向传播推理时你把张量解码成边界框、置信度和类别概率再做后处理。很多初学者会把训练和推理的逻辑搞混以为模型直接输出的是“最终的框”实际上是解码和后处理共同完成了最终结果。3.2 置信度到底在预测什么YOLO 里每个预测框都带一个置信度但它的含义比分类概率要复杂一点。置信度被定义为P(obj) × IoU(pred, truth)前一项表示“当前 Anchor 对应的位置是否存在物体”后一项表示“预测框和真实框到底匹配到什么程度”。这两项的乘积才是一个完整的质检指标——既要有物体又得框得准。这也是很多人第一次自己写损失函数时最容易犯的错误把真实标签里的 0/1 直接当成置信度去监督网络。这样训练出来的模型推理时会出现一个很尴尬的现象网络对某个区域极其确定“有物体”但框很不准按置信度排序时它的分数还很高后面的 NMS 又把其他质量更好的框给抑制掉了。正确做法是让置信度去拟合当前预测框和真实框之间的 IoU也就是说置信度是动态变化的监督信号而不是静态标签。模型在训练时会逐渐学到“什么样的输出会导致框不准从而压低置信度”这在推理排序阶段非常重要。3.3 训练阶段的损失组成正负样本怎么平衡再往下拆整个训练损失大致可以分成三块坐标损失、置信度损失、分类损失。坐标损失只作用于被分配了真实框的那些 Anchor 上网络要在这些位置把tx, ty, tw, th回归准。分类损失也比较直接但注意分类只在“有物体的 Anchor”上计算因为背景 Anchor 没有类别可言。最需要小心的是置信度损失。一张 416×416 的图特征图上可能有上千个 Anchor其中绝大多数是背景。如果让所有背景 Anchor 的置信度都往 0 压负样本数量会碾压正样本模型很快会陷入“什么都预测成 0”的陷阱。这里就有工程上常见的处理手段设置一个 ignore 阈值那些和某个真实框 IoU 处于中间区间比如 0.3 到 0.5 之间的 Anchor既不算是正样本也不算是负样本它们不贡献损失只有低于 ignore 阈值的 Anchor 才作为负样本压制。实际训练时你可能还会进一步做样本均衡比如限制负样本总数或者用类似焦损失函数的加权思想让训练更集中在难例上。总损失 正样本坐标损失 正样本分类损失 有物体 Anchor 的置信度损失 无物体且低于 ignore 阈值的 Anchor 的置信度损失3.4 后处理为什么一定要做 NMSYOLO 输出的一堆预测框里同一个物体往往会被相邻网格、或者同一个网格里的不同 Anchor 同时命中。如果不去重画出来的框会密密麻麻叠在一起没法用。去重用的就是非极大值抑制流程很简单把所有预测按分数降序排列取当前最高分的框然后遍历剩余框删掉与当前最高分框 IoU 超过阈值的重复框再取下一高分的框重复操作直到遍历完成。这里有两个参数实战中值得反复调。第一个是置信度阈值一般先设到 0.25 左右做粗筛把明显低质量的预测丢出去千万不要一开始设到 0.5否则一些真阳性的预测被提前过滤后续调参时很难判断模型到底是没检测出来还是被阈值误杀了。第二个是 NMS 的 IoU 阈值通常取 0.5 左右阈值太严两个靠得很近的同类物体会被误合并成一次检测阈值太松又会留下大量重复框。NMS 建议按类别分别做不同类别的框即使重叠度高也大概率是两个不同物体不该互相抑制。4. 候选区域Region Proposals一条“先粗筛、再精判”的老路线4.1 从选择性搜索说起它和滑窗/锚框的根本区别如果 Anchor Boxes 是靠“形状模板”分散在全图每个位置那候选区域路线一开始的思路完全不同先用图像处理里的底层线索找出图上“可能含有物体的区域”再把这些区域交给分类网络精细判断。这个过程也叫区域提议最经典的实现是选择性搜索。它看颜色、纹理、边缘、区域大小等信息把相似的像素小块不断合并最终输出一两千个可能包含物体的候选框。这条路线的好处是候选区域是“按图生成的”而不是均匀铺满全图场景里有物体的地方才产生密集候选纯背景区域候选天然偏少。但早期实现有一个致命问题每个候选区域都要单独从原图裁剪、缩放到固定尺寸再送入卷积网络提取特征几千个候选框就意味着几千次前向速度完全不可用。也就是说候选区域这个思路本身有价值但“先裁剪再分别计算”的落地方式不划算。4.2 两阶段演进共享卷积、RoI 池化、用网络代替搜索这条路线的后续演进特别值得体会因为每一步都在优化同一个瓶颈怎么让这一两千个候选区域的计算尽可能共享。第一步改进是让整张图只过一次卷积得到共享的特征图每个候选区域只需映射到特征图上的对应区域再用 RoI 池化把不同大小的区域压成固定尺寸统一送入后面的全连接层做分类和回归。这一步让候选框的数量不再直接影响卷积计算量速度一下子提了上来。第二步改进更关键既然网络已经很强了为什么还要用选择搜索这种传统视觉方法去找候选干脆让网络自己学一个区域提议网络这就是 Faster R-CNN 里的 RPN。RPN 的工作原理是把特征图上每个位置当作潜在物体中心每个位置放若干组 Anchor Boxes预测每个 Anchor 是前景还是背景同时粗略回归物体边界最后选出分数最高的几百个候选区域送到下一阶段精判。到这里你就看出来了候选区域路线发展到最后内部也在用 Anchor Boxes。所以准确的说法是Faster R-CNN 本身就是一个“用 Anchor Boxes 生成候选区域再做精细分类回归”的两阶段系统。Anchor 不是 YOLO 专有的而是一个通用的候选生成工具区别只在于后续流程走“一步到位”还是“两段精修”。4.3 单阶段与双阶段的取舍速度、精度、小目标既然有了两条路线实际项目里到底选哪个不能只看谁更新要看场景约束。单阶段模型把分类和回归放在一次前向里完成速度优势显著尤其适合实时视频流、移动端、嵌入式这些算力有限且对延迟敏感的场景。但它也有天然短板全图上绝大多数 Anchor 是背景正负样本极端不平衡训练时网络很容易被大量简单负样本带偏如果缺少好的样本均衡策略精度上限会受影响。两阶段模型先用 RPN 粗筛候选粗筛阶段把大部分背景过滤掉了第二个阶段拿到的候选区域里正样本比例要高不少分类器学起来更容易精度也更容易做高。它在小目标检测上通常也更占优因为候选区域经过 RoI 池化相当于做了局部特征放大小目标的信息不会被淹没在整个特征图里。而单阶段模型早期没有这个操作小目标信号很容易在中途丢失这也是为什么后来的 YOLO 要靠特征金字塔、多层特征融合来补救小目标检测。对比向单阶段YOLO 系两阶段候选区域路线候选生成Anchor 直接负责最终预测RPN 先用 Anchor 粗筛候选推理速度快适合实时慢通常适合离线或高精度小目标表现需靠多层特征融合补强天然占优RoI 池化放大细节训练难度正负样本极度不均两阶段后样本较均衡工程复杂度相对简单端到端一体化两个阶段独立训练或联合训练管线更复杂5. 从这三节课程到真实项目我踩过的几个坑5.1 坑一把自己框死在“一个格子只能预测一个物体”我在做第一个检测小项目时一度以为每个网格无论有多少 Anchor最终只能输出一个检测框。这个误会让我的标签分配逻辑写得很扭曲导致训练出来的模型总是漏检密集场景里的物体。实际上 Anchor 数量直接决定了每个网格最多能同时预测多少个目标可前提是每个目标都分配到了不同的 Anchor。真正难处理的场景是两个同类物体中心恰好落在同一个网格且它们匹配到的正好是同一个 Anchor。这时一个 Anchor 要对两个真实框负责训练信号互相拉扯模型会学着在两个目标之间打折扣。这种问题靠 Anchor 本身解决不了要靠更细的网格划分、多尺度特征融合或者在后处理时容忍其中一次检测失败。5.2 坑二置信度当成分类概率这个坑我在 3.2 里提过但值得单独拿出来说因为它太隐蔽了。很多项目里的人们图省事给置信度标签填的是 0 或者 1训练损失降得很漂亮一到推理阶段就翻车。后来我把置信度换成 IoU 监督才看到明显效果模型开始学会对边界框质量差的结果给低置信度NMS 排序也一下子正常了。这个细节在课程材料里往往只是一句话带过但工程上直接决定模型能不能用。如果你的模型明显出现一堆半准不准的框排在前面先检查置信度监督方式大概率问题就在这儿。5.3 坑三Anchor 尺寸是拍脑袋定的用别人预训练好的默认 Anchor 跑自己的数据集是起步最快的路线但千万不要默认它们适配你的场景。我当时做的是横向细长目标检测默认 Anchor 里宽高比最大的也就 3:1 左右目标经常被横向拆成两半训练时标签分配反复飘移。后来我把标注框的宽高分布画了个散点图发现绝大多数目标的宽高比集中在 4:1 以上于是重新聚类 Anchor同一个模型的其他参数完全没动检测效果立刻提升了一大截。这件事给我的教训是Anchor 数量、尺寸、每层对应的缩放比例都是可以直接从标注数据里统计出来的超参数不要凭感觉拍脑袋。5.4 一个小技巧把输出张量亲手推一遍如果让我给正在学这段内容的人提一个最有效的建议那就是把给定输入、核对锚点数量、核对下采样倍数亲手把预测张量的 shape 和每个维度对应的含义写一遍再把解码公式代入几个边界例子算一算。这件事不用写代码拿支笔在纸上推效果比看十遍讲解都明显。我自己每次接触一个新的检测模型都是从这个“手推张量形状”的动作开始的它能迫使你把概念之间的边界彻底对清后面调参和改代码时才不会四处瞎试。推演的时候可以拿一个具体数字走一遍比如输入宽高 416下采样 32 倍特征图是 13×13每格放 3 个 Anchor类别数 5 类那么输出张量就是 13×13×3×(55)也就是 13×13×30。再把 3.2 的置信度定义、2.3 的解码公式带到几个坐标值里算一遍你很快就能理解为什么中心偏移要用 sigmoid为什么宽高要用指数也就能明白训练和推理阶段面对同样的张量为什么做的事完全不同。回头再看这三节连续课程Clé de tout其实就是那三个问题的分工Anchor 定形状YOLO 定框架候选区域定流程。把它们的边界理清之后再去读任何一篇现代检测网络的文章或者打开一个开源实现的配置文件都会轻松很多。