ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

YOLOv5动态锚点与多尺度预测原理解析

YOLOv5动态锚点与多尺度预测原理解析

简介:本资源是一份面向Python与计算机视觉初学者的YOLOv5实战入门指南,聚焦目标检测框架的本地环境搭建与端到端应用,解决新手在CUDA/cuDNN适配、PyTorch依赖冲突、预训练模型调用及基础检测流程等常见卡点问题。文档以清晰步骤覆盖从系统要求(Windows/macOS/Linux + Python 3.6+ + CUDA 10.2+/cuDNN 7.6+)到虚拟环境创建、YOLOv5源码克隆、requirements安装、yolov5s.pt模型下载、detect.py命令行参数详解及结果输出路径说明,并延伸至自定义数据集训练的关键路径指引。资源为单文件Word文档(.docx),结构完整、图文结合、命令可直接复现,总大小仅19KB,轻量易读。目前已有238人学习下载,适合高校学生、初级算法工程师及CV方向转岗者用于理论教学辅助或工程项目快速启动。

1. YOLOv5 不是“一跑就准”的黑匣子:它用动态锚点+多尺度预测把检测精度锚定在 GPU 显存带宽的临界点上

你刚 clone 下来yolov5s.pt,python detect.py --source test.jpg一执行,框就出来了——快得像魔法。但真正让工业级部署敢用它的,不是这秒级响应,而是它在416×416 输入下,仅用 2.3GB 显存就完成 3 层特征图联合预测的硬核设计。这不是靠堆参数换来的精度,而是把目标检测从“暴力回归”拉回“结构化建模”的关键转折:YOLOv5 放弃了 YOLOv3/v4 中手工设计的固定 anchor 尺寸,转而用自适应 anchor 聚类 + 网络前向传播中实时计算 anchor 偏移量的双阶段机制,在训练初期就让模型自己“学会”该在哪种尺度上检测什么尺寸的物体。更反直觉的是,它的detect.py脚本里根本没写 NMS(非极大值抑制)逻辑——那套 IOU 阈值、置信度筛选、坐标合并的完整流程,全被封装进torchvision.ops.nms的 C++ 后端里,连 Python 层都看不到循环。这意味着:你调参时改的--conf 0.4,实际触发的是 PyTorch 底层对scores张量的一次 CUDA kernel 调度;你看到的每个检测框,背后是grid坐标系到image坐标系的三次张量广播运算。搞懂这个,你才不会在--img 640时发现小目标漏检率飙升——那不是模型不行,是stride=8/16/32的三层特征图分辨率与 anchor 尺寸匹配关系被你手动打破了。这份资源的价值,正在于它把这套“看不见的调度链”拆成可验证、可打断、可重写的代码模块,让你从调参工程师,变成能看懂models/common.py里Conv类如何决定梯度流走向的底层玩家。

2. 动态 anchor 机制:为什么 YOLOv5 不再需要手调anchors:字段?

YOLOv5 的models/yolov5s.yaml文件里,anchors:字段下面写着三组数字,比如[10,13, 16,30, 33,23]这样的序列。新手常误以为这是“预设锚点”,实则这是anchor 聚类的初始种子值(initial seeds),而非最终生效的 anchor 尺寸。真正的 anchor 尺寸是在train.py启动时,由autoanchor.py模块根据你的数据集标注框长宽比,自动聚类生成的。这个过程决定了模型能否在训练早期就建立对目标尺度分布的先验认知。

2.1 anchor 聚类:从标注文件中“偷学”目标真实尺寸分布

YOLOv5 的autoanchor.py不依赖人工经验,而是直接扫描所有.txt标注文件(每行格式为class_id center_x center_y width height,归一化到[0,1]),提取所有width和height值,构建成(N, 2)的 numpy 数组,再用 k-means(k=9,对应 3 层 × 每层 3 个 anchor)进行聚类:

