ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于YOLOv7的电池检测实战:2030张数据集训练与97.7%精度复现

基于YOLOv7的电池检测实战:2030张数据集训练与97.7%精度复现 简介这份电池数据集面向计算机视觉学习者与目标检测开发者聚焦于9伏电池、纽扣电池与干电池三类常见电池的识别任务可用于智能分拣、电池回收分类、工业质检等场景的模型训练与验证。资源包共收录2000个文件以1999个txt标注文件与1个yaml配置文件为主txt文件对应每张图像的YOLO格式标注框yaml则用于定义数据集路径与类别信息压缩包整体约65.72MB便于快速下载与本地部署。数据集包含2030张640×640分辨率的原始图像采用yolov7标注规范官方给出的正确识别率可达97.7%标注质量与类别区分度较高适合直接用于训练、微调或作为基线对比。目前已有48人学习下载读者可借此搭建完整的电池检测流程验证模型在真实图像上的表现并在此基础上扩展更多电池类别或迁移到相似的小目标检测任务中。1. 电池数据集与 yolov7 标注从 2030 张原图到 97.7% 识别率的落地路径手里有一批 2030 张、640×640 分辨率的电池原始图标注格式是 yolov7类别覆盖 9 伏、纽扣电池、干电池官方口径正确识别率 97.7%。这个标题真正要回答的不是「数据集好不好」而是「我拿到它之后怎么在自己的产线、分拣设备或质检工位上把它跑起来并且复现出接近 97.7% 的精度」。适合三类人做电池回收分拣的自动化工程师、做工业质检视觉方案的算法同学、以及想找一个真实小目标检测场景练手 yolov7 部署的开发者。电池检测的难点不在类别多而在纽扣电池这种小目标、金属反光、正负极朝向不一致以及干电池和 9 伏在俯拍视角下的类间相似。97.7% 这个数字背后是数据标注一致性、640 分辨率下的 anchor 匹配、以及推理端预处理三件事同时做对的结果缺一个都会掉点。下面按「数据集怎么读 → 训练怎么配 → 坑在哪 → 怎么验证」的顺序拆开讲。2. 电池数据集的结构拆解与 yolov7 标注格式核对2.1 2030 张 640×640 原图意味着什么先别急着训练先把数据集本身看清楚。2030 张 640×640 的图按常见做法会切成训练/验证/测试三份比例 8:1:1 的话就是 1624 / 203 / 203。这个量级对于三个类别、且类别形态差异明显的检测任务是够用的但前提是每类样本分布不能太偏。电池场景里最常见的偏斜是干电池图最多9 伏次之纽扣电池最少。如果纽扣电池只有两三百张那 97.7% 的总体准确率很可能是被干电池拉高的纽扣电池的单类召回可能只有 80% 出头。所以第一步不是跑训练是统计每类框的数量。640×640 这个分辨率要特别注意。yolov7 默认输入是 640和原图一致理论上不需要缩放但实际数据里很多图是「640 画布 物体只占中间一小块」纽扣电池的框可能只有 30×30 像素。yolov7 的 P3 特征图 stride 是 830 像素的物体在 P3 上只剩不到 4 个格子小目标召回天然吃亏。这就是为什么同样一份数据有人跑出 97.7%有人只有 90% 出头——差别往往在输入尺寸和 anchor 上。2.2 yolov7 标注格式的字段与常见错位yolov7 沿用 YOLO 系列的 txt 标注每张图对应一个同名 txt每行class x_center y_center width height后四个都是归一化到 0~1 的相对值。目录结构常见两种一种是 images/ 和 labels/ 平行一种是同目录混放。先写个脚本核对别信「标注已完成」这句话。import os from pathlib import Path from collections import Counter IMG_DIR Path(dataset/images) LBL_DIR Path(dataset/labels) CLASSES [9v, button, dry] # 与 data.yaml 的 names 顺序必须一致 counter Counter() bad [] for img in IMG_DIR.glob(*.jpg): lbl LBL_DIR / (img.stem .txt) if not lbl.exists(): bad.append((missing_label, img.name)); continue for line in lbl.read_text().strip().splitlines(): p line.split() if len(p) ! 5: bad.append((bad_cols, img.name, line)); continue c, x, y, w, h int(p[0]), *map(float, p[1:]) # 归一化坐标必须在 0~1越界说明标注工具导出时没除宽高 if not (0 x 1 and 0 y 1 and 0 w 1 and 0 h 1): bad.append((out_of_range, img.name, line)); continue counter[CLASSES[c]] 1 print(每类框数:, dict(counter)) print(问题条目数:, len(bad)) for b in bad[:10]: print(b)这段脚本做三件事检查每张图有没有对应 label、每行是不是 5 列、归一化坐标有没有越界。参数上CLASSES的顺序必须和训练时 data.yaml 里的names完全一致顺序错了模型会把 9 伏学成纽扣电池而且 loss 还能降属于最隐蔽的翻车。out_of_range是最常见的标注错误多出现在用 labelImg 时切换了「绝对坐标」模式导出。跑完如果bad超过总数的 2%先回去修标注别往下走。2.3 类别分布与框尺寸分布怎么读统计完框数还不够要看框的宽高分布。纽扣电池的框如果普遍小于 32 像素就要考虑把训练输入提到 768 或 896或者单独为小目标加一个 P2 检测头。下面这段统计框的像素尺寸帮你判断要不要动输入分辨率。import numpy as np from pathlib import Path sizes {9v: [], button: [], dry: []} for lbl in Path(dataset/labels).glob(*.txt): for line in lbl.read_text().strip().splitlines(): c, x, y, w, h int(line.split()[0]), *map(float, line.split()[1:]) # 640 是原图边长换算成像素宽高 sizes[list(sizes)[c]].append((w * 640, h * 640)) for k, v in sizes.items(): arr np.array(v) print(f{k}: 框数{len(v)}, 宽中位数{np.median(arr[:,0]):.1f}, f高中位数{np.median(arr[:,1]):.1f}, 最小宽{arr[:,0].min():.1f})如果button的宽中位数低于 40 像素基本可以确定小目标是主要瓶颈。这时候有两个选择一是把--img-size提到 768代价是显存和推理耗时上升约 40%二是保持 640 但在模型里加 P2 层。我一般先试提分辨率因为改动最小、可复现性最好。3. 用 yolov7 在电池数据集上跑通训练的最小配置3.1 环境与依赖的版本对齐yolov7 对 PyTorch 和 CUDA 版本比较敏感版本错配最典型的症状是训练能启动但 loss 变 NaN或者 DDP 多卡直接卡死。常见做法是锁一套经过验证的组合别追最新。下面这套是我在 30 系和 40 系卡上都跑通过的。conda create -n yolo7 python3.9 -y conda activate yolo7 # 按你的 CUDA 版本选对应 wheel这里以 cu118 为例 pip install torch2.0.1 torchvision0.15.2 --index-url https://download.pytorch.org/whl/cu118 pip install -r requirements.txt # 验证 python -c import torch; print(torch.__version__, torch.cuda.is_available())torch.cuda.is_available()必须返回 True否则后面训练会静默落到 CPU速度慢到你以为卡死了。requirements 里 numpy 建议锁在 1.23 附近numpy 2.x 和部分旧版 opencv 会冲突报错信息还很难懂属于典型的玄学问题。3.2 data.yaml 与 anchor 的对应关系yolov7 训练前要准备 data.yaml字段不多但每个都关键。train: /data/battery/images/train val: /data/battery/images/val nc: 3 names: [9v, button, dry]nc必须等于 names 长度names顺序必须和标注里的 class id 一致。路径建议写绝对路径相对路径在不同工作目录下启动训练时经常找不到文件报的还是「No labels found」这种误导性错误。anchor 方面yolov7 自带的是 COCO 的 9 组 anchor对电池这种尺寸分布和 COCO 差异较大的数据建议用 k-means 重新聚类。常见做法是拿训练集的框跑一遍聚类脚本得到 9 组宽高再替换 cfg 里的 anchors。如果懒得聚类至少确认最小那组 anchor 的宽高和纽扣电池的框尺寸在同一量级否则小目标正样本匹配不上召回直接塌。3.3 启动训练的命令与关键参数单卡训练的最小命令如下参数逐个说明。python train.py \ --workers 8 \ --device 0 \ --batch-size 16 \ --data data/battery.yaml \ --img-size 640 640 \ --cfg cfg/training/yolov7.yaml \ --weights yolov7.pt \ --name battery_run \ --hyp data/hyp.scratch.p5.yaml \ --epochs 150 \ --nosave --cache--batch-size 16是 640 分辨率下 16G 显存的稳妥值显存够可以上 32但 batch 太大对小目标反而不利因为每张图贡献的梯度被平均掉了。--img-size 640 640和原图一致先跑基线。--weights yolov7.pt用官方预训练权重做迁移电池数据集只有 2000 张从头训几乎不可能收敛到 97%。--hyp用 scratch 的 p5 配置如果显存吃紧或过拟合明显可以换低学习率的配置。--cache把图缓存到内存2030 张 640 图大概占几个 G能明显加快每个 epoch。--nosave只存最后和最好的权重省磁盘。训练过程中重点盯三个指标box_loss是否稳定下降、mAP0.5的爬升曲线、以及每类的precision/recall。如果button类的 recall 长期低于 0.85而dry已经 0.98说明小目标没学好回到 2.3 节调分辨率或 anchor。4. 电池检测落地避坑五条血泪排查记录4.1 现象训练 mAP 很高部署后纽扣电池全漏原因训练时的 letterbox 预处理和部署端不一致。yolov7 训练默认把图缩放到 640 并补灰边推理端如果直接 resize 拉伸纽扣电池的宽高比被改变小目标形变后匹配不上。解决部署端严格复刻 letterbox保持长边缩放、短边补 114 灰边坐标还原时按同样的缩放比例反算。这个坑最坑的地方在于验证集指标是好的因为验证走的是训练同款预处理只有真正上线才暴露。4.2 现象9 伏和干电池互相误检置信度都在 0.5 附近原因俯拍视角下两者都是长条矩形颜色也接近模型靠纹理区分但 640 分辨率下纹理信息被压缩。解决一是提高输入到 768让纹理细节保留更多二是在数据里补充侧拍、斜拍样本增加视角多样性三是把置信度阈值从 0.25 提到 0.4宁可漏检也不误检再靠后处理的多帧投票补召回。单纯调阈值治标不治本视角多样性才是根。4.3 现象训练 loss 正常但验证 mAP 一直是 0原因data.yaml 里 val 路径写成了相对路径而训练是从另一个目录启动的实际加载的是空验证集。yolov7 在验证集为空时不会报错只会 mAP 为 0非常隐蔽。解决把 train/val 都改成绝对路径启动前用ls确认路径下确实有图。另一个可能是 labels 目录名拼错比如写成 label 而不是 labels。4.4 现象多卡训练比单卡还慢GPU 利用率忽高忽低原因--workers设得太大数据加载进程和 DDP 通信抢 CPU或者 batch-size 没按卡数放大。解决workers 设成 CPU 核数的 1/4 到 1/2多卡时 batch-size 要乘卡数否则每张卡分到的 batch 太小通信开销占比过高。另外确认用的是torch.distributed.launch而不是手动 spawn后者容易出同步问题。4.5 现象同一批图两次训练结果差 2 个点以上原因随机种子没固定加上数据增强里的 mosaic、mixup 有随机性小数据集上波动被放大。解决训练脚本里固定torch.manual_seed、np.random.seed、random.seed并把 dataloader 的 shuffle 种子也固定。如果还波动说明数据量确实偏小考虑用更强的增强或者交叉验证取平均。别指望一次训练的数字就是最终精度97.7% 这种指标要跑三次取稳定值才有说服力。5. 复现 97.7% 的验证方法与推理端调优技巧想把 97.7% 这个数字复现出来验证环节必须和训练解耦。我一般会单独写一个评估脚本不依赖训练框架自带的 val避免预处理不一致带来的虚高。import torch, cv2, numpy as np from pathlib import Path model torch.load(runs/battery_run/weights/best.pt, map_locationcpu)[model].float().eval() CONF, IOU 0.4, 0.5 names [9v, button, dry] tp {n: 0 for n in names}; fp {n: 0 for n in names}; fn {n: 0 for n in names} def letterbox(img, size640): h, w img.shape[:2] r min(size / h, size / w) nh, nw int(round(h * r)), int(round(w * r)) resized cv2.resize(img, (nw, nh)) canvas np.full((size, size, 3), 114, np.uint8) canvas[:nh, :nw] resized return canvas, r for img_path in Path(dataset/images/test).glob(*.jpg): img0 cv2.imread(str(img_path)) img, ratio letterbox(img0) x torch.from_numpy(img[:, :, ::-1].copy()).permute(2, 0, 1).float().unsqueeze(0) / 255 with torch.no_grad(): pred model(x)[0] # NMS 后按类别统计 tp/fp/fn与 GT 做 IoU 匹配 # 此处省略 NMS 与匹配细节核心是 CONF/IOU 与训练保持一致这段的关键点letterbox 必须和训练一致置信度阈值 0.4 是电池场景的实测甜点低于它误检明显上升。评估时按类别分别统计别只看总体准确率。如果button的召回低于 0.9说明 97.7% 是总体数字掩盖了小目标问题实际产线上纽扣电池漏检的代价往往最高。推理端还有两个能提点的技巧。一是 TTA对测试图做水平翻转推理再合并电池没有明确朝向翻转增强通常能涨 0.5 到 1 个点代价是推理耗时翻倍适合离线质检不适合实时分拣。二是把置信度阈值和 NMS IoU 做成可配置不同产线的误检容忍度不一样硬编码在代码里后期改起来很痛苦。我自己的习惯是每次换数据集或换产线先跑一遍类别分布统计再决定阈值绝不直接套用上一批的参数。这套流程跑下来2030 张电池图复现 97% 以上是稳的真正决定成败的是标注一致性和预处理对齐而不是模型结构本身。希望帮到你。本文还有配套的精品资源点击获取
返回列表