简介一份基于PyTorch与YOLOv8的苹果成熟度检测完整项目面向毕业设计、课程设计及项目开发者解决从苹果图像采集标注、模型训练到推理部署的全流程需求支持一键运行适合快速搭建目标检测实验环境。压缩包共2000个文件大小约44.08MB核心内容包含约1000张苹果图片及对应txt标注文件用于目标框位置与成熟度类别标注、Python训练与推理脚本、yaml模型和数据配置、Markdown说明文档同时附带预训练权重pt文件及Jupyter示例目录结构严谨便于按模块检索学习。已有388人学习使用作为计算机视觉方向课程项目或毕设方案具有一定参考价值。项目提供完整数据集与分步说明可直接复现训练、评估与导出流程也可针对自定义数据调整参数进行迁移学习有效缩短开发周期辅助完成实验报告与功能演示。1. 苹果成熟度检测为什么选 YOLOv8 PyTorch在果园或分选线上苹果往往互相遮挡成熟度判断不能只看整张图。直接把图片塞给分类网络的做法遇到多个不同熟度的苹果就会失效。苹果成熟度检测本质上是一个目标检测问题既要定位每个苹果又要区分未熟、半熟、成熟。YOLOv8 把定位和分类放进一个网络配合 PyTorch 的动态图生态很适合毕业设计和课程设计从训练做到演示。Ultralytics 官方库把数据组织、训练命令都封装好了新手不用手写数据加载器有经验的人也可以继续压榨性能比如换 Backbone、导出 ONNX 部署到边缘设备。下面从数据、训练、推理和落地四个环节拆解所有代码按“新增一个数据集”的视角写方便迁移到其他农产品检测。2. YOLOv8 模型结构与苹果成熟度数据集准备2.1 YOLOv8 的目标检测头与 C2f 模块YOLOv8 的网络结构仍然是 Backbone-Neck-Head 的三段式设计。Backbone 里最核心的 C2f 模块把特征图分成两支一支做 1x1 卷积降维另一支经过多个 Bottleneck 后再拼接最后再融合。这种结构让梯度可以跨层回传深层和浅层信息都保留得比较完整对苹果这种从远处小目标到近景大目标都存在的任务是有利的。和 YOLOv5 不一样的是YOLOv8 的检测头换成了 Anchor-Free直接在特征图上回归目标中心和边框省去了预先聚类 Anchor 的环节后处理也更简单。很多人以为 YOLOv8 只是把 v5 改了个名其实它把分类分支和回归分支解耦并且去掉了 Objectness 分支推理时的输出维度也变了。理解这一点后面导出 ONNX 处理输出时就不会懵。2.2 苹果成熟度怎么定义类别目标检测的数据集质量决定模型上限。苹果成熟度没有绝对标准但做项目必须给一个可标注、可衡量的定义。我建议分成三类unripe绿色为主、half_ripe黄绿或淡红、ripe红色为主。不要分五级标注员自己都会标乱。类别名颜色特征标注建议备注unripe绿色为主果肉硬完整可见的苹果遮挡超过一半不标防止背景误检half_ripe黄绿、淡红颜色转折明显最容易和 ripe 混淆ripe红色或深红确保光照下颜色可辨阴影下需要补光如果画面里经常有叶子挡到苹果可以额外加一个 leaf 类把被叶子遮挡超过 1/3 的苹果标成 leaf让网络学习到“这部分不用检”。这样反而能降低苹果类别的误检率。类别不要贪多控制在 34 个课程设计完全够用。2.3 用 LabelImg 标注并生成 YOLO 格式数据集YOLOv8 的训练标注文件是 txt每行格式为类别id x_center y_center width height坐标都是归一化比例值。我推荐用 LabelImg 的 YOLO 模式直接标注省去转格式。如果你已经有一批 Pascal VOC 的 xml 标注可以用下面这段脚本批量转换成 YOLO txtimport os import xml.etree.ElementTree as ET classes [unripe, half_ripe, ripe, leaf] def xml_to_yolo(xml_path, out_dir): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size).find(width).text) img_h int(root.find(size).find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in classes: continue cls_id classes.index(name) box obj.find(bndbox) xmin int(box.find(xmin).text) ymin int(box.find(ymin).text) xmax int(box.find(xmax).text) ymax int(box.find(ymax).text) x_c ((xmin xmax) / 2) / img_w y_c ((ymin ymax) / 2) / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {x_c:.6f} {y_c:.6f} {w:.6f} {h:.6f}) txt_name os.path.basename(xml_path).replace(.xml, .txt) with open(os.path.join(out_dir, txt_name), w, encodingutf-8) as f: f.write(\n.join(lines))代码逻辑很简单从 xml 里读取原图宽高和 bndbox 绝对坐标转换成中心点加宽高的归一化数值。注意classes的顺序一旦确定后面训练配置里的 names 必须保持一致。如果顺序乱了框可能画对位置标签却错了这类错误很难排查。转换完 txt 后再写一个apple_data.yaml这是 Ultralytics 训练时的数据入口path: ./apple_data train: images/train val: images/val test: images/test names: 0: unripe 1: half_ripe 2: ripe 3: leafpath可以是相对路径但我更建议用绝对路径防止不同机器上工作目录不同导致找不到图片。图片和 txt 文件的文件名必须一一对应扩展名可以不同但主名要一致。2.4 数据增强与样本平衡苹果成熟度检测最常见的问题是“成熟果样本多、未熟样本少”尤其在你只收集了某些时段图片的时候。为了平衡除了多拍还要靠增强。Ultralytics 自带训练时增强比如hsv_h、degrees、fliplr等参数直接在命令行控制即可。如果离线增强可以用 Albumentations它会同步更新 bboximport albumentations as A transform A.Compose([ A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(p0.3), A.Rotate(limit15, p0.3), ], bbox_paramsA.BboxParams(formatyolo, label_fields[labels])) # 假设 img 是 BGR 或 RGB 数组bboxes 是归一化的 [[x_c, y_c, w, h], ...] # transformed transform(imageimg, bboxesbboxes, labelslabels)把bbox_params设成yolo后旋转和翻转会自动换算框坐标不需要自己改。这里要小心的是Rotate对于旋转角较大时框会发生形变建议角度控制在 15 度以内否则模型的边框回归会学得很纠结。3. 在 PyTorch 下训练 YOLOv8环境、命令与参数调优3.1 PyTorch 与 Ultralytics 环境安装要点训练前先搭 PyTorch 环境。是否需要 GPU 取决于你的数据集规模500 张以下用 CPU 也能跑但 50 个 epoch 可能要四五个小时有 NVIDIA 显卡还是尽量用 GPU。安装时先看驱动支持的最高 CUDA 版本再选择对应的 PyTorch。常见组合是 CUDA 11.8 或 12.1对应 PyTorch 2.x。我通常在 Anaconda 里建独立环境conda create -n yolo python3.10 conda activate yolo pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics安装完先确认 GPU 是否可用不然训练时才发现用的是 CPU 就晚了import torch print(torch.cuda.is_available()) print(torch.cuda.get_device_name(0) if torch.cuda.is_available() else CPU)如果输出False很大概率是装了 CPU 版 PyTorch或者显卡驱动太旧。重新安装对应 CUDA 版本的 PyTorch 即可。注意 Python 版本尽量选 3.10有部分 3.12 的朋友反馈 ultralytics 依赖解析会出问题。3.2 YOLOv8 训练自己的苹果数据集最小命令数据集目录准备好后训练命令很短这是 Ultralytics 做得好的地方yolo detect train dataapple_data.yaml modelyolov8n.pt epochs50 batch16 imgsz640 device0modelyolov8n.pt会自动下载预训练权重并作为初始权重不是从零开始训练。n 是 nano 版本显存占用小、训练快课程设计先用它跑通全流程。想提精度可以换成yolov8s.pt或yolov8m.pt但显存消耗会成倍增长普通 8G 显卡建议用 s。这里要注意data参数指向的是apple_data.yamlUltralytics 会根据里面的train和val路径自动寻找图片。运行后会在runs/detect/train下保存权重和训练曲线。3.3 关键训练参数表我整理了一份常用参数表帮助你在项目答辩时解释参数选择的理由参数常用值影响坑点modelyolov8n.pt / yolov8s.pt模型体量与速度需要预训练文件dataapple_data.yaml数据配置路径错会直接报错epochs50~100拟合程度过大过拟合过小欠拟合imgsz640输入分辨率苹果小就保持 640batch16~32占显存大小OOM 时优先降这个patience20早停轮数模型不升就停lr00.01初始学习率迁移学习时可调小freeze10冻结前几层小数据集防止破坏特征batch在 8G 显存下跑yolov8n一般能开到 32。如果报 CUDA Out Of Memory别急着换小模型先降低 batch 到 8 或者 4效果差距并不大。3.4 训练过程观察与损失函数曲线绘制训练时终端会输出box_loss、cls_loss、dfl_loss还有mAP50、mAP50-95。第一次训练的人容易只看 mAP其实要先看 loss 是否稳定下降。如果 loss 震荡不收敛优先检查数据集再考虑调学习率。Ultralytics 训练完成后会在结果目录生成results.csv我用一段脚本画损失函数曲线图直接放进论文或答辩 PPTimport pandas as pd import matplotlib.pyplot as plt res pd.read_csv(runs/detect/train/results.csv) plt.figure(figsize(10, 4)) plt.subplot(1, 2, 1) plt.plot(res[epoch], res[train/box_loss], labelbox_loss) plt.plot(res[epoch], res[train/cls_loss], labelcls_loss) plt.legend() plt.title(Training Loss) plt.subplot(1, 2, 2) plt.plot(res[epoch], res[val/mAP50-95], labelmAP50-95) plt.legend() plt.title(Validation mAP) plt.savefig(loss_curve.png, dpi200)代码从results.csv中读训练损失和验证 mAP 两个子图。不同 Ultralytics 版本的列名可能不一样如果 KeyError 就先print(res.columns)查看真实列名。保存的loss_curve.png可以直接用来展示模型收敛过程。3.5 从预训练权重迁移与超参数微调小数据集训练一定要用迁移学习。直接用官方预训练权重比随机初始化快不少精度也高很多。如果你的苹果图片不足 300 张我建议先冻结部分 Backbone 训练yolo detect train dataapple_data.yaml modelyolov8n.pt epochs30 freeze10 lr00.005freeze10表示冻结前 10 层lr0调小一点防止破坏预训练特征。跑完一轮后再减少freeze或解冻所有层用更小学习率微调。这样两阶段训练对成熟度检测尤其有效因为颜色和纹理特征在预训练模型里已经有了我们只需要微调检测器。4. 苹果成熟度检测推理实现从图片到摄像头的完整代码4.1 图片推理读取模型并检测成熟度训练结束后runs/detect/train/weights/下会有best.pt和last.pt。推理时优先用best.pt。下面这段代码是图片推理的最小实现from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict(apple_01.jpg, conf0.25, devicecpu, saveTrue) result results[0] boxes result.boxes.xyxy.cpu().numpy() classes result.boxes.cls.cpu().numpy().astype(int) scores result.boxes.conf.cpu().numpy() for box, cls, score in zip(boxes, classes, scores): name result.names[cls] x1, y1, x2, y2 [int(v) for v in box] print(f{name}: {score:.3f} at ({x1},{y1},{x2},{y2}))result.boxes.xyxy是四个角坐标cls是类别索引conf是置信度。conf0.25会过滤掉低分框如果背景误检多就调到 0.4如果漏检多就降到 0.2。saveTrue会把画好框的结果图保存到当前目录方便快速查看。4.2 应用推理统计不同成熟度苹果数量毕业设计里通常要输出“多少个未熟、多少熟”不能只画框。我在项目里会写一个统计函数import cv2 img cv2.imread(apple_01.jpg) result model.predict(img, conf0.25)[0] counts {unripe: 0, half_ripe: 0, ripe: 0} for box, cls, score in zip(result.boxes.xyxy, result.boxes.cls, result.boxes.conf): name result.names[int(cls)] if name in counts: counts[name] 1 x1, y1, x2, y2 [int(v) for v in box] color (0, 255, 0) if name ripe else (0, 165, 255) if name half_ripe else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, f{name} {score:.2f}, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) cv2.imwrite(output.jpg, img) print(counts)代码会按类别画不同颜色的框并在终端输出统计结果。实际使用中如果发现成熟果和木板箱的颜色很像可以在采集端增加照明训练集里多放一些不同背景的样本比调颜色阈值更可靠。4.3 摄像头实时识别读取视频流答辩演示时经常要现场连摄像头。用 VideoCapture 读取每一帧再用plot()快速画出结果import cv2 from ultralytics import YOLO model YOLO(best.pt) cap cv2.VideoCapture(0) while cap.isOpened(): ret, frame cap.read() if not ret: break results model.predict(frame, conf0.3, device0, verboseFalse) annotated results[0].plot() cv2.imshow(Apple Ripeness, annotated) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()plot()是官方封装好的画框方法不需要手动处理坐标。device0表示用 GPU没有 GPU 就改成cpu。摄像头编号在笔记本上可能是 0 或 1如果打开黑屏就多试几个编号。4.4 导出为 ONNX 以加速部署CPU 上跑 YOLOv8n 视频流一般只有十几帧导出成 ONNX 后用 ONNXRuntime 推理能稍微快一点也是嵌入式部署的前提yolo export modelbest.pt formatonnx opset12导出后的best.onnx可以用 ONNXRuntime 加载但要注意输出后处理逻辑和原始 PyTorch 模型不完全一样。如果是带 NMS 的版本直接取输出即可如果是不带 NMS 的版本需要自己解析xywh和置信度。我建议课程设计阶段还是在 PyTorch 环境里做推理把 ONNX 导出当作加分项写进报告。5. 毕业设计落地技巧模型评估、导出与演示优化5.1 从训练结果中提取评估指标答辩不能只会说“效果不错”要用数据说话。用model.val()可以拿到验证集上的核心指标from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(dataapple_data.yaml) print(metrics.box.map50) print(metrics.box.map) print(metrics.box.precision) print(metrics.box.recall)map50是 IoU 阈值 0.5 的 mAPmap是 0.5 到 0.95 的平均 mAP。对苹果成熟度检测我更看重 recall因为漏掉一个成熟果比多画一个框问题更严重。如果某个类别 recall 明显低回到数据里补充该类别难例。5.2 做一个极简 Gradio 演示界面答辩现场演示用 Gradio 做一个上传图片就能出结果的界面非常加分。代码量很少import gradio as gr from ultralytics import YOLO model YOLO(best.pt) def detect(img): result model.predict(img, conf0.25)[0] return result.plot() gr.Interface(fndetect, inputsimage, outputsimage, title苹果成熟度检测).launch()这条命令会启动一个本地 Web 页面答辩时可以展示识别效果也可以让评委直接传图片试比命令行演示直观得多。5.3 常见坑位与规避环境问题占了我遇到问题的一半以上。PyTorch 和 CUDA 版本不匹配直接导致cuda.is_available()为 False。检查时用nvidia-smi确认驱动上限再去 PyTorch 官网选择对应版本。Windows 下路径不能带中文yaml路径错了就会报AssertionError。另外如果显存不够优先降batch而不是降imgsz。5.4 项目目录结构建议一个清晰的项目结构能减少答辩时跑不通的风险apple_ripeness/ ├── data/ │ ├── images/ │ ├── labels/ │ └── apple_data.yaml ├── runs/ │ └── detect/train/weights/best.pt ├── scripts/ │ ├── train.py │ ├── detect.py │ └── gradio_app.py ├── requirements.txt └── README.md把训练、推理、演示各自拆成脚本依赖写进requirements.txt换一台电脑也能十分钟跑通。视频演示尽量用提前录好的 mp4 代替真实摄像头现场灯光、驱动不可控录好的视频更稳妥也能反复展示模型在不同场景的表现。本文还有配套的精品资源点击获取