# utils/autoanchor.py: kmean_anchors() def kmean_anchors(dataset='./data/coco128.yaml', n=9, img_size=640, thr=4.0, gen=1000, verbose=True): from utils.general import check_dataset from utils.datasets import LoadImagesAndLabels # 加载数据集标注(不加载图像,只读 .txt) data = check_dataset(dataset) # 解析 yaml 获取 train: 路径 dataset = LoadImagesAndLabels(data['train'], img_size, batch_size=1, rect=True, cache=False) # 提取所有标注框的宽高(归一化值 × 图像尺寸 → 实际像素尺寸) shapes = [dataset.shapes[i] for i in range(len(dataset))] wh0 = np.concatenate([l[:, 3:5] * s for s, l in zip(shapes, dataset.labels)]) # (N, 2) # k-means 聚类(使用 CIoU 距离替代欧氏距离) i = 0 while i < gen: i += 1 # 初始化 k 个中心点(随机选样本或用 k-means++) anchors_old = anchors.copy() # 计算每个框到各中心点的 CIoU 距离 dists = 1 - wh_iou(wh0, anchors) # wh_iou 返回 (N, k) 的 IoU 矩阵 # 分配每个框到最近中心点 assign = dists.argmin(1) # 更新中心点为所属框的均值 for k in range(n): if len(wh0[assign == k]) > 0: anchors[k] = wh0[assign == k].mean(0) # 收敛判断:中心点移动距离小于阈值 if (anchors_old - anchors).abs().max() < 1e-3: break

提示:wh_iou()函数计算的是宽高比相似度(min(w1/w2, w2/w1) * min(h1/h2, h2/h1)),而非传统 IoU。这使得聚类更关注“形状匹配”,而非绝对尺寸——正是这一设计,让 YOLOv5 在跨数据集迁移时,anchor 泛化能力远超 YOLOv3。

2.2 前向传播中的 anchor 偏移:网络自己决定“该往哪偏”

YOLOv5 的检测头(Detect layer)输出并非直接的(x,y,w,h),而是(tx,ty,tw,th)—— 即相对于 anchor 的偏移量(offset)。其解码公式为:

bx = sigmoid(tx) + cx # cx 是 grid cell 左上角 x 坐标(整数索引) by = sigmoid(ty) + cy # cy 是 grid cell 左上角 y 坐标 bw = pw * exp(tw) # pw 是该 anchor 的预设宽度(来自 autoanchor 结果) bh = ph * exp(th) # ph 是该 anchor 的预设高度

关键在于:pw,ph并非写死在模型权重里,而是作为Detect类的self.anchors属性,在__init__()时从yaml文件加载,并在forward()中通过self.anchors.to(x.device)动态搬运到当前设备。这意味着:同一份yolov5s.pt权重文件,在不同数据集上 fine-tune 后,其self.anchors属性会被autoanchor覆盖为新值,而主干网络权重保持不变。这种“权重与先验解耦”的设计,是 YOLOv5 支持快速适配小样本场景的核心。

2.3 多尺度 anchor 分配:为什么小目标必须落在 stride=8 的特征图上?

YOLOv5 的 3 层检测头分别对应stride=8,16,32(即特征图分辨率是输入图的 1/8, 1/16, 1/32)。autoanchor聚类出的 9 个 anchor,会按宽高比自动分配到这三层:

  • stride=8层:分配最“瘦高”和最“扁宽”的 3 个 anchor(适合小目标,如行人头部、车牌字符)
  • stride=16层:分配中等比例的 3 个 anchor(通用目标,如汽车、自行车)
  • stride=32层:分配最大尺寸的 3 个 anchor(适合大目标,如整辆卡车、建筑立面)

这个分配逻辑藏在models/yolo.py的Detect.forward()中:

# models/yolo.py: Detect.forward() def forward(self, x): z = [] # 输出检测结果列表 self.training |= self.export for i in range(self.nl): # nl = number of detection layers = 3 x[i] = self.m[i](x[i]) # conv -> (bs, 3*(5+nc), h, w) bs, _, ny, nx = x[i].shape # (bs, 255, h, w) for yolov5s, nc=80 x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2) # (bs, na, ny, nx, no) # 获取该层对应的 anchor(na=3) anchor_i = self.anchors[i] # shape: (3, 2) # 生成 grid 坐标(cx, cy) grid_i = self.grid[i].to(x[i].device) # 解码:tx,ty → bx,by;tw,th → bw,bh y = x[i].sigmoid() y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + grid_i) * self.stride[i] # bx, by y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * anchor_i # bw, bh z.append(y.view(bs, -1, self.no)) # (bs, h*w*3, no) return x if self.training else (torch.cat(z, 1), x)

