YOLO网格与边界框:目标检测的底层范式解析
1. 为什么YOLO的“网格”和“边界框”不是技术细节而是整个目标检测范式的底层心脏你翻过YOLO论文也跑过v5、v8甚至v11的训练脚本但可能一直没真正“看见”那个被反复提及却极少被拆解的底层结构grid。它不是代码里一个叫grid_size的参数也不是画在图上几条浅灰色的辅助线它是YOLO把“世界”强行切片、再逐格审查的暴力哲学。而bounding box更不是模型输出的四个数字——它是这个切片世界里每一格被赋予的“视觉主权”这一格到底要为哪块像素负责它的责任边界在哪它的置信度有多高它的形状该长成什么样这组概念之所以成为YOLO区别于Faster R-CNN、DETR等所有主流检测器的分水岭根本在于它彻底放弃了“先找候选区域、再分类回归”的两阶段冗余逻辑。YOLO说我不找我直接分。把整张图像像切蛋糕一样切成S×S个等大的网格单元grid cell每个单元天生就“认领”自己辖区内的目标中心点。如果一个目标的中心落在第3行第5列的格子里那这个格子就自动获得对该目标的全部检测权——它必须预测出这个目标的类别、置信度以及最关键的这个目标在本格子坐标系下的宽高比例与偏移量。这种设计带来的连锁反应极其深刻。它让YOLO快得离谱因为没有RPN网络生成上千个anchor proposal的计算开销但它也让YOLO对小目标、密集目标、边界模糊目标异常敏感——因为一个格子只能负责一个中心点两个中心点挤进同一格必然有一个被“吃掉”。你调参时发现mAP上不去八成不是学习率错了而是你的grid size选得太大把本该分开管的两个小目标硬塞进了一个格子你后处理时NMS抑制过度很可能不是IOU阈值设高了而是每个格子预测的bounding box本身形状太散、置信度分布太扁平。所以这不是一次“讲解YOLO原理”的知识复述而是一次对YOLO骨架的解剖。接下来我会带你亲手画出那个S×S网格用最原始的坐标变换推导出bounding box的归一化公式还原YOLOv3中9个anchor先验框如何被嵌入每个格子解释为什么YOLOv5的动态anchor匹配机制能缓解网格僵化问题最后用一段不到20行的NumPy代码让你亲眼看到当一张640×480的图片被划分为20×15的网格后每个格子是如何从原始像素坐标一步步算出它该预测的tx, ty, tw, th的。你将明白所谓“YOLO训练”本质上就是教会每个格子如何精准地校准它对自己辖区那块像素的“视觉直觉”。2. 网格Grid的物理意义与数学实现从图像切片到坐标系重定义2.1 网格不是抽象概念是YOLO强制施加的空间主权划分很多人误以为YOLO的grid是某种神经网络内部的隐式特征图划分。错。它是一个硬编码的、不可学习的、由输入图像尺寸和网络下采样倍数共同决定的刚性结构。以YOLOv5s为例其主干网络CSPDarknet53最后一层特征图的下采样倍数是32。这意味着如果你输入一张640×640的图像经过32倍下采样后得到的特征图尺寸就是20×20。这个20×20就是YOLOv5s的grid sizeS20。它不是模型学出来的而是由网络架构和输入尺寸共同锁死的物理事实。提示你可以用torchsummary打印YOLOv5模型的各层输出尺寸找到最后一个卷积层的输出shape其H和W就是S值。不要依赖文档里的“默认20×20”实测你的输入尺寸和模型版本才是唯一真相。这个20×20网格每一个cell都对应着原始图像上一块32×32像素的正方形区域因为640/2032。它像一张覆盖在原图上的透明坐标纸把连续的像素空间粗暴地离散化为400个独立的、互不重叠的管辖区域。每个cell的职责非常明确只负责预测落在自己这块32×32像素区域中心点的目标。注意是“中心点”不是整个目标轮廓。这是理解YOLO检测逻辑的起点。2.2 坐标系的两次关键重定义从像素坐标到网格坐标再到归一化坐标YOLO的魔力很大程度上藏在这两次坐标系转换里。我们以一个具体例子来推演假设原始图像尺寸为640×480宽×高一个真实目标的标注框ground truth左上角坐标为(100, 80)右下角为(180, 160)。那么它的中心点坐标就是((100180)/2, (80160)/2) (140, 120)。第一次重定义像素坐标 → 网格坐标Grid Coordinate现在我们将这张图划分为20×15的网格注意这里故意用非正方形网格因为640/2032480/1532保证每个cell仍是32×32像素。那么中心点(140, 120)落在哪个cell里cell的列索引x_index floor(140 / 32) floor(4.375) 4cell的行索引y_index floor(120 / 32) floor(3.75) 3所以这个目标被分配给了第3行索引从0开始、第4列的cell。这个cell的物理管辖范围是x∈[128, 160), y∈[96, 128)。而中心点(140, 120)在这个cell内部的相对位置是cx_in_cell 140 - 128 12cy_in_cell 120 - 96 24第二次重定义网格内相对坐标 → 归一化坐标Normalized CoordinateYOLO的损失函数要求所有预测值都在[0,1]区间内便于梯度统一。因此需要将cell内的相对坐标除以cell的宽度和高度都是32得到归一化偏移量tx cx_in_cell / 32 12 / 32 0.375ty cy_in_cell / 32 24 / 32 0.75这就是YOLO输出的tx和ty。它们永远在0到1之间代表中心点在本cell内的相对位置。无论图像多大只要grid size固定tx/ty的取值范围就恒定。这个设计是YOLO能泛化到不同分辨率图像的关键。2.3 网格尺寸S的选择精度、速度与小目标检测的三角博弈选择S20还是S40绝不是拍脑袋决定的。它直接牵动三个核心指标定位精度S越大每个cell越小中心点定位越精细。S40时cell大小为16×16像素中心点误差理论上可控制在±8像素内S20时cell为32×32误差上限翻倍。这对车牌识别、电路板元件检测等高精度场景至关重要。计算速度S越大cell数量呈平方级增长40×401600 vs 20×20400。每个cell都要预测多个bounding box和类别概率计算量直接飙升。在RK3588等边缘设备上S40可能导致推理帧率从30FPS暴跌至12FPS。小目标召回率这是最常被忽视的致命点。一个10×10像素的小目标其中心点必然落在某个cell内。但如果S太小如S10cell尺寸为64×48这个小目标就像一粒芝麻掉进一个大盘子里其几何特征在cell特征图中几乎无法被有效表征导致置信度极低极易被NMS过滤。实测数据表明在COCO数据集上将S从20提升到40对small objectarea32²的AP提升可达8.2个百分点。实操心得我的经验是先用S20快速验证流程然后针对你的数据集做“小目标占比分析”。用脚本统计所有标注框的面积画出面积分布直方图。如果峰值集中在100~500像素²果断上S40如果大部分目标都在2000像素²以上S20甚至S16更经济。别迷信“越大越好”平衡才是王道。3. 边界框Bounding Box的生成逻辑从先验锚点到动态回归的进化史3.1 YOLOv1/v2的朴素思想每个格子预测固定数量的“盒子”YOLOv1是纯粹的“网格思维”奠基者。它规定每个grid cell预测B个bounding boxB2每个box包含5个值tx, ty, tw, th, confidence。其中tx/ty是前述的归一化中心偏移tw/th则是对box宽高的归一化表示但其基准不是cell尺寸而是整张图像的宽高。即pw exp(tw) * anchor_widthph exp(th) * anchor_heightpw和ph才是最终预测的宽高像素值这里出现了第一个关键概念Anchor Box锚框。YOLOv2引入了K-means聚类在COCO训练集的所有标注框上运行得到了9种最具代表性的宽高比如116×90, 156×198等。这些anchor不是凭空设定的而是数据驱动的先验知识。每个grid cell在预测tw/th时会分别关联到这9个anchor中的某一个作为其宽高的“生长基底”。这极大缓解了v1中直接回归绝对宽高带来的训练不稳定问题。3.2 YOLOv3/v5的质变每个格子绑定多个Anchor实现“一对多”映射YOLOv3将anchor机制发扬光大。它不再让每个cell“猜”该用哪个anchor而是为每个cell硬性绑定3个不同尺度的anchor。例如对于20×20的大尺度特征图绑定的是小anchor如10×13, 16×30对于10×10的中尺度图绑定中anchor33×23, 30×61对于5×5的小尺度图绑定大anchor62×45, 59×119。这种设计本质是让不同尺度的特征图各司其职大图抓小目标小图抓大目标。这就引出了YOLO训练中最核心的标签分配label assignment规则一个ground truth框其中心点落在哪个grid cell就由该cell负责预测。但该cell有3个anchor它该选哪个答案是计算该gt框与本cell所绑定的3个anchor的宽高比aspect ratio的IOU选择IOU最大的那个anchor进行匹配。如果一个gt框与多个cell的anchor都产生了高IOUYOLOv5会采用更复杂的“simOTA”或“Task-Aligned Assigner”策略动态选择最优的几个cell-anchor组合而非简单的一对一。3.3 从“预测”到“解码”后处理中bounding box的完整重生路径训练时网络输出的是tx, ty, tw, th这些归一化参数。但在推理时我们必须把它们变回真实的像素坐标。这个过程叫“解码”decoding是后处理post-processing的第一步也是最容易出错的环节。以下是YOLOv5的完整解码公式以20×20 grid为例# 假设网络输出的tensor shape为 [1, 3, 20, 20, 85] # 其中3是anchor数量855(box)80(cls) # 我们取第0个anchor的预测值 pred_tx output[0, 0, :, :, 0] # shape: [20, 20] pred_ty output[0, 0, :, :, 1] pred_tw output[0, 0, :, :, 2] pred_th output[0, 0, :, :, 3] pred_conf output[0, 0, :, :, 4] pred_cls output[0, 0, :, :, 5:] # Step 1: 将tx, ty通过sigmoid映射到[0,1]确保中心点在cell内 cx torch.sigmoid(pred_tx) # [20, 20] cy torch.sigmoid(pred_ty) # Step 2: 计算中心点在原图上的绝对坐标 # grid_x, grid_y 是每个cell在grid上的索引需广播 grid_x, grid_y torch.meshgrid(torch.arange(20), torch.arange(20), indexingxy) grid_x grid_x.float() grid_y grid_y.float() # 每个cell的左上角在原图的像素坐标 cell_x grid_x * 32 # 32 640/20 cell_y grid_y * 32 # 中心点绝对坐标 abs_cx (cx grid_x) * 32 # 注意cx是相对偏移grid_x是cell索引 abs_cy (cy grid_y) * 32 # Step 3: 解码宽高 anchor_w 10.0 # 第0个anchor的宽度像素 anchor_h 13.0 # 第0个anchor的高度像素 pw torch.exp(pred_tw) * anchor_w ph torch.exp(pred_th) * anchor_h # Step 4: 计算最终bounding box的左上和右下坐标 x1 abs_cx - pw / 2 y1 abs_cy - ph / 2 x2 abs_cx pw / 2 y2 abs_cy ph / 2这段代码揭示了YOLO的精髓bounding box不是凭空生成的它是grid cell的索引坐标、网络预测的微小偏移、以及数据驱动的先验anchor三者精密耦合的产物。任何一个环节出错——比如忘了对tx/ty做sigmoid或者anchor尺寸用错了——都会导致bbox严重偏移甚至出现负坐标。4. 网格与边界框的协同作战损失函数设计与训练稳定性根源4.1 YOLO损失函数的三重奏定位、置信度、分类的权重博弈YOLO的总损失Loss是三个部分的加权和L_total λ_coord * L_coord λ_obj * L_obj λ_noobj * L_noobj λ_class * L_class。这个公式里的每一个λ都是工程师用血泪调出来的经验值。而L_coord、L_obj、L_noobj这三项正是网格与bounding box思想最直接的体现。L_coord定位损失只计算那些“负责预测目标”的grid cell。即只有当某个cell被分配了gt框positive sample才计算其tx, ty, tw, th的MSE或CIoU损失。YOLOv5用的是CIoU因为它不仅惩罚坐标偏差还惩罚宽高比和中心点距离对box形状的约束更强。L_obj有目标置信度损失同样只计算positive sample。它要求该cell预测的confidence值无限接近1。这个confidence IOU(predicted_box, gt_box)所以它既是“有没有目标”的判断也是“框得准不准”的度量。L_noobj无目标置信度损失计算所有“不负责预测目标”的cellnegative sample。它要求这些cell预测的confidence无限接近0。这部分损失权重λ_noobj通常设得很高如YOLOv3中为0.5因为negative sample数量远超positive400个cell里可能只有2-3个有目标若不加权模型会倾向于把所有cell都预测为“无目标”以降低总体loss。注意这里的“负责”与否完全由grid cell的中心点归属和anchor匹配规则决定。一个gt框的中心点落在cell A且与A的anchor1匹配最好那么只有cell A的anchor1参与L_coord和L_obj计算cell A的anchor2、anchor3以及所有其他cell都只参与L_noobj计算。这种精确的样本筛选是YOLO高效训练的基础。4.2 网格僵化Grid Rigidity问题为什么YOLO总在“擦边球”上栽跟头这是YOLO最顽固的缺陷源于其网格设计的先天硬伤。想象一个目标其中心点恰好位于两个相邻grid cell的边界线上比如在x160的位置cell0的右边界cell1的左边界。根据floor()运算规则它会被分配给cell0。但如果这个目标本身是细长的其大部分像素其实延伸到了cell1的辖区那么cell0预测的box就会严重偏向左侧而cell1则因为没被分配到任何gt只能拼命把confidence压到0形成一个“虚假的空洞”。结果就是检测框要么偏左要么在NMS时被cell1预测的、置信度稍低的“干扰框”抑制掉。解决方案并非没有但都带着妥协提高grid sizeS让边界线更多中心点“恰好在线上”的概率降低。但如前所述计算量飙升。使用Soft-NMS或DIoU-NMS在后处理时不简单地用IOU阈值硬过滤而是根据IOU大小对重叠框的置信度进行衰减。这样即使cell0和cell1都预测了它们的置信度也会被平滑处理保留更优的那个。引入CenterNet式的关键点检测这是更激进的改进完全抛弃grid转而预测目标中心点的热力图heatmap。但这已经脱离了YOLO的原始范式。4.3 实战中的网格诊断用可视化工具揪出你的训练病灶光看loss曲线是不够的。我习惯在训练中期用以下方法对grid和bbox进行“CT扫描”绘制GT与Pred的网格热力图写一个脚本遍历整个验证集统计每个grid cell被分配到gt框的次数以及该cell预测出高置信度0.7box的次数。用matplotlib.imshow画出20×20的热力图。理想状态是两者分布高度重合且热点集中在图像中央区域因为目标多在此处。如果发现gt热力图很均匀但pred热力图在四角大面积空白说明模型学不会检测边缘目标大概率是数据增强如随机裁剪过于激进把边缘目标切掉了。分析bbox宽高比分布将所有训练轮次中被正确匹配的pred box的宽高比pw/ph收集起来画直方图。YOLOv5的9个anchor宽高比是固定的如果你的数据集目标普遍很瘦如行人而直方图峰值却在1.5~2.0较宽说明anchor匹配失败应该用你的数据集重新聚类anchor。检查tx/ty的分布正常情况下tx和ty应该近似服从[0,1]上的均匀分布。如果直方图在0和1处出现尖峰说明大量box的中心点被预测在cell的边缘这是过拟合或学习率过大的典型信号。实操心得我写了一个grid_debug.py工具集成在训练脚本里每10个epoch自动执行一次上述三项诊断并生成PDF报告。它帮我提前发现了三次严重的anchor失配问题避免了上百小时的无效训练。工具的核心就是把抽象的grid和bbox变成肉眼可察的、有温度的数字。5. 从理论到落地手把手实现一个最小可行网格检测器NumPy版5.1 不依赖PyTorch用纯NumPy构建YOLO的“灵魂内核”为了让你彻底吃透grid与bbox下面是一个仅用NumPy实现的、可运行的最小YOLO检测器。它不训练只做前向推理和后处理但包含了所有核心逻辑。你可以把它当作一个“可执行的教科书”。import numpy as np import cv2 import matplotlib.pyplot as plt def create_grid(S, img_w, img_h): 创建SxS网格返回每个cell的中心点坐标归一化到[0,1] cell_w, cell_h img_w / S, img_h / S # 创建网格索引 grid_x, grid_y np.meshgrid(np.arange(S), np.arange(S), indexingxy) # 计算每个cell中心点在原图的绝对坐标 center_x (grid_x 0.5) * cell_w center_y (grid_y 0.5) * cell_h # 归一化到[0,1] norm_center_x center_x / img_w norm_center_y center_y / img_h return norm_center_x, norm_center_y, cell_w, cell_h def decode_bbox(pred_tx, pred_ty, pred_tw, pred_th, norm_center_x, norm_center_y, cell_w, cell_h, anchor_w, anchor_h, img_w, img_h): 解码单个bounding box # Step 1: sigmoid tx, ty to [0,1] cx 1 / (1 np.exp(-pred_tx)) cy 1 / (1 np.exp(-pred_ty)) # Step 2: 计算中心点绝对坐标 abs_cx (cx norm_center_x) * cell_w abs_cy (cy norm_center_y) * cell_h # Step 3: 解码宽高 pw np.exp(pred_tw) * anchor_w ph np.exp(pred_th) * anchor_h # Step 4: 计算左上右下 x1 abs_cx - pw / 2 y1 abs_cy - ph / 2 x2 abs_cx pw / 2 y2 abs_cy ph / 2 # Clamp to image boundary x1 np.clip(x1, 0, img_w) y1 np.clip(y1, 0, img_h) x2 np.clip(x2, 0, img_w) y2 np.clip(y2, 0, img_h) return np.stack([x1, y1, x2, y2], axis-1) # shape: [S, S, 4] # --- 主程序 --- if __name__ __main__: # 模拟一张640x480的输入图像 IMG_W, IMG_H 640, 480 S 20 # grid size # 1. 创建网格 norm_cx, norm_cy, cell_w, cell_h create_grid(S, IMG_W, IMG_H) print(fGrid created: {S}x{S}, cell size: {cell_w:.1f}x{cell_h:.1f} pixels) # 2. 模拟网络预测这里用随机数代替实际是模型输出 # 假设我们只预测1个anchor所以pred_tx等是[S, S]的矩阵 np.random.seed(42) pred_tx np.random.normal(0, 0.3, (S, S)) # mean0, std0.3 pred_ty np.random.normal(0, 0.3, (S, S)) pred_tw np.random.normal(-1.0, 0.2, (S, S)) # log(anchor_w/pw) ≈ -1.0 pred_th np.random.normal(-1.0, 0.2, (S, S)) # 3. 定义anchor像素尺寸 ANCHOR_W, ANCHOR_H 32.0, 32.0 # 一个正方形anchor # 4. 解码所有bbox bboxes decode_bbox(pred_tx, pred_ty, pred_tw, pred_th, norm_cx, norm_cy, cell_w, cell_h, ANCHOR_W, ANCHOR_H, IMG_W, IMG_H) # 5. 可视化画出前10个预测框 img np.zeros((IMG_H, IMG_W, 3), dtypenp.uint8) for i in range(min(10, S*S)): row, col i // S, i % S x1, y1, x2, y2 bboxes[row, col] cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) # 6. 同时画出grid线 for i in range(S1): # 垂直线 x int(i * cell_w) cv2.line(img, (x, 0), (x, IMG_H), (255, 0, 0), 1) # 水平线 y int(i * cell_h) cv2.line(img, (0, y), (IMG_W, y), (255, 0, 0), 1) plt.figure(figsize(12, 9)) plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) plt.title(fYOLO Grid ({S}x{S}) and Predicted Bounding Boxes) plt.axis(off) plt.show()运行这段代码你会看到一张黑色背景图上面布满了红色的网格线以及10个绿色的矩形框。这些框就是你的“模型”在没有任何训练的情况下基于随机预测参数所生成的bounding box。它们杂乱无章但每一个框的生成逻辑都严格遵循了我们前面讲解的全部数学规则从grid cell索引到归一化中心偏移再到anchor驱动的宽高解码。这个过程就是YOLO的“灵魂内核”。5.2 如何用这个最小系统调试你的真实项目这个NumPy版不是玩具它是你调试真实YOLO项目的瑞士军刀验证数据预处理把你的真实训练数据用同样的create_grid和decode_bbox函数处理看看解码出的bbox是否与标注框吻合。如果不吻合问题一定出在数据增强如Mosaic改变了坐标或是标注格式转换如COCO转YOLO时的宽高颠倒。调试后处理NMS把模型输出的原始pred tensorshape[1,3,S,S,85]用np.squeeze()取出然后用这个脚本的decode_bbox函数批量解码再喂给cv2.dnn.NMSBoxes。你可以清晰地看到哪些框被保留哪些被抑制以及它们的IOU值是多少。这比在PyTorch里debug快十倍。理解anchor作用把ANCHOR_W, ANCHOR_H改成10和50再运行一次。你会发现所有的绿色框都变成了又高又瘦的竖条。这直观地证明了anchor不是可有可无的它是模型预测宽高的“基因”。我在调试一个高空电力巡检项目时就用这个脚本发现了致命bug数据集里绝缘子串的标注框宽高比普遍是1:8但我用的YOLOv5s预训练模型的anchor是基于COCO的最瘦的也只有1:3。结果模型学了半天一直在预测“胖绝缘子”。我把anchor重新聚类后mAP直接提升了12.7%。这个教训只有亲手拧过这个最小系统的螺丝才能刻骨铭心。6. 常见问题与排查技巧实录那些年我们在网格和边界框上踩过的坑6.1 “我的YOLO模型完全不收敛loss在nan和inf之间震荡”——归一化灾难现象训练刚开始L_coord就爆掉loss瞬间变成inftensorboard里全是红色警告。根因tw和th的解码公式是pw exp(tw) * anchor_w。如果网络预测的tw是一个很大的正数比如10exp(10)≈22026再乘以anchor_w32得到的pw高达70万像素远超图像尺寸导致后续的IOU计算出现除零错误梯度爆炸。排查在训练脚本的loss计算前加一行print(max tw:, pred_tw.max().item(), min tw:, pred_tw.min().item())。如果max tw 5或min tw -5基本可以确诊。解决在网络输出层对tw/th加一个torch.clamp(min-5, max5)限制其范围。更优雅的做法是改用softplus激活函数替代exppw softplus(tw) * anchor_wsoftplus(x) log(1exp(x))它天然有下界且在x很大时增长缓慢。YOLOv8就采用了此方案。6.2 “检测框总是偏左上角而且特别小”——tx/ty未做sigmoid的铁证现象所有预测框都挤在图像左上角尺寸只有几个像素。根因tx和ty的物理意义是“中心点在cell内的相对偏移”其理论取值范围是[0,1]。但网络输出的原始值是任意实数。如果你忘了在解码时加sigmoid那么一个tx-10的预测经过cx -10 grid_x后中心点就跑到了cell之外导致x1和y1为负被clip后全变成0。排查在解码后的abs_cx和abs_cy上打印min()和max()。如果abs_cx.min() 0或abs_cy.min() 0就是它了。解决在解码函数里cx torch.sigmoid(pred_tx)是雷打不动的第一行。记住sigmoid不是可选项是YOLO协议的一部分。6.3 “为什么我的模型在验证集上mAP很高但实际部署时漏检严重”——网格分辨率与部署图像尺寸的错配现象你在640×640的图像上训练mAP0.75但把模型部署到手机APP用户上传的图片是1280×720检测效果一塌糊涂。根因YOLO的grid sizeS是由输入尺寸和下采样倍数决定的。你在640×640上训练得到的是20×20 grid但如果你在推理时把1280×720的图直接resize到640×640再送入模型相当于把原图压缩了2倍所有目标都变小了其中心点可能就从一个cell跳到了另一个cell导致匹配错乱。排查检查你的部署代码。确认cv2.resize(img, (640, 640))这行是否在resize后还做了img img.transpose(2,0,1)HWC→CHW和img img.astype(np.float32) / 255.0。漏掉任何一步都会导致输入失真。解决永远保持训练与推理的预处理流程100%一致。最好的实践是写一个preprocess_image(img, target_size640)函数里面封装了resize、pad用cv2.copyMakeBorder补齐到正方形避免拉伸变形、归一化、transpose的全部步骤并在训练和推理时共用这一个函数。我在一个工业质检项目里就因为训练时用了pad推理时用了resize导致产线漏检率飙升到15%花了三天才定位到这个“一眼就能看出”的bug。6.4 “NMS之后同一个目标出现了两个框一粗一细”——多尺度特征图的幽灵重影现象一个目标被两个不同尺度的特征图如20×20和10×10同时检测到生成了两个IOU很高的框NMS没能完全抑制掉。根因YOLOv5的PANet结构让不同尺度的特征图信息充分融合有时会导致同一个目标在多个尺度上都产生了高置信度响应。排查用torchvision.utils.draw_bounding_boxes把NMS前的所有预测框按置信度排序取top-50画在同一张图上。如果看到两个框几乎重叠但一个明显更大来自10×10图一个更小来自20×20图就是此问题。解决调高NMS的IOU阈值从0.45提到0.6。更推荐的方法是使用aggregated NMS先对每个尺度的框单独做NMS再把所有尺度的幸存框合并