资讯动态

目标检测核心机制解析:从Anchor Boxes到YOLO的算法原理与实战

发布时间:2026/10/17 9:45:12 来源:尧图企业网站定制
目标检测可能是计算机视觉里最“出圈”的任务之一。图像分类回答“图里有什么”目标检测还要回答“东西在哪”——给每个目标画一个框。这看起来只多了一步但正是这一步逼出了Anchor Boxes、YOLO、候选区域这一整套经典设计。我在深度学习专项课程第四门课的第三周里完整过了一遍这部分内容视频编号正好是3.8到3.10三个小节串起来就是一张目标检测的技术地图先讲为什么需要Anchor Boxes再讲YOLO怎么把检测变成一次前向传播最后补上候选区域这条经典路线。这篇文章就把这三节内容拆开揉碎结合实际实现中的经验帮你把这套东西真正吃到脑子里。不管你是刚学完卷积神经网络基础、准备转向检测方向的学生还是已经在用某个检测框架、但对底层原理一知半解的开发者这篇内容都适合。我把每个概念背后的“为什么”讲清楚再给出可以直接上手的公式、代码和调参经验让你看完之后不仅能看懂课程代码还能自己动手改一版YOLO出来。1. 目标检测的整体图景从图像分类到定位检测1.1 目标检测到底在解决什么问题先明确一下任务定义。给定一张图片目标检测要输出两类信息图片里有哪些类别的目标以及每个目标的位置。位置通常用一个矩形框表示矩形框可以用两个坐标点表示左上角和右下角也可以用中心点加宽高表示。在课程里用的约定是 ( (b_x, b_y, b_h, b_w) )中心点坐标加上框的高度和宽度。这个任务和图像分类有一个本质区别输出长度不固定。分类任务不管图片里是什么输出永远是固定长度的概率向量比如1000个类别就是1000个数。但检测任务里图片里可能有0个目标、5个目标、50个目标输出数量是动态的。这一条听起来简单实际上决定了整个检测算法的设计走向。动态输出长度怎么处理你想到了两条路。第一条先找出一些候选位置再对每个候选位置单独判断“这里有没有目标、是什么目标”这就是候选区域路线。第二条干脆把图片划分成固定网格让每个网格区域负责预测固定数量的目标把所有预测结果一次性输出这就是YOLO的路线。1.2 传统办法为什么走不通课程之前的内容里大家可能接触过滑动窗口法。思路很直观用不同尺寸的窗口在图片上滑动每个窗口送入CNN分类判断窗口内是什么。这个方法在简单场景下可行但问题非常明显。第一计算量爆炸。一张图片上要跑的窗口数量极多每个窗口都要做一次完整的前向传播算力吃不住。第二窗口尺寸和长宽比不好选。目标有大有小有的细长有的接近正方形固定尺寸的窗口必然漏检。第三即使加了滑动窗口定位精度也差因为窗口和目标实际位置很难严丝合缝。所以课程里才会引出更聪明的方法。一个核心思想是不再用窗口去“框”目标而是让模型直接回归出目标框的坐标另一个思想是先粗选出可能有目标的区域再做精细分类。1.3 两条技术路线候选区域与直接回归课程3.9讲候选区域3.8讲Anchor Boxes和YOLO这两条路线其实是同期发展的两种思路。候选区域方法后来演化成R-CNN家族分两步走先生成可能包含目标的候选区域再对每个区域做分类和位置修正。YOLO走的是另一条路把检测视为一个回归问题一次前向传播直接输出所有目标框。你不需要二选一这两条路线都非常重要。现在工业界的主流One-Stage检测器比如YOLO系列源头是直接回归思想而Two-Stage检测器比如Faster R-CNN源头是候选区域思想。但这两条路线的底层组件是共通的图像特征提取、边框回归、IoU匹配、非极大值抑制。所以不管学哪条路线这些基础能力都是通用的。2. Anchor Boxes 锚框机制详解2.1 什么是Anchor Boxes它解决了什么问题Anchor Boxes翻译成锚框是在图片上预先放置的一组固定形状和大小的参考框。之所以需要锚框是因为直接让模型回归任意形状的框太困难了。目标的位置和形状变化无穷模型直接输出四个坐标数很难收敛也不稳定。锚框的思路是我先给你一些“模板”你不需要从零预测框的形状只需要预测“相对于某个模板的偏移量”。比如一个锚框定义是宽100、高50模型预测的偏移量是宽增加10%、高减少5%最终框的尺寸就是110和47.5。这样一来模型的输出从“绝对坐标”变成了“相对偏移”预测难度大大降低训练也更稳定。这个思想在课程里强调得非常清楚。你可以把锚框理解成先验知识在自然图片中目标一般不会是完全不规则的形状绝大多数目标可以近似成若干种比例的矩形。基于这一点预设一组覆盖常见形状的锚框等于是把人类对“目标形状分布”的直觉注入模型。2.2 锚框如何与真实目标匹配锚框不是凭空起作用的它要在训练时和图片中真实标注的目标框Ground Truth做匹配。匹配的度量标准就是IoU交并比。IoU的计算是两个框的交集面积除以并集面积值在0到1之间。如果锚框和真实框的IoU超过某个阈值就说这个锚框“匹配”上了这个目标它就有资格负责预测这个目标。课程4.2节对应视频编号里的概念细节里讲的匹配逻辑是对于一个网格单元如果目标中心落在该网格内那么该网格中与这个目标IoU最大的锚框被选中负责预测这个目标。其余锚框如果IoU不够高会被标记为负样本即没有目标需要预测。这里有个容易被忽略的细节目标只用一个锚框来负责就是IoU最大的那个其他锚框即使也有一些覆盖面积也不会同时负责同一个目标。这个设计是为了避免多个锚框都去预测同一目标导致输出混乱。实际训练中一个网格单元如果有多个目标中心落在里面那就用多个锚框分别匹配多个目标。2.3 多锚框带来的输出变化加入锚框机制后神经网络的输出张量结构会发生显著变化。假设图片被划分成 ( S \times S ) 的网格每个网格单元定义 ( B ) 个锚框类别总数为 ( C )。那么网络的输出就是一个形状为 ( S \times S \times (B \times (5 C)) ) 的张量。这里的5代表每个预测框包含的5个值( t_x )、( t_y )中心坐标偏移量、( t_w )、( t_h )宽高缩放量和一个置信度Confidence Score。置信度用来表示“这个框内确实有目标”的概率等于 ( P(目标) \times IoU_{预测框与真实框} ) 的乘积。这个输出结构你需要死记硬背它就是YOLO系列最核心的接口。无论网络结构怎么改最后输出的都是一个这样的多维张量后续的损失计算、NMS后处理全都建立在这个结构上。如果这里没想明白后面代码一定是云里雾里。2.4 锚框的超参数选择锚框的尺寸、数量、长宽比并不是随便定的。课程里用的是固定的经验值比如3种尺寸搭配3种长宽比一共9个锚框。但在实际项目中更好的做法是从你的训练数据中统计出目标框的形状分布用K-Means聚类算法自动找到合适的锚框尺寸。我在项目里实测过聚类出来的锚框比默认锚框效果提升明显尤其当你的数据集目标形状和通用数据集差异很大时。比如做行人检测目标大多是瘦高的做车辆检测目标大多是扁宽的。锚框贴合数据后模型收敛更快精度上限也更高。具体操作很简单把训练集里所有标注框的宽和高提取出来用K-Means聚类成 ( K ) 类用IoU作为距离度量。 ( K ) 的取值一般是9或15要平衡粒度与计算量。聚类的目的是让每个目标都有与之相近的锚框这样模型只需要做小幅修正即可。3. YOLO算法从候选区域到端到端检测3.1 YOLO的核心设计理念YOLO的全称是You Only Look Once只看一次。这个名字已经概括了它的全部哲学整个检测过程只进行一次前向传播网络同时输出目标的位置和类别不需要任何额外的区域建议流程。为什么能做到因为YOLO把目标检测彻底看成一个回归问题。输入一张图片输出一个形状固定的张量解码这个张量就得到所有目标的框和类别。在课程3.8中讲解的正是这个基本思路把图片分成 ( S \times S ) 网格每个网格负责预测中心落在它区域内的目标。YOLO的核心优势是速度。因为只有一个网络、一次前向传播没有重复计算在配置合适的硬件上能做到实时检测。但它的早期版本也有缺点每个网格最多预测 ( B ) 个目标对密集小目标不友好另外直接回归坐标比候选区域方法更难优化。3.2 网格与预测张量设计YOLO将输入图片划分成 ( S \times S ) 的网格比如7×7。每个网格单元独立做出预测它的责任是如果某个目标的中心点落在这个网格内网格负责预测这个目标。每个网格单元预测的信息包括( B ) 个边界框每个边界框有5个参数中心坐标 ( x, y )、宽高 ( w, h )、置信度 ( c )( C ) 个类别概率表示这个网格注意是网格不是每个框内目标属于各类别的概率。这里有个非常容易混淆的点类别概率是网格级别的不是边界框级别的。早期YOLO的实现中每个网格只预测一组类别概率也就是说同一网格内检测出的所有框共享同一组类别概率。后来YOLOv3改进了这一点每个锚框预测自己的类别概率。输出张量的维度就是 ( S \times S \times (B \times 5 C) )。例如使用7×7网格、2个锚框、20个类别输出就是 ( 7 \times 7 \times 30 )。如果你写过YOLO系列代码会发现后续版本的输出只是在这个基础上做了更细的拆解和更深的特征层。3.3 置信度与类别概率如何组合训练和推理时YOLO会计算最终的检测分数。置信度 ( c ) 表示“这个框内含有目标”的信心类别概率 ( P(class_i) ) 表示“如果框内有目标它是第 ( i ) 类”的概率。推理时两者相乘得到最终的类别置信度[ \text{score} P(class_i) \times \text{confidence} ]这个乘积的意义是既要框内有目标又要类别判断正确两者缺一不可。如果一个框置信度很高但类别概率很低乘积后分数依然不高说明模型对这个目标区域有信心但不确定具体是什么类别这种情况通常发生在相似类别之间。在课程中这个乘积被反复强调它是后续阈值过滤和NMS的依据。实际实现时你会先设定一个阈值比如0.5低于阈值的检测框直接丢弃高于阈值的保留进入NMS环节。3.4 非极大值抑制NMS是非极大值抑制它的作用是处理大量重叠的预测框。由于一个目标可能被多个网格或锚框同时预测到YOLO输出中会出现多个高度重叠的框这些框都指向同一个目标需要合并。NMS的流程很简单把所有预测框按照得分从高到低排序取得分最高的框它被确定为一个目标的最终框计算其余框与这个框的IoUIoU超过阈值的框被认为是重复框直接删除重复第2、3步直到处理完所有框。NMS的阈值是经验值一般取0.4到0.5。阈值太高重叠框保留太多阈值太低相邻的不同目标会被误删。我在实际调试中发现对于密集场景比如人群计数NMS阈值要适当降低到0.35左右对于稀疏场景可以调到0.6。课程中没有深入展开NMS的代码实现但这部分几乎是所有检测项目必写的后处理函数。后面我会给出一段可用的NMS实现直接抄作业即可。4. 候选区域方法另一条经典路线4.1 R-CNN家族的核心逻辑候选区域思路的代表是R-CNNRegion-based CNN。它的核心逻辑是先用某种方法在图片上生成2000个左右可能包含目标的候选框把所有候选框缩放到固定尺寸然后逐一送入CNN提取特征最后用SVM分类器判断候选框类别同时训练一个回归器修正候选框的位置。这个方法的效果在当时是颠覆性的比传统方法精度高一大截。但它的问题也很致命2000个候选框要跑2000次CNN前向传播速度极慢。之后Fast R-CNN改进了这一点让整张图片只过一遍CNN得到特征图再从特征图上对应位置裁剪出候选区域的特征后接全连接层分类和回归速度大幅提升。Faster R-CNN更进一步把候选区域生成也交给网络自己完成这就是RPNRegion Proposal Network。4.2 选择性搜索如何生成候选区域在R-CNN时代候选区域是由选择性搜索Selective Search算法生成的。它不是用深度学习而是基于传统图像处理。核心思想是先把图片分割成大量小区域然后根据颜色、纹理、大小、形状相似度迭代地合并相邻区域最终形成不同尺度的候选区域。选择性搜索的角色在课程3.9中做了说明它提供的是“可能包含目标的区域”。因为是基于图像底层信息颜色、纹理生成所以它能覆盖各种位置和尺度不太会漏掉目标但结果也有大量误报——许多候选区域实际上不含目标。为什么深度学习时代还需要了解这个因为“由粗到细、先生成候选再判断”这个思路本身是普适的。如今的Transformer类检测器演变出的稀疏候选机制本质上仍然是“生成一个候选框集合再逐个分类和回归”。理解了这个你就理解了Two-Stage架构为什么精度高而速度慢。4.3 两阶段 vs 单阶段用一张表格做对照这样最直观对比维度单阶段YOLO系两阶段R-CNN系检测流程一次前向传播直接输出先生成候选再分类回归推理速度快可实现实时慢难以实时检测精度早期版本低后续版本追平一贯高精度训练复杂度较低损失函数需自己设计较高需要多任务训练小目标表现早期差YOLOv3后多尺度改善较好ROI处理较精细代表算法YOLO、SSD、RetinaNetFast R-CNN、Faster R-CNN现在做项目时选型会考虑两点如果对速度有硬性要求比如工业实时质检、视频结构化基本选YOLO系如果对精度要求极高任务复杂、且没有延时压力Faster R-CNN仍是可靠选项。不过YOLOv5、YOLOv8之后单阶段在多数基准上已经不输两阶段所以新项目我基本都推荐从YOLO系入手。5. 实操要点与复现心得5.1 从零实现YOLO的关键步骤理解原理之后动手实现是必经之路。课程中提供的实现框架比较简洁但你自己动手时会发现细节特别多。我的建议是先跑通一个最小可用版本再逐步增加功能。最小可用版本包含四步构建基础卷积网络替换最后几层的全连接层输出形状定为 ( S \times S \times (B \times 5 C) )读取训练集的标注信息把真实边框转换成目标网格坐标格式定义损失函数包含三部分坐标损失、置信度损失、类别损失训练过程中穿插验证每若干轮打印预测框可视化结果。代码上先用一个简单的数据集跑通。自行车、行人的单一类别数据集最容易上手避免一上来就处理多类别复杂的匹配逻辑。5.2 IoU与NMS的代码实现细节IoU的实现非常基础但也是检测代码里最容易被写错的地方。以下代码是我常用的实现逻辑清晰坐标以格子内的相对值表示def compute_iou(box1, box2): box格式: [x1, y1, x2, y2] 左上角和右下角坐标 x1 max(box1[0], box2[0]) y1 max(box1[1], box2[1]) x2 min(box1[2], box2[2]) y2 min(box1[3], box2[3]) inter_area max(0, x2 - x1) * max(0, y2 - y1) box1_area (box1[2] - box1[0]) * (box1[3] - box1[1]) box2_area (box2[2] - box2[0]) * (box2[3] - box2[1]) union_area box1_area box2_area - inter_area iou inter_area / union_area return iouNMS实现如下def nms(boxes, scores, iou_threshold0.5): boxes: [[x1, y1, x2, y2], ...] scores: [score1, score2, ...] order scores.argsort()[::-1] keep [] while order.size 0: i order[0] keep.append(i) ious torch.tensor([compute_iou(boxes[i], boxes[j]) for j in order[1:]]) mask ious iou_threshold order order[1:][mask] return keep注意几个细节坐标要统一格式不要中心和宽高混用计算IoU时两个框如果完全不重叠交集面积应为0NMS中对IoU的判断要用“小于等于”保留非重复框。5.3 锚框聚类的操作方式前面提过K-Means聚类锚框这里展开讲操作细节。聚类时不能用标准K-Means的欧氏距离因为大小不同的框距离权重不同无法反映形状相似性。正确做法是使用距离度量[ \text{distance}(box, centroid) 1 - \text{IoU}(box, centroid) ]这样距离越小表示两个框形状越接近聚类结果更合理。聚类过程就是不断计算每个标注框到簇中心的IoU距离分配到最近的簇迭代更新簇中心取簇内所有标注框的宽高均值。聚类完成后把K个簇中心对应的宽高作为锚框预设值。使用锚框时关键的一步是归一化锚框尺寸要除以特征图的大小比如13×13、26×26得到相对宽高这样才能在不同尺度的特征图上使用。5.4 训练自己的检测器注意事项自己训练一版YOLO类检测器时有几个很容易踩的坑。第一正负样本不平衡。一张图片里可能有几十个锚框但真正匹配上目标的锚框只有几个负样本数量远大于正样本。不加处理的话模型会学会“什么都预测为空”置信度全部趋近于0。课程代码里用的办法是只计算置信度损失时给负样本一个较小的权重正样本的坐标和类别损失正常计算。我在项目中建议将负样本的置信度损失权重设在0.5到0.8之间正样本权重保留为1。第二学习率过大导致训练不稳定。坐标回归部分对学习率很敏感。先用一个较小的学习率比如0.001观察损失曲线如果震荡剧烈就降一半。我的经验是坐标损失和类别损失需要分阶段调节先让网络学会“找到目标”再让网络“精调坐标”。第三验证时使用和训练一致的数据增强策略。YOLO类模型常用的增强包括随机翻转、随机缩放、颜色抖动。如果增强只用在训练集而验证集没用验证时模型会“不适应”导致精度虚低。这个坑很多人踩过我建议验证时也做同样的增强或者把增强统一为一个独立的预处理函数供训练和验证共用。6. 常见问题与排查记录6.1 为什么训练时正样本太少课程代码里匹配正负样本的逻辑看起来简单但实际运行时可能发现匹配上的正样本极其稀少。原因是锚框和真实框的IoU普遍不高尤其是目标小或者形状极端时。排查方法打印每个真实框与所有锚框的最大IoU看看分布情况。如果最大IoU普遍低于0.5说明锚框尺寸和数据分布差异太大需要用聚类重新生成锚框。另外一个常见改进是调整匹配阈值头部增长阶段可以适当降低IoU阈值比如从0.5降到0.4让更多锚框参与正样本训练。6.2 小目标检测不出来怎么办小目标是YOLO早期版本最头疼的问题。原因在于小目标在高层特征图上可能只有一两个像素特征信息几乎丢失。课程3.8版本的代码在7×7网格上检测小目标基本只能靠运气。解决办法有两条路。一是多尺度预测在多个不同分辨率的特征图上分别做检测浅层特征图负责小目标深层特征图负责大目标。这是YOLOv3之后的标配。二是提高输入图片分辨率训练时用608×608甚至更大。但分辨率提高对显存压力很大需要权衡。我的建议是先上多尺度预测再根据硬件情况调分辨率。6.3 预测框大量重叠如何调参预测框大量重叠通常有两个原因NMS阈值设置不当或者不同网格的多个锚框同时匹配了同一目标。NMS阈值需要根据目标分布调整。对于密集小目标IoU阈值可以设到0.35对于稀疏大目标可以设到0.6。此外注意NMS操作的是同一类别内的框不同类别之间的重叠不需要处理。如果发现两个不同类别比如猫和狗的框重叠严重那不是NMS的问题可能是多目标标注本身就重叠不用管。6.4 课程代码调试踩坑实录课程3.8-3.10相关代码我在调试过程中遇到过一个非常隐蔽的问题标签编码时中心坐标的计算。课程使用的约定是在格子内做归一化( x )、( y ) 的取值范围是0到1代表目标中心在格子内的相对位置。但我最初理解成整张图片的相对坐标导致训练完全无法收敛。排查了整整两天打印了编码后的标签和原始标注做对比才发现问题。这个坑提醒我一定要写一个“编码-解码”的往返测试把标签编码成张量再解码回去可视化对比确认无误后再开始训练。另外课程代码早期版本中置信度标签是匹配上的框取IoU值未匹配的框取0。但在实际实现中如果正样本的置信度直接用IoU值而预测用的是sigmoid输出训练时梯度会偏小。更稳妥的做法是将正样本置信度设为1让模型自己去学IoU的近似值。这个小改动能让收敛速度明显加快。我在实际跑代码时还发现YOLO的损失函数里坐标部分包含开根号操作目的是平衡大小框之间的偏差。大框即使偏移几个像素也看不出来小框偏移几个像素就偏差很大。开根号让模型对小框的预测更加谨慎这个细节在课程代码里用了但最初我直接照抄没理解一直到自己在项目中遇到小目标漏检才真正体会它的价值。最后再分享一个小技巧验证模型时不要只看mAP多保存几张可视化预测图观察框和目标的贴合程度。mAP是一个综合指标但有些时候框位置偏差很大却仍能靠IoU踩线及格真正部署时会发现用户体验极差。可视化图能帮你直观看到模型在哪些场景掉链子比如光照变化、遮挡、目标密集。善用这些直观反馈你才能把YOLO这类算法调成真正能用的程度。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