注意self.stride[i]的作用:它把grid_i(整数索引)映射回原图像素坐标。stride=8层的grid_i步长是 8,意味着每个 grid cell 覆盖原图 8×8 区域,因此它天然适合定位尺寸在8×8到32×32的小目标。如果你强行把--img 416改成--img 128,stride=8层的特征图就只剩16×16,grid cell 数量锐减,小目标 anchor 无处安放——这就是--img参数不能乱调的根本原因。

3. 多尺度预测与特征融合:PANet 结构如何让浅层语义和深层细节“握手言和”?

YOLOv5 的 backbone(CSPDarknet53)负责提取强语义特征,但浅层特征图(如stage2输出)包含丰富纹理和边缘信息,对小目标定位至关重要。YOLOv5 采用 PANet(Path Aggregation Network)结构,在 backbone 之上构建双向特征金字塔,让深层语义“下渗”到浅层,同时让浅层细节“上送”到深层,形成真正的多尺度协同。

3.1 自顶向下路径(Top-down Path):语义信息的“降维渗透”

从 backbone 最深层输出(C5, stride=32)开始,YOLOv5 用nn.Upsample(scale_factor=2)上采样,再与C4(stride=16)做Concat操作,然后经过Conv层融合:

# models/common.py: C3 module (used in PANet) class C3(nn.Module): # CSP Bottleneck with 3 convolutions def __init__(self, c1, c2, n=1, shortcut=True, g=1, e=0.5): # ch_in, ch_out, number, shortcut, groups, expansion super().__init__() c_ = int(c2 * e) # hidden channels self.cv1 = Conv(c1, c_, 1, 1) self.cv2 = nn.Sequential(Conv(c1, c_, 1, 1), Conv(c_, c_, 3, 1)) self.cv3 = Conv(2 * c_, c2, 1) # act=FReLU(c2) if act is True else nn.Identity() def forward(self, x): return self.cv3(torch.cat((self.cv1(x), self.cv2(x)), 1))

这个C3模块就是 PANet 中的融合单元。在models/yolov5s.yaml中,neck部分明确写出:

# YOLOv5 v5.0 backbone backbone: # [from, number, module, args] [[-1, 1, Conv, [64, 6, 2, 2]], # 0-P1/2 [-1, 1, Conv, [128, 3, 2]], # 1-P2/4 [-1, 3, C3, [128]], # 2 [-1, 1, Conv, [256, 3, 2]], # 3-P3/8 [-1, 6, C3, [256]], # 4 [-1, 1, Conv, [512, 3, 2]], # 5-P4/16 [-1, 9, C3, [512]], # 6 [-1, 1, Conv, [1024, 3, 2]], # 7-P5/32 [-1, 3, C3, [1024]], # 8 [-1, 1, SPPF, [1024, 5]], # 9 ] neck: [[-1, 1, Conv, [512, 1, 1]], # 10 [-1, 1, nn.Upsample, [None, 2, 'nearest']], # 11 [[-1, 6], 1, Concat, [1]], # 12 # cat backbone P4 [-1, 3, C3, [512, False]], # 13 # high-res head [-1, 1, Conv, [256, 1, 1]], # 14 [-1, 1, nn.Upsample, [None, 2, 'nearest']], # 15 [[-1, 4], 1, Concat, [1]], # 16 # cat backbone P3 [-1, 3, C3, [256, False]], # 17 # highest-res head [-1, 1, Conv, [256, 3, 2]], # 18 [[-1, 14], 1, Concat, [1]], # 19 # cat down-sampled high-res [-1, 3, C3, [512, False]], # 20 # mid-res head [-1, 1, Conv, [512, 3, 2]], # 21 [[-1, 10], 1, Concat, [1]], # 22 # cat down-sampled mid-res [-1, 3, C3, [1024, False]], # 23 # low-res head ]

看neck部分第 10–13 行:[-1, 1, Conv, [512, 1, 1]]将P5(1024→512 通道压缩),nn.Upsample上采样 2 倍,再与P4(第 6 行输出,512 通道)Concat,最后用C3融合。这个过程让P5的强语义(如“这是辆车”)精准地指导P4的定位(“车轮在哪”)。

3.2 自底向上路径(Bottom-up Path):细节信息的“逆流而上”

