news 2026/10/1 2:14:55

基于YOLOv8的果园果实自动计数:从数据标注到Gradio部署全流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于YOLOv8的果园果实自动计数:从数据标注到Gradio部署全流程

简介:这份资源面向计算机、人工智能、自动化等专业的在校学生与教师,提供一套基于YOLOv8的果园成熟果实自动计数完整方案,可用于毕业设计、课程设计或大作业。压缩包共8个文件,约15.91MB,包含3个Python脚本、3个模型权重文件与2个说明文本,分别承担可视化界面、模型训练与视频检测等功能,并附有完整数据集与部署教程,简单部署即可运行。项目可生成核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果及标签分布图,便于答辩展示与结果分析。目前已有30人学习,代码均经测试运行成功,拿来即用,也可在此基础上修改扩展实现其他功能,适合目标检测入门进阶与毕设保底需求。

1. 果园果实自动计数:从人工估产到 YOLOv8 落地的真实差距

果园里最耗人力的环节不是采摘,是数果。一个 50 亩的苹果园,三个人拿计数器逐棵走,一天下来眼睛发花,误差还在 15% 以上。更麻烦的是,估产窗口只有谢花后到套袋前那两三周,错过就只能凭经验拍脑袋。基于 YOLOv8 的果园成熟果实自动计数,解决的就是这件事:用普通摄像头或手机拍一圈,模型把果实框出来,按棵树或按亩给出数量,误差压到 5% 以内。它适合三类人——做毕设或课程设计的学生,需要一套能跑通、有界面、有数据集的完整闭环;做智慧农业的工程人员,想验证视觉计数在自家果园的可行性;还有想入门 YOLOv8 目标检测的开发者,拿一个真实场景练手比跑 COCO 有意思得多。这套方案的核心不是模型多先进,而是把数据标注、训练、推理、可视化界面、部署串成一条能复现的链路,简单部署即可运行。

2. 数据准备:果园数据集怎么标、怎么转、怎么分

2.1 果园场景的数据集长什么样

果园果实计数和通用目标检测最大的区别在数据分布。通用数据集里目标大小均匀、背景干净,果园恰恰相反:果实有遮挡,叶片和果实颜色接近,光照从顺光到逆光跨度极大,还有套袋果、病果、未熟果混在一起。如果数据集里只有晴天顺光的清晰果实,模型一到阴天就集体翻车。

我一般建议数据集至少覆盖四个维度:光照(顺光、逆光、阴天)、遮挡程度(无遮挡、半遮挡、严重遮挡)、果实成熟度(青果、转色果、熟果)、拍摄距离(近景单果、中景枝条、远景整树)。每个维度不用平均分配,但每个维度都得有样本,否则模型学到的就是“晴天顺光下的圆形物体”。

数据集规模上,单类果实计数任务,标注 800 到 1500 张图基本能到可用水平。低于 500 张,模型容易过拟合到背景;高于 3000 张,边际收益递减,除非你的果园品种特别杂。标注工具用 LabelImg 或 Labelme 都行,YOLOv8 检测任务用矩形框,导出 YOLO 格式的 txt。

2.2 用 Labelme 标注并转成 YOLOv8 格式

Labelme 默认输出 JSON,YOLOv8 要的是每行class_id x_center y_center width height的归一化 txt。转换脚本如下:

