简介基于YOLOv10的麦穗计数系统文档包面向熟悉Python和深度学习基础的研究者与工程开发人员提供了从环境搭建、模型准备、代码编写到GUI呈现的完整实现思路。系统以实时识别麦穗并自动计数为核心支持视频流处理与识别效果评估为农业生产中的麦穗数量估算和精准管理提供了可行的技术方案。压缩包内仅含1个docx文件大小约46KB文档结构清晰涵盖项目介绍、实现步骤、代码解释、数据示例及完整代码整合等章节便于按模块查阅和复现。内容中针对数据集多样性、环境适应性测试和用户体验优化等注意事项做了说明并指出了超参数优化、模型集成、边缘计算等未来改进方向对实际部署和后续研究具有参考价值。目前已有143人学习下载适合希望快速上手YOLOv10目标检测应用并落地农业场景的读者。1. 麦穗计数不是单纯的检测问题先把 YOLOv10 路线盘清楚田间测产时数麦穗依然是靠人弯腰数样方一小时最多数几十个小方格。基于 YOLOv10 的麦穗计数系统想解决的就是这件事用无人机或手机拍下田间图像训练检测模型自动定位每一根麦穗并输出总数。这个方向听起来就是把目标检测跑通就完事实际做下来真正的瓶颈并不在 YOLOv10 本身而是标注口径、数据划分和稠密遮挡下的计数后处理。这篇笔记面向要做智慧农业测产、或正准备把检测模型落到田间的开发者把从数据准备到样方验证的完整路线和踩过的坑讲清楚。2. 数据准备麦穗图像标注、目录组织与 wheat.yaml 创建麦穗计数系统成败一半在数据。YOLOv10 虽然有公开的 COCO 预训练权重但麦穗不是 COCO 里的常见类别迁移学习只能给你一个比较好的特征起点真正决定精度的是你喂进去的田间图像和标签质量。先别急着写训练命令把数据这一层打扎实后面能省出几倍调参时间。2.1 麦穗数据集的目录组织与标注边界我一般建议把整个数据集放在一个独立目录下images 和 labels 严格分开训练集、验证集、测试集三个子目录固定结构。这样后面写 wheat.yaml、跑训练、做推理路径都不会乱。wheat_dataset/ ├── images/ │ ├── train/ # 训练图像jpg 或 png │ ├── val/ # 验证图像 │ └── test/ # 测试图像只在最后评估时用 ├── labels/ │ ├── train/ # 与 images/train 同名同前缀的 txt │ └── val/ # 与 images/val 同名同前缀的 txt └── wheat.yaml # 数据集配置文件labels 里的每个 txt 与对应图像同名例如field01_001.jpg对应field01_001.txttxt 中每一行代表一个麦穗目标。目录结构确认后再去谈标注规范。标注麦穗比标注行人、车辆更容易出现口径分歧。我的经验是三条硬规则一穗一框一个分蘖穗就算一个目标哪怕它在图像里只露出大半框按可见的穗部主体来画不要为了把麦芒包进去强行放大边框遮挡严重的穗可见面积小于三分之一时宁可不标标了反而教模型去学残缺形状。拍麦穗时相机角度稍微俯视穗与穗之间互相遮挡很常见标注员的主观判断会直接影响计数结果所以建议同一批数据固定一个人标注不要多人混标。采集图像时注意覆盖不同密度、不同成熟期、不同光照条件。我用手机和无人机都拍过手机贴近麦穗时拍出来的穗子大、好检测但测产时真正需要的是无人机俯拍的广域图像那里面的麦穗往往只有二三十个像素。两种尺度最好都采集一些单一尺度会让模型在另一个尺度上计数偏差很大。2.2 把 VOC/JSON 标注转成 YOLO 格式转换脚本很多标注工具默认导出 VOC 格式的 XML 或 COCO 格式的 JSON而 YOLO 系列训练需要的是归一化的 txt 文件。格式转换这一步不需要借助在线转换网站一个几十行的 Python 脚本就能处理完也方便你批量检查。import xml.etree.ElementTree as ET import os # classes 按实际类别名映射麦穗这里只有一类 CLASSES {wheat: 0} def voc_xml_to_yolo_txt(xml_path, txt_path): tree ET.parse(xml_path) root tree.getroot() # 图像宽高来自 XML 的 size 节点 img_w float(root.find(size/width).text) img_h float(root.find(size/height).text) lines [] for obj in root.iter(object): name obj.find(name).text if name not in CLASSES: continue box obj.find(bndbox) x1 float(box.find(xmin).text) y1 float(box.find(ymin).text) x2 float(box.find(xmax).text) y2 float(box.find(ymax).text) # 归一化中心点坐标和宽高 cx (x1 x2) / 2.0 / img_w cy (y1 y2) / 2.0 / img_h bw (x2 - x1) / img_w bh (y2 - y1) / img_h # 防止越界 cx max(0.0, min(cx, 1.0)) cy max(0.0, min(cy, 1.0)) bw max(0.0, min(bw, 1.0)) bh max(0.0, min(bh, 1.0)) lines.append(f{CLASSES[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(txt_path, w, encodingutf-8) as f: f.write(\n.join(lines)) # 批量转换 xml_dir voc_xml txt_dir labels/train os.makedirs(txt_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue txt_name xml_name.replace(.xml, .txt) voc_xml_to_yolo_txt( os.path.join(xml_dir, xml_name), os.path.join(txt_dir, txt_name) )这个脚本的核心逻辑是从 XML 里读取 bndbox 的四个角点换算成 YOLO 格式的归一化中心点和宽高。注意img_w和img_h是图像原始尺寸不是标注工具里显示框时的缩放尺寸一旦取错后面训练出来的框会整体偏移。转换完成后随手抽查几个 txt 文件确认坐标值都在 0 到 1 之间。尺度特殊时宽高算出来超过 1 说明某个标注框画出了图像边界这类脏数据要靠max/min夹紧但最好回到标注工具里修掉不要靠脚本硬裁。2.3 创建 wheat.yaml三个最容易写错的点YOLOv10 的训练入口沿用了 ultralytics 的命令行体系数据集配置文件是一个 yaml 文件。网上搜“yolov10 yaml文件怎么创建”的人特别多其实格式上和 YOLOv8 几乎一样坑往往出在三个细节上。# wheat.yaml path: /home/user/wheat_dataset # 数据集根目录建议写绝对路径 train: images/train # 注意这里是目录不是图片文件列表 val: images/val # 验证集目录 # test: images/test # 有测试集时加上这行 names: 0: wheat第一个坑是path。写相对路径时训练命令的工作目录必须和 yaml 里的相对路径对齐否则直接提示找不到图片。我最开始贪图方便写../wheat_dataset换个目录跑训练就报错。第二个坑是train和val的值。有人习惯写成 txt 文件列表路径比如train: train.txtultralytics 也支持这种写法但对应的 txt 里每一行必须是图片的完整路径不能只写文件名。第三个坑是names的索引必须从 0 开始并且和标签文件里每行开头的整数对应。如果你把0: wheat写成了1: wheat训练不会直接报错但类别错位验证时 mAP 会莫名其妙地低。yaml 写好后可以用一个命令快速验证配置有没有问题yolo detect train datawheat.yaml modelyolov10s.pt epochs1 batch1如果数据集路径、标签格式有误这个最小化训练会在加载阶段就抛异常比跑完 150 个 epoch 再发现数据没读对要省时间得多。3. 训练配置模型选型、超参与显存预算数据准备好之后进入训练阶段。YOLOv10 论文里的核心卖点是去掉了推理侧的 NMS 依赖实现端到端检测在稠密目标场景下减少了后处理排序带来的不确定性。但在实际训练时你感受最深的反而不是 NMS-free 结构而是模型的尺寸选择、显存占用和训练收敛节奏。3.1 选哪个尺寸的 YOLOv10n/s/m 的取舍YOLOv10 官方提供了 n/s/m/l/x 几个尺寸麦穗计数用不到最大号关键是在 n、s、m 之间选。我一般按部署端和图像尺度来定如果最终要在嵌入式设备或手机端跑选 yolov10n如果只是做离线批量识别或者 PC 端分析直接上 yolov10s精度比 n 高一截显存压力也不大如果采集的无人机图很大而且你打算做切块检测那么 yolov10m 值得考虑。模型体积/速度定位适合场景单卡训练显存参考batch 16, imgsz 640yolov10n最小最快手机端、边缘设备、快速原型6GB 级别可以跑yolov10s均衡PC 端批量图像计数、一般精度需求8GB 级别建议起步yolov10m精度优先无人机大图切块检测、密集遮挡严重12GB 级别较稳妥麦穗不是大目标很多穗子在图里只有十几个像素所以我不建议为了速度强行用 yolov10n。端到端设计对标签噪声更敏感模型容量太小时标注里那点抖动会被放大成漏检。3.2 训练命令与关键超参表训练命令本身很简洁但参数值需要按数据量调整。下面给出我常用的一组起点配置。cd /home/user/wheat_dataset yolo detect train \ datawheat.yaml \ modelyolov10s.pt \ epochs150 \ batch16 \ imgsz640 \ device0 \ projectwheat_runs \ nameexp1 \ cos_lrTrue \ close_mosaic10重点说明几个参数。epochs不要照搬默认值如果训练集只有三五百张图150 个 epoch 足够再多就开始过拟合数据量到一千张以上时可以跑到 200。batch受显存限制显存不够就从 16 降到 8但不要低于 4否则 BN 统计量不稳。imgsz640是 YOLO 系列默认训练分辨率麦穗偏小的话可以试试 960但显存占用会明显上升不一定划算。cos_lrTrue让学习率按余弦曲线下降对收敛稳定性有帮助。close_mosaic10表示最后 10 个 epoch 关闭马赛克增强让模型在接近真实分布的数据上收尾。还有一个值得提的是预训练权重。modelyolov10s.pt会下载 COCO 预训练权重不要改成随机初始化。麦穗和 COCO 里的物体差异很大但底层的纹理、边缘特征仍然有用从预训练权重出发收敛速度快很多。如果不希望训练过程中保存每个 epoch 的权重可以在命令里加save_period0或者训练结束后只保留 best.pt 和 last.pt。3.3 训练过程中的三个检查点训练一旦跑起来不是盯着终端等结束就完事。第一个检查点是前 50 个 epoch 的 loss 曲线。train/loss 应该在波动中持续下降val/loss 可能有起伏但整体趋势不能掉头向上。第二个检查点是验证集的混淆矩阵。如果混淆矩阵里背景那一栏占比很高说明模型把大量背景当成了麦穗这时要回查标签是不是有些麦穗完全被麦芒盖住、标注框偏离了可见区域。第三个检查点是训练结束后的 val 预测图把验证集图像和预测框画在一起肉眼看一遍框是否贴合穗体、有没有一个目标被重复框出。这三个检查做完再谈调优而不是只看最后的 mAP 数字。4. 推理与计数从检测框到田间麦穗数的后处理训练完成后wheat_runs/exp1/weights/best.pt就是可用的模型。但把检测结果变成“麦穗数”这件事比想象中多一些细节。直接数框是可行的但要在 conf 阈值、大图切块和重复框处理上做约束否则计数结果会系统性偏大或偏小。4.1 单图计数最小脚本展示一个最小推理脚本拆解关键参数的作用。from ultralytics import YOLO import cv2 model YOLO(wheat_runs/exp1/weights/best.pt) results model.predict( sourcetest_field.jpg, conf0.30, # 置信度阈值麦穗密集时不宜太低 imgsz640, # 推理尺寸和训练保持一致 device0 ) for r in results: boxes r.boxes # YOLOv10 的端到端推理已经去掉了大部分 NMS 依赖 # 但 ultralytics 推理链路里仍保留 iou 参数以兼容不同任务 print(麦穗数量:, len(boxes)) im r.plot() # 在图像上画检测框 cv2.imwrite(test_field_result.jpg, im)conf是计数时最该调的参数。麦穗密集遮挡时低置信度预测会产生大量碎片框虚高计数太高又会漏掉远处的小穗。我一般从 0.25 到 0.4 之间做网格搜索挑一个让验证集计数误差最小的值。imgsz必须与训练时的分辨率一致否则检测尺度变化会带来精度损失。len(boxes)就是当前图像的麦穗计数这就是最朴素的检测计数法。4.2 视频与批量图像抽帧计数策略如果采集的是田间视频不需要逐帧计数。麦穗是静止目标相邻帧之间数量几乎不变逐帧推理只会增加计算量。常见做法是每 30 帧取一帧做检测或者干脆从视频中抽取几个关键帧求平均。批量图片推理更简单把source指向一个目录即可ultralytics 会自动遍历图片yolo detect predict \ modelwheat_runs/exp1/weights/best.pt \ source/home/user/field_images/ \ conf0.30 \ save_txtTrue \ save_confTruesave_txtTrue会把每个图片的检测结果写到同名 txtsave_confTrue会附带置信度数值。批量跑完后再写一个汇总脚本读取所有 txt 统计框数量就能得到整个地块的麦穗总数估计。4.3 大图切块检测解决小目标漏检无人机拍出来的图像动辄 4000x3000 像素直接缩放到 640 分辨率时一个麦穗只剩不到 10 个像素检测器很难识别。这时候切块检测比换更大模型更有效。切块思路是把大图裁成 640 或 960 的小块小块之间留一部分重叠分别检测后再把结果合并到原图坐标上。import cv2 import numpy as np from ultralytics import YOLO model YOLO(wheat_runs/exp1/weights/best.pt) img cv2.imread(large_field.jpg) H, W img.shape[:2] tile_size 960 overlap 120 # 重叠区域避免麦穗正好被切在边界上 detections [] for y in range(0, H, tile_size - overlap): for x in range(0, W, tile_size - overlap): y2 min(y tile_size, H) x2 min(x tile_size, W) tile img[y:y2, x:x2] results model.predict(tile, conf0.30) for box in results[0].boxes: cx, cy, bw, bh box.xywh[0].tolist() # 把小块坐标映射回大图坐标 detections.append((x cx, y cy, bw, bh)) # 简单去重中心点距离小于 20 像素的框视为同一穗 final [] for d in detections: dup False for f in final: if abs(d[0] - f[0]) 20 and abs(d[1] - f[1]) 20: dup True break if not dup: final.append(d) print(大图麦穗总数:, len(final))切块时重叠区不能为零否则切在边界上的麦穗会被切掉一半检测结果会在边缘处大量偏移。重叠 100 到 150 像素是比较稳的区间。合并时的去重逻辑有很多种写法我用的是中心点距离阈值法简单但有效如果你的图像里麦穗间距非常近20 像素阈值可能导致两个真实麦穗被合并需要调小到 10 左右。切块检测的代价是推理次数成倍增加但换来的是小目标召回率明显提升在计数任务里值这个成本。5. 麦穗计数避坑指南五个翻车点与排查方法这一章把常见问题集中整理出来每条按现象、原因、解决的思路写。这些都是实际跑麦穗计数时会遇到的真实情况不是理论推演。5.1 训练 mAP 高、换地块就“失灵”数据泄露现象训练集和验证集上的 mAP 都在 0.95 以上但把模型拿到另一个田块的照片上测试计数偏差超过 30%。原因数据划分出了问题。如果同一时间、同一田块拍的照片被随机分到了训练集和验证集验证集相当于“开卷考试”模型记住的是这块地的光照、土壤背景和品种特征而不是麦穗的通用形态。解决按田块或者按拍摄批次来划分数据保证同一个田块的图像只出现在训练集或验证集里。不要在整目录随机切分先按文件名前缀或拍摄时间分组再对组做随机划分。这个动作让我重做了两次数据之后的精度才真实。5.2 计数偏大标注重复框与 conf 阈值过低现象单张图片的检测框数量总是比人工数出来的穗数多 10% 以上肉眼看出有大量重叠框。原因两个来源。一是原始标注里同一个穗被标了两次训练时模型学到“一个目标出两个框”的坏习惯二是推理时 conf 设得太低本来应该是 0.5 的响应变成了两个 0.35 的小框通过阈值。解决标注完成后跑一个脚本统计同一张图内框中心点距离小于 10 像素的标签对人工确认后删除重复标签。推理时把 conf 从 0.25 提到 0.35 到 0.4观察计数结果和人工计数的差值曲线在回调率和虚检率之间取平衡点。5.3 成熟期麦穗与背景融为一体光照与增强现象晴天下拍的麦穗曝光正常但阴天或逆光照片里的麦穗和背景都是金黄色检测框从麦田边缘开始大量漏检。原因麦穗成熟后颜色和背景秸秆非常接近模型过度依赖颜色特征纹理特征的权重不够。单一光照条件下采集的数据让模型对光照变化非常敏感。解决数据增强上把 HSV 调整开到偏大一点让模型见过更多饱和度、色相偏移的样本实际采集时尽量覆盖顺光、侧光、阴天三种光照条件。如果已经训练完可以用 OpenCV 做一次 CLAHE 对比度增强后再推理往往能救回一部分边界模糊的漏检。5.4 yaml 文件创建报错三类常见报错定位现象执行训练命令后立刻报Dataset not found或no labels found in train。原因yaml 文件里的路径解析失败。path写错是最常见的其次是 labels 目录里的 txt 文件前缀与 images 目录里的图片前缀不一致哪怕只差一个后缀.jpg和.jpeg都会找不到标签。解决第一类报错检查path是不是绝对路径用ls /home/user/wheat_dataset/images/train确认命令里的路径能真实访问到第二类报错检查 images 和 labels 子目录下的文件是否一一对应特别注意隐藏文件和多出来的.DS_Store第三类报错检查 txt 里是不是空文件空标签文件会让训练逻辑直接跳过该图但在验证时又会被算进数据集导致统计异常。一个find . -name *.txt -empty命令能把所有空标签文件找出来。5.5 显存不足从模型、batch、分辨率三个方向降现象训练启动后直接报 CUDA out of memory或者跑几个 step 后进程被杀。原因输入分辨率、batch、模型尺寸三者叠加导致显存超限。麦穗数据集往往图像很大有人直接把原图 4000x3000 分辨率塞进去即使 batch4 也会爆显存。解决按顺序调整。先降 batch 到 8 或 4再把imgsz从 960 降到 640注意训练和推理必须保持一致最后一步才是换更小的模型从 yolov10s 降到 yolov10n。如果 batch 必须大于 8可以用梯度累积等效替代但 Epoch 之间的学习率策略要相应调整不要在同一轮里累积过多 batch。显存问题不应该靠牺牲训练分辨率硬扛因为麦穗本身是小目标分辨率一降召回率跟着就掉。6. 样方验证用 0.25 平方米样方给计数系统做体检mAP 是检测指标但测产用户要的是“数得准不准”。我习惯在系统上线前做一组样方验证在田里随机取 5 到 10 个 0.25 平方米的样方人工数出每个样方的麦穗数再用模型对同样区域拍照计数得到一组对照数据。样方编号人工计数模型计数偏差百分比186882.3%274796.8%39591-4.2%463664.8%51081101.9%用这组数据算两个值平均绝对百分比误差和线性回归系数。可以用一小段代码快速处理import numpy as np manual np.array([86, 74, 95, 63, 108]) model_count np.array([88, 79, 91, 66, 110]) # 平均绝对百分比误差 mape np.mean(np.abs(model_count - manual) / manual) * 100 k, b np.polyfit(manual, model_count, 1) print(fMAPE: {mape:.2f}%) print(f回归系数 k{k:.3f}, b{b:.3f})如果回归系数 k 接近 1、截距 b 接近 0说明系统没有明显的系统误差可以直接用模型计数代替人工计数。如果 k 明显大于 1说明整体高估后期可以乘一个修正系数。样方验证通过之后测产折算就顺理成章了每亩穗数等于样方麦穗数除以样方面积再乘以 666.67得到亩穗数后配合穗粒数和千粒重计算理论产量。当年我第一次做这套系统时省略了按地块划分数据这步mAP 看着漂亮田间验证却一塌糊涂。重新组织数据后样方验证的 MAPE 才压到 5% 以内。如果你准备做麦穗计数请把数据划分和样方验证当成和训练同等重要的事希望帮到你。本文还有配套的精品资源点击获取