PANet 的另一半是自底向上路径。neck第 18–20 行:[-1, 1, Conv, [256, 3, 2]]对P3(最高分辨率)做下采样(stride=2),再与P4的压缩版(第 14 行)Concat,用C3融合。这步让P3的精细边缘(如车牌边框)反向增强P4的特征表达力,避免上采样带来的模糊。

3.3 三层检测头的物理意义:不是“越多越好”,而是“各司其职”

YOLOv5 的 3 个Detect层,分别接在neck的P3,P4,P5输出上(对应stride=8,16,32):

层级特征图尺寸(输入 640×640)感受野近似适配目标尺寸典型 anchor 宽高(像素)
P380×80~32×3216×16 ~ 64×6410×13, 16×30, 33×23
P440×40~64×6432×32 ~ 128×12830×61, 62×45, 59×119
P520×20~128×12864×64 ~ 256×256116×90, 156×198, 373×326

注意:表中 anchor 尺寸是autoanchor在 COCO 数据集上的聚类结果,单位为像素。当你用--img 416时,这些值会按比例缩放到416/640≈0.65,即10×13 → 6.5×8.45,所以P3层仍能覆盖小目标。但若你用--img 128,P3的 anchor 就缩到2×2.6,远小于常见小目标,漏检必然发生。

4. detect.py 的隐藏调度链:从 Python 脚本到 CUDA kernel 的全链路解析

python detect.py --weights yolov5s.pt --source test.jpg看似简单,实则触发了跨越 Python、C++、CUDA 的三级调度。理解这条链,是排查“为什么我的视频检测卡顿”“为什么 CPU 版本比 GPU 版本还快”的钥匙。

4.1 Python 层:detect.py只是“发令枪”,不参与计算

detect.py的核心逻辑是:

# detect.py if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--weights', nargs='+', type=str, default='yolov5s.pt', help='model.pt path(s)') parser.add_argument('--source', type=str, default='data/images', help='source') # file/folder, 0 for webcam parser.add_argument('--img-size', type=int, default=640, help='inference size (pixels)') parser.add_argument('--conf-thres', type=float, default=0.25, help='object confidence threshold') parser.add_argument('--iou-thres', type=float, default=0.45, help='IOU threshold for NMS') opt = parser.parse_args() with torch.no_grad(): # 关闭梯度,节省显存 detect(opt) # 主函数

detect()函数只做三件事:1)加载模型model = attempt_load(weights, map_location=device);2)预处理图像img = letterbox(img0, new_shape=imgsz)[0](等比缩放+pad);3)调用pred = model(img)。所有计算都在model(img)内部完成,detect.py本身不包含任何模型推理代码。

4.2 PyTorch 层:model(img)触发的张量运算图

model是models/yolo.py中的Model类实例。model(img)执行时,会依次调用:

  1. self.backbone(img)→ 输出C3,C4,C5特征图
  2. self.neck([C3,C4,C5])→ 输出P3,P4,P5融合特征
  3. self.head([P3,P4,P5])→ 输出 3 个(bs, na, h, w, no)张量

其中no = 5 + nc(5=xywh+conf,nc=类别数)。此时pred是一个 list,含 3 个张量,每个张量需解码为(x1,y1,x2,y2,conf,cls)格式。

4.3 torchvision.ops.nms:真正的 NMS 在 CUDA 里完成

解码后的pred是未过滤的原始预测框。detect.py中调用:

# utils/general.py: non_max_suppression() def non_max_suppression(prediction, conf_thres=0.25, iou_thres=0.45, classes=None, agnostic=False, multi_label=False, labels=()): # ... 预处理:筛选 conf > conf_thres 的框,转换为 [x1,y1,x2,y2,conf,cls] ... boxes = prediction[:, :4] # x1y1x2y2 scores = prediction[:, 4] # conf # 关键调用: i = torchvision.ops.nms(boxes, scores, iou_thres) # ← 这里进入 C++/CUDA return prediction[i]

torchvision.ops.nms是 PyTorch 官方实现,其 CUDA kernel 位于torchvision/csrc/cuda/nms_cuda.cu。它不使用 Python 循环,而是将boxes和scores传入 GPU,由 CUDA kernel 并行计算所有框对的 IoU,再用并行排序算法选出最优框。--iou 0.5的效果,取决于这个 kernel 的实现效率,而非 Python 代码。这也是为什么在 RTX 3090 上nms耗时 <0.5ms,而在 CPU 上可能 >10ms。