import json import os from pathlib import Path def labelme_to_yolo(json_dir, output_dir, class_map): """ json_dir: Labelme 标注文件目录 output_dir: 输出 YOLO txt 目录 class_map: {'apple': 0, 'orange': 1} 类别映射 """ os.makedirs(output_dir, exist_ok=True) for json_file in Path(json_dir).glob('*.json'): with open(json_file, 'r', encoding='utf-8') as f: data = json.load(f) img_w = data['imageWidth'] img_h = data['imageHeight'] lines = [] for shape in data['shapes']: label = shape['label'] if label not in class_map: continue points = shape['points'] xs = [p[0] for p in points] ys = [p[1] for p in points] x_min, x_max = min(xs), max(xs) y_min, y_max = min(ys), max(ys) # 归一化并转为中心点+宽高 x_center = (x_min + x_max) / 2.0 / img_w y_center = (y_min + y_max) / 2.0 / img_h width = (x_max - x_min) / img_w height = (y_max - y_min) / img_h # 过滤掉宽高为0的异常框 if width <= 0 or height <= 0: continue lines.append(f"{class_map[label]} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}") txt_path = Path(output_dir) / (json_file.stem + '.txt') with open(txt_path, 'w') as f: f.write('\n'.join(lines)) if __name__ == '__main__': labelme_to_yolo( json_dir='datasets/labelme_json', output_dir='datasets/labels', class_map={'apple': 0} )

这段脚本的关键点有三个。第一,class_map必须和后续data.yaml里的names顺序一致,否则类别全乱。第二,归一化用的是图像原始宽高,不是缩放后的尺寸,Labelme 的imageWidth和imageHeight就是原图尺寸,直接用。第三,过滤width <= 0的框,Labelme 里偶尔会画出退化的矩形,不处理会在训练时触发 NaN 损失。

2.3 数据集划分与 data.yaml 配置

标注完按 8:1:1 划分训练、验证、测试。划分脚本用splitfolders或手写都行,关键是保证同一棵树的图片不要同时出现在训练集和验证集,否则验证指标虚高。我一般按拍摄批次划分,同一批次(同一天、同一棵树)整体进训练或验证。

data.yaml配置:

path: /home/user/yolo_fruit train: images/train val: images/val test: images/test names: 0: apple

path是数据集根目录,train/val/test是相对路径。如果只有一类果实,names就一行。多类的话按 0、1、2 顺序写,和转换脚本的class_map对应。

注意:data.yaml里的路径不要用中文和空格,YOLOv8 在部分环境下解析中文路径会报编码错误,这是血泪经验。

3. YOLOv8 训练:参数怎么设、损失曲线怎么看、模型怎么选

3.1 环境配置与模型选型

环境配置是新手第一道坎。Ubuntu 20.04 上 CPU 版本和 GPU 版本差别很大。有 NVIDIA 显卡(比如 GTX 1660 Ti 6G)就装 CUDA 版本的 PyTorch,训练速度比 CPU 快 20 倍以上。没有显卡就用 CPU 版本,但训练时间会从几小时拉到几天,建议直接下载预训练权重做推理,或者用云 GPU 跑训练。

# 创建虚拟环境 conda create -n yolo_fruit python=3.9 -y conda activate yolo_fruit # 安装 PyTorch(GPU 版本,CUDA 11.8) pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 安装 ultralytics pip install ultralytics # 验证安装 yolo checks

yolo checks会输出环境信息,重点看 CUDA 是否可用。如果显示CUDA:0 (NVIDIA GeForce GTX 1660 Ti)就对了,显示CPU说明 PyTorch 装成了 CPU 版本,需要重装。

模型选型上,YOLOv8 提供 n/s/m/l/x 五个尺寸。果园计数任务,yolov8n和yolov8s是性价比最高的。n 模型 3.2M 参数,在 GTX 1660 Ti 上 batch=16 能跑到 60 FPS 以上,mAP 和 s 差距在 2 个点以内。如果果实遮挡严重、小目标多,上yolov8m,但推理速度会降到 30 FPS 左右。l 和 x 不建议,果园计数不需要那么大的容量,反而容易过拟合。

3.2 训练命令与关键参数

yolo detect train \ data=/home/user/yolo_fruit/data.yaml \ model=yolov8s.pt \ epochs=150 \ imgsz=640 \ batch=16 \ lr0=0.01 \ lrf=0.01 \ patience=30 \ device=0 \ workers=4 \ project=runs/fruit \ name=exp1