4.4 避坑:常见问题与底层原因排查

现象 1:GPU 显存占用远超模型大小(如yolov5s.pt仅 14MB,却占 3GB 显存)

原因:PyTorch 默认启用cudnn.benchmark=True,会在首次运行时缓存多个卷积算法的性能 profile,导致显存峰值飙升。此外,torchvision.ops.nms的 CUDA kernel 需要临时显存存放 IoU 矩阵(尺寸为N×N,N 为候选框数)。
解决:在detect.py开头添加torch.backends.cudnn.benchmark = False;或限制--conf阈值,减少候选框数量。

现象 2:CPU 版本比 GPU 版本更快

原因:--source指向单张小图(如 320×240)时,GPU 启动开销(kernel launch latency)超过计算收益。且torchvision.ops.nms的 CPU 实现(基于scipy.spatial.distance.pdist)在小 N(<100)时比 CUDA 更快。
解决:批量处理图像(--source指向文件夹),或改用--device cpu并关闭nms(--agnostic-nms)做纯推理对比。

现象 3:--img 640时小目标漏检严重,但--img 1280反而提升

原因:--img不仅控制输入尺寸,还决定stride的实际感受野。yolov5s的stride=[8,16,32]是针对 640 设计的。当--img=1280,P3层stride=8的感受野变为1280/8=160,能覆盖更大范围的小目标;而--img=128时P3感受野仅16,小于多数小目标。
解决:不要盲目调大--img,应先用utils/autoanchor.py为你的数据集重新聚类 anchor,并修改models/yolov5s.yaml中的anchors:字段。

现象 4:detect.py报错RuntimeError: Input type (torch.cuda.FloatTensor) and weight type (torch.FloatTensor) should be the same

原因:--weights模型是 CPU 训练保存的(map_location=torch.device('cpu')),但--device指定了cuda,导致模型权重未加载到 GPU。
解决:加载模型时强制指定设备:model = attempt_load(weights, map_location=device),确保device是torch.device('cuda')。

现象 5:视频检测时帧率不稳定,偶发卡顿

原因:cv2.VideoCapture的read()方法在某些摄像头驱动下存在缓冲区阻塞,且torchvision.ops.nms的 CUDA kernel 启动有 jitter。
解决:在detect.py中添加cv2.CAP_PROP_BUFFERSIZE, 1设置缓冲区为 1 帧;或改用--source 0(摄像头)时加--half参数启用半精度,降低 kernel 启动延迟。

5. 训练自定义数据集:从train.py到models/yolov5s.yaml的参数传导链

训练不是“改个路径就开跑”,而是一条从数据配置、模型结构、损失函数到优化器的完整传导链。train.py是总控,但真正决定模型行为的,是data/mydata.yaml和models/yolov5s.yaml两个文件的联动。

5.1data/mydata.yaml:定义数据“世界观”

该文件必须包含:

train: ../mydata/images/train # 训练图像路径(相对路径) val: ../mydata/images/val # 验证图像路径 nc: 3 # 类别数(必须与 labels 中 class_id 一致) names: ['cat', 'dog', 'bird'] # 类别名列表(索引即 class_id)

关键点:nc值会传导给models/yolov5s.yaml中的nc字段,并最终决定Detect层输出的no = 5 + nc。如果nc=3但你的labels/下有class_id=5的标注,训练会报错IndexError: index 5 is out of bounds for dimension 1 with size 3。

5.2models/yolov5s.yaml:定义模型“生理结构”

该文件中nc必须与data/mydata.yaml一致。更重要的是depth_multiple和width_multiple:

# Parameters nc: 3 # number of classes depth_multiple: 0.33 # model depth multiple width_multiple: 0.50 # layer channel multiple
  • depth_multiple控制C3模块中n(重复次数)的缩放,如n=3→int(3*0.33)=1,减少参数量
  • width_multiple控制所有Conv层的out_channels缩放,如64→int(64*0.5)=32,降低显存

修改这两个值,相当于给模型“瘦身”或“增肌”,但必须与nc配合。例如,nc=1(二分类)时,width_multiple=0.5可能导致Detect层通道不足,需同步调高。

5.3train.py的损失函数传导:ComputeLoss如何绑定 anchor