逐个说参数。epochs=150是上限,实际训练中patience=30会在 30 轮没有提升时早停,所以不用怕设大了。imgsz=640是输入尺寸,果园远景图里果实可能只有 20 像素,640 够用;如果果实特别小,上 1280,但显存占用翻四倍。batch=16在 6G 显存上跑 yolov8s 刚好,报 OOM 就降到 8。lr0=0.01是初始学习率,YOLOv8 默认 0.01,数据集小的话降到 0.001 更稳。lrf=0.01是最终学习率因子,余弦退火到lr0 * lrf。

workers=4是数据加载线程数,Windows 上设 0 避免多进程报错,Linux 上设 4 到 8。device=0指定第一块 GPU,CPU 训练改成device=cpu。

3.3 损失曲线怎么读、什么时候该停

训练完在runs/fruit/exp1/下会生成results.csv和results.png。重点看三条曲线:train/box_loss、val/box_loss、metrics/mAP50。

正常情况:train loss 持续下降,val loss 先降后平,mAP50 持续上升然后平台。如果 train loss 还在降但 val loss 开始上升,就是过拟合,早停该触发。如果两条 loss 都震荡不降,检查学习率是不是太大,或者标注框有没有问题。

我一般会额外画一张损失曲线图,方便对比多次实验:

import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv('runs/fruit/exp1/results.csv') df.columns = df.columns.str.strip() fig, axes = plt.subplots(1, 2, figsize=(12, 4)) axes[0].plot(df['epoch'], df['train/box_loss'], label='train') axes[0].plot(df['epoch'], df['val/box_loss'], label='val') axes[0].set_xlabel('epoch') axes[0].set_ylabel('box_loss') axes[0].legend() axes[1].plot(df['epoch'], df['metrics/mAP50(B)'], label='mAP50') axes[1].plot(df['epoch'], df['metrics/mAP50-95(B)'], label='mAP50-95') axes[1].set_xlabel('epoch') axes[1].set_ylabel('mAP') axes[1].legend() plt.tight_layout() plt.savefig('loss_curve.png', dpi=150)

results.csv的列名前后可能有空格,strip()处理一下。mAP50 到 0.85 以上、mAP50-95 到 0.55 以上,果园计数就基本可用了。如果 mAP50 卡在 0.6 上不去,优先检查数据集标注质量,而不是换模型。

4. 推理与可视化界面:从命令行到 Web 端

4.1 命令行推理与计数逻辑

训练完的权重在runs/fruit/exp1/weights/best.pt。命令行推理:

yolo detect predict \ model=runs/fruit/exp1/weights/best.pt \ source=test_images/ \ conf=0.4 \ iou=0.5 \ save=True \ save_txt=True

conf=0.4是置信度阈值,低于 0.4 的框不输出。果园场景建议 0.35 到 0.45,太低会误检叶片,太高会漏检遮挡果。iou=0.5是 NMS 的 IoU 阈值,果实密集时降到 0.4 避免漏检相邻果。

计数逻辑很简单:推理结果里每个框就是一个果实,统计框的数量即可。但要注意,如果一张图里有多棵树,需要按区域划分再计数。我一般用save_txt=True导出每张图的检测结果,再用脚本统计:

from pathlib import Path def count_fruits(txt_dir): """统计每张图的果实数量""" results = {} for txt_file in Path(txt_dir).glob('*.txt'): with open(txt_file, 'r') as f: lines = [l for l in f.readlines() if l.strip()] results[txt_file.stem] = len(lines) return results counts = count_fruits('runs/fruit/exp1/labels') total = sum(counts.values()) print(f"总果实数: {total}") for name, cnt in sorted(counts.items()): print(f"{name}: {cnt}")

这个脚本读的是 YOLO 格式的 txt,每行一个框,行数就是果实数。实际部署时会把这段逻辑嵌到推理流程里,直接输出计数结果。

4.2 用 Gradio 搭一个可视化界面

毕设和课程设计通常要求有可视化界面。Gradio 是最快的方式,几十行代码就能跑起来:

import gradio as gr from ultralytics import YOLO from PIL import Image import numpy as np model = YOLO('runs/fruit/exp1/weights/best.pt') def detect_and_count(image, conf_thres): """输入图片,返回标注图和计数结果""" results = model.predict(image, conf=conf_thres, iou=0.5, verbose=False) result = results[0] # 绘制检测框 annotated = result.plot() # 统计数量 count = len(result.boxes) if result.boxes is not None else 0 # 按类别统计 class_counts = {} if result.boxes is not None: for cls_id in result.boxes.cls.tolist(): name = model.names[int(cls_id)] class_counts[name] = class_counts.get(name, 0) + 1 count_text = f"总果实数: {count}\n" for name, cnt in class_counts.items(): count_text += f"{name}: {cnt}\n" return annotated, count_text demo = gr.Interface( fn=detect_and_count, inputs=[ gr.Image(type='pil', label='上传果园图片'), gr.Slider(0.1, 0.9, value=0.4, label='置信度阈值') ], outputs=[ gr.Image(type='numpy', label='检测结果'), gr.Textbox(label='计数结果') ], title='果园成熟果实自动计数系统', description='上传果园图片,自动检测并计数成熟果实' ) demo.launch(server_name='0.0.0.0', server_port=7860)

gr.Image(type='pil')接收 PIL 图像,model.predict直接支持。result.plot()返回带框的 numpy 数组,Gradio 能直接显示。conf_thres做成滑块,用户可以根据图片情况调整。server_name='0.0.0.0'让局域网内其他设备也能访问,方便演示。

注意:Gradio 默认只监听 127.0.0.1,部署到服务器上必须改成 0.0.0.0,否则外部访问不了。这是新手最常见的翻车点。

4.3 界面部署到服务器或本地

本地跑直接python app.py。部署到服务器的话,用nohup或screen保持后台运行:

nohup python app.py > gradio.log 2>&1 &

然后浏览器访问http://服务器IP:7860。如果服务器有防火墙,开放 7860 端口。CentOS 7 用firewall-cmd,Ubuntu 用ufw。

如果要做成更正式的 Web 系统,可以用 Flask 或 FastAPI 包一层,前端用 HTML 上传图片,后端调 YOLOv8 推理。但毕设和课程设计用 Gradio 足够了,省下来的时间花在模型调优上更值。

5. 避坑与排查:果园计数最容易翻车的五个地方

5.1 验证集指标很高,实际果园一跑就废

现象:训练时 mAP50 到 0.9,拿真实果园图片测试,漏检一半以上。

原因:验证集和训练集来自同一批拍摄,光照、角度、品种都一样,模型学到的是这批数据的特征,不是果实的通用特征。

解决:划分数据集时按拍摄批次分,不要随机分。如果只有一批数据,至少按光照条件分,晴天进训练,阴天进验证。更彻底的做法是补拍不同时间、不同果园的数据。

5.2 果实密集区域漏检严重

现象:单果、稀疏果检测正常,一簇果挤在一起就只框出两三个。

原因:NMS 的 IoU 阈值太高,相邻果实的框重叠度超过阈值被抑制掉了。

解决:推理时把iou从默认 0.7 降到 0.4 到 0.5。训练时可以在data.yaml里加augment: True,YOLOv8 默认开启 mosaic 增强,对密集目标有帮助。如果还不行,考虑用 YOLOv8 的agnostic_nms参数,或者换用 Soft-NMS。

5.3 训练到一半 loss 变成 NaN

现象:前几十轮正常,突然 loss 变 NaN,训练中断。

原因:学习率太大,或者标注框有异常值(宽高为 0、坐标超出图像范围)。

解决:先把lr0降到 0.001 重跑。如果还 NaN,检查标注文件,用脚本过滤掉宽高小于 1 像素的框。另外,batch太大也可能导致梯度爆炸,降到 8 试试。