train.py中,model加载后立即创建compute_loss = ComputeLoss(model)。ComputeLoss类的__init__()会遍历model.model的所有Detect层,提取其self.anchors和self.stride,并预计算self.balance(各层损失权重平衡系数):

# models/yolo.py: ComputeLoss.__init__() def __init__(self, model, autobalance=False): super(ComputeLoss, self).__init__() self.sort_obj_iou = False device = next(model.parameters()).device # get model device h = model.hyp # hyperparameters # Define criteria BCEcls = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h['cls_pw']], device=device)) BCEobj = nn.BCEWithLogitsLoss(pos_weight=torch.tensor([h['obj_pw']], device=device)) # Class label smoothing https://arxiv.org/pdf/1902.04103.pdf self.cp, self.cn = smooth_BCE(eps=0.0) # positive, negative BCE targets # Focal loss g = h['fl_gamma'] # focal loss gamma if g > 0: BCEcls, BCEobj = FocalLoss(BCEcls, g), FocalLoss(BCEobj, g) det = model.module.model[-1] if is_parallel(model) else model.model[-1] # Detect() layer self.balance = {3: [4.0, 1.0, 0.4]}.get(det.nl, [4.0, 1.0, 0.4, 0.1]) # P3-P7 self.ssi = list(det.stride).index(16) if autobalance else 0 # stride 16 index self.BCEcls, self.BCEobj, self.gr, self.hyp, self.autobalance = BCEcls, BCEobj, model.gr, h, autobalance for k in 'na', 'nc', 'nl', 'anchors': setattr(self, k, getattr(det, k))

注意self.anchors和self.nl(number of layers)直接来自Detect层。这意味着:你在autoanchor.py中生成的新 anchor,会自动注入损失计算,无需修改train.py。ComputeLoss.forward()中,会用这些 anchor 与 GT 框计算CIoU,并分配正样本(positive sample)到最匹配的 anchor 层。

5.4hyp.scratch.yaml:超参的“神经突触”

data/hyp.scratch.yaml定义了学习率、数据增强、损失权重等超参:

lr0: 0.01 # initial learning rate (SGD=1E-2, Adam=1E-3) lrf: 0.1 # final OneCycleLR learning rate (lr0 * lrf) momentum: 0.937 # SGD momentum/Adam beta1 weight_decay: 0.0005 # optimizer weight decay 5e-4 warmup_epochs: 3.0 # warmup epochs (fractions ok) warmup_momentum: 0.8 # warmup initial momentum warmup_bias_lr: 0.1 # warmup initial bias lr box: 0.05 # box loss gain cls: 0.5 # cls loss gain cls_pw: 1.0 # cls BCELoss positive_weight obj: 1.0 # obj loss gain (scale with pixels) obj_pw: 1.0 # obj BCELoss positive_weight iou_t: 0.20 # IoU training threshold anchor_t: 4.0 # anchor-multiple threshold # P3/8-small, P4/16-medium, P5/32-large fl_gamma: 0.0 # focal loss gamma (efficientDet default gamma=1.5) hsv_h: 0.015 # image HSV-Hue augmentation (fraction) hsv_s: 0.7 # image HSV-Saturation augmentation (fraction) hsv_v: 0.4 # image HSV-Value augmentation (fraction) degrees: 0.0 # image rotation (+/- deg) translate: 0.1 # image translation (+/- fraction) scale: 0.5 # image scale (+/- gain) shear: 0.0 # image shear (+/- deg) perspective: 0.0 # image perspective (+/- fraction), range 0-0.001 flipud: 0.0 # image flip up-down (probability) fliplr: 0.5 # image flip left-right (probability) mosaic: 1.0 # image mosaic (probability) mixup: 0.0 # image mixup (probability) copy_paste: 0.0 # segment copy-paste (probability)

其中anchor_t: 4.0是关键:它定义了 GT 框与 anchor 的匹配阈值。计算wh_iou(gt_wh, anchor_wh),若> 1/anchor_t = 0.25,则该 anchor 被视为正样本。anchor_t越小,匹配越严格,正样本越少,训练越困难但精度可能更高。

6. 从“能跑通”到“懂调度”:我的三个血泪习惯与一次翻车复盘

我第一次用 YOLOv5 训练自己的 PCB 缺陷数据集

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进