5.4 Gradio 界面本地能跑,服务器上打不开

现象:服务器上python app.py显示 running,但浏览器访问超时。

原因:Gradio 默认监听 127.0.0.1,只允许本机访问;或者服务器防火墙没开放端口。

解决:demo.launch(server_name='0.0.0.0', server_port=7860),然后检查防火墙。Ubuntu 用sudo ufw allow 7860,CentOS 用sudo firewall-cmd --add-port=7860/tcp --permanent && sudo firewall-cmd --reload。

5.5 模型在 RK3588 等边缘设备上跑不动

现象:PC 上推理 50 FPS,部署到 RK3588 上只有 2 FPS。

原因:RK3588 的 NPU 需要模型转成 RKNN 格式才能加速,直接跑 PyTorch 模型用的是 CPU。

解决:用 RKNN-Toolkit2 把best.pt先转 ONNX,再转 RKNN。转换时注意量化方式,果园场景建议用混合量化,纯 INT8 量化会掉 3 到 5 个点 mAP。转换脚本和参数在 RKNN 官方文档里有,这里不展开。

6. 进阶技巧:用跟踪做视频计数,把重复计数压下去

图片计数有个天然缺陷:同一棵树拍多张,果实会被重复计数。视频计数能解决这个问题,思路是用 YOLOv8 检测加 ByteTrack 跟踪,给每个果实分配唯一 ID,最后统计 ID 数量。

from ultralytics import YOLO import cv2 model = YOLO('runs/fruit/exp1/weights/best.pt') # 用 YOLOv8 内置的跟踪模式 results = model.track( source='orchard_video.mp4', conf=0.4, iou=0.5, tracker='bytetrack.yaml', persist=True, save=True ) # 统计唯一 ID unique_ids = set() for result in results: if result.boxes is not None and result.boxes.id is not None: for track_id in result.boxes.id.tolist(): unique_ids.add(int(track_id)) print(f"视频中唯一果实数: {len(unique_ids)}")

model.track是 YOLOv8 内置的跟踪接口,tracker='bytetrack.yaml'指定跟踪器,persist=True让跟踪器在帧之间保持状态。result.boxes.id是每个检测框的跟踪 ID,同一果实在不同帧里 ID 不变。最后统计唯一 ID 数量就是果实总数。

这个方案的关键参数是conf和iou。视频跟踪对漏检更敏感,因为漏一帧可能导致 ID 切换,所以conf可以降到 0.3,宁可多检不可漏检。iou保持 0.5,太低会导致同一果实被分配多个 ID。

实际测试中,视频计数比图片计数误差低 3 到 5 个百分点,代价是推理速度减半。如果只是做毕设演示,图片计数够用;如果要做产品级估产,视频跟踪是必须的。

我自己踩过的坑是:一开始用图片计数,同一棵树拍了 5 张,数出来 200 个果,实际只有 80 个。后来改成视频绕树拍一圈,跟踪 ID 去重,数字才靠谱。这个教训让我明白,果园计数不是检测问题,是检测加去重的问题。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 2:14:52

现代智能雷达技术16——算法(2)

二次雷达&#xff08;SSR&#xff09;通过“一问一答”实现精准空域管理与敌我识别&#xff0c;其核心在于地面询问机与飞机应答机的数字对话。民用模式从广播式&#xff08;Mode A/C&#xff09;演进至点名式&#xff08;Mode S&#xff09;和自动广播&#xff08;ADS-B&#…

作者头像 李华
网站建设 2026/10/1 2:14:20

res-downloader 资源嗅探下载器三步上手完整指南

res-downloader 资源嗅探下载器三步上手完整指南 【免费下载链接】res-downloader 视频号、小程序、抖音、快手、小红书、直播流、m3u8、酷狗、QQ音乐等常见网络资源下载! 项目地址: https://gitcode.com/GitHub_Trending/re/res-downloader res-downloader 是一款跨平台…

作者头像 李华