简介:本资源是一套面向计算机视觉方向本科毕业设计与农业AI应用开发者的完整实践方案,聚焦小麦麦穗智能检测这一典型农业场景,融合YOLOv5目标检测模型与Flask轻量级Web服务,解决田间图像中麦穗定位、计数与可视化反馈的实际问题。压缩包共37个文件(2.23MB),含14个Python核心模块(如app.py、detect_utils.py、wbf_utils.py)、7个配置类YAML文件(含模型结构与训练参数)、12张实测JPG图像、1个HTML前端页面、1个CSS样式文件及README.md等文档,整体采用模块化分层设计,便于理解推理流程、调试部署逻辑与二次集成。目前已有95人学习下载,提供从数据预处理、YOLOv5模型训练、Flask接口封装到前后端联调的全流程源码与详细使用说明,涵盖异常处理、参数调优与性能优化要点,是深入掌握CV落地工程化实践的高价值参考项目。 做毕业设计选了农业视觉方向的同学,应该都绕不开目标检测这块。YOLOv5加Flask实现小麦麦穗检测,这套组合在近两年的毕设题目里出现频率非常高,原因也简单:一是YOLOv5在检测精度和部署灵活性上比较均衡,适合本科毕设的体量;二是Flask能快速把模型包装成一个网页应用,演示效果好,答辩时不用只对着终端输出讲,打开浏览器上传一张图就能看到检测框,说服力直接拉满。
我把整套东西完整跑了一遍,从环境搭建、数据处理、模型训练到Flask接口封装,中间踩了不少坑,也搞清楚了很多细节。这篇就以“毕设级小麦麦穗检测项目”为线索,把整个链路拆开讲透,重点说清楚每一步为什么要这么做、参数怎么调、哪些环节最容易出问题,以及项目中“使用说明”里没写出来的那些经验。
1. 项目整体设计与技术选型:为什么是YOLOv5加Flask
拿到这个毕设题目,第一反应不能是“直接开训”,而是要先想清楚系统架构。一个完整的小麦麦穗检测项目,实际上由三块组成:目标检测算法、模型训练/推理脚本、Web展示层。三者的技术选型决定了整个毕设的工作量和答辩深度。
1.1 检测模型选型的得与失
目标检测算法可选的方向很多,从传统的Haar、HOG加SVM,到两阶段的Faster R-CNN,再到单阶段的SSD和YOLO系列。Faster R-CNN精度高但推理速度慢,训练一轮的时间也长,在普通学生电脑上跑起来很难受;SSD速度尚可但小目标检测能力一般,而小麦麦穗在田间图像里恰恰有很多小目标——麦穗在远景图像里可能只有几十个像素大小,这直接影响了检测器的选型倾向。
YOLOv5正好卡在一个舒服的位置。它用CSPDarknet作为骨干网络,配合PANet做特征融合,在COCO数据集上表现出不错的精度,同时推理速度能跑到100FPS以上(看显卡型号)。更关键的是,Ultralytics官方把训练、验证、导出流程整理得特别完善,一个train.py就能跑完整轮训练,一个detect.py就能做推理,对毕设项目来说,这意味着你不需要从零手写训练循环,可以把精力放在数据处理和系统整合上。
这个取舍在毕设场景里非常重要。你要清楚,毕设不是发论文,目标不是刷SOTA精度,而是把一个完整项目做扎实。YOLOv5能让你花更少的时间在底层工程上,把更多时间留给前端交互、接口设计、实验对比这些更“看得见”的工作。
1.2 Web框架选型:Flask为什么够用
Web框架方面,最常见的对比对象就是Flask和Django。Django功能强大,自带Admin后台、ORM、表单校验,但这也意味着它的学习成本和项目体积都不小。而Flask是一个轻量级框架,核心代码非常精简,路由、请求处理、模板渲染这些基础功能都具备,对于“上传一张图、返回检测结果”这样的单功能应用来说,Flask几十行代码就能搞定,而且代码逻辑一目了然,答辩时也容易讲清楚。
有人可能会问,为什么不直接用前端JavaScript调用ONNX模型或者TensorFlow.js在浏览器里跑推理?理论上可以,但这对前端功底要求很高,模型转换和前端推理库的调优也容易出问题。而Flask的做法是:前端只管上传图片和展示结果,后端负责加载模型、做推理、返回标注图片,职责分离很清晰。这种模式也是真实工业项目里最常见的架构之一,做毕设的收获会更大。
1.3 项目解压后的结构认知
拿到压缩包后,首先应该建立对项目整体结构的认知。一个规范的YOLOv5+Flask项目,一般不会把训练代码和Web代码混在一起,而是分成几个明确的功能模块。正常的目录结构是这样:
project_root/ ├── app.py # Flask主程序 ├── models/ # 存放训练好的权重 │ └── best.pt ├── yolov5/ # YOLOv5官方源码目录 │ ├── train.py │ ├── detect.py │ └── ... ├── data/ # 数据集与配置文件 │ ├── mw.yaml │ └── images/ ├── templates/ # Flask前端页面 │ └── index.html ├── static/ │ ├── uploads/ # 上传的原始图片 │ └── results/ # 标注后的结果图 └── requirements.txt第一次拿到手,先别急着跑,把每个目录打开看一眼,弄清楚哪些是官方代码、哪些是用户自己写的,这对后续修改和答辩讲解非常重要。很多同学一上来就跑app.py,结果报错找不到yolov5模块,其实就是没理解相对路径的引用关系。
2. 环境准备:搭好一套能跑起来的PyTorch环境
环境配置是很多毕设项目的第一个坑,而且这个坑往往不是技术难度问题,而是版本不匹配问题。YOLOv5对PyTorch版本有要求,Flask对Python版本有要求,如果一开始没规划好,后面会遇到一堆莫名其妙的报错。
2.1 Python与PyTorch版本的匹配策略
YOLOv5官方在2023年左右的版本要求Python 3.8以上,推荐3.10左右。PyTorch方面,如果使用GPU训练,需要根据显卡驱动版本选择合适的CUDA版本,再安装对应的PyTorch。这里最容易踩的坑是:直接pip install torch装的是CPU版本,训练速度慢到怀疑人生;或者装了一个太新的CUDA版本,显卡驱动不支持。
我的建议是先用nvidia-smi查一下自己显卡驱动的CUDA版本上限,然后去PyTorch官网选择对应的安装命令。比如驱动支持CUDA 11.8,就装cu118的版本,不要盲目追求最新。如果机器没有独立显卡,那就老老实实装CPU版PyTorch,训练会很慢但也不是不能用,后面会讲到CPU机器怎么调整策略。
Python版本管理建议用conda,创建一个独立环境,不要污染系统Python:
conda create -n wheat python=3.10 -y conda activate wheat这样即使环境装坏了,删掉重来也就一条命令的成本。
2.2 YOLOv5官方依赖安装的坑
YOLOv5源码里自带一个requirements.txt,里面列了所有依赖项。理论上直接pip install -r requirements.txt就能装好,但实际执行时经常遇到几个问题:
一是torch和torchvision会自动装成最新版,这会覆盖你已经安装好的GPU版本。解决办法是先用pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118装好GPU版,再安装requirements.txt里的其他依赖,但安装时要用--no-deps跳过torch相关项,或者先手动删掉requirements.txt里torch那几行再装。
二是opencv-python在部分Python版本下会编译失败或出现依赖冲突。如果装的是opencv-python-headless,注意它和完整版不能共存,否则会报libGL.so.1相关的错误。
三是很多国内网络环境访问GitHub或PyTorch官方源下载模型权重时很慢或者直接超时。权重文件下载时可以手动到官方模型仓库下载,然后放到项目指定目录,比如YOLOv5会自动下载yolov5s.pt到当前目录,如果卡住了,可以手动下载后放进对应路径。
我实际跑通一份版本组合是这样的:Python 3.10 + torch 2.0.1+cu118 + torchvision 0.15.1+cu118 + opencv-python 4.8.0.74 + Flask 3.0.0。这套组合整体稳定,没有遇到明显的冲突问题。
2.3 没有GPU时怎么应对训练环节
本科毕设的机器配置参差不齐,用CPU训练YOLOv5不是不可以,但要有心理准备。一张640x640的图像在CPU上调参训练,一个epoch可能要跑几分钟到十几分钟,100个epoch基本是天文数字。
我的建议是分情况处理。如果只是想做推理演示,那就直接用官方预训练权重微调(实际上麦穗检测这类场景,微调也能出效果);如果是训练自己的小麦麦穗数据集,可以采取三种策略:一是降低图像分辨率到320或者416,训练速度会明显提升;二是缩小模型规模,用YOLOv5s甚至YOLOv5n替代YOLOv5m;三是租用云GPU训练,成本不高,比自己电脑熬几天强多了。
另外,不管用什么设备,训练之前一定要先跑一个极小的迭代(比如--epochs 3 --batch-size 2)验证整个流程能走通,再正式开训。这个习惯可以省下无数排查时间。
3. 数据准备与模型训练复现:小麦麦穗检测的核心
环境装好之后,就到了整个项目最有技术含量的部分——数据与训练。麦穗检测和通用物体检测不太一样,小目标多、背景复杂(农田、光照、遮挡),训练细节直接影响最终效果。我在这部分踩的坑最多,也最值得详细讲。
3.1 CDataset的获取与标注格式转换
做任何检测项目,数据是第一生产力。小麦麦穗检测的开源数据集最有名的是GWHD(Global Wheat Head Detection)数据集,在Kaggle上可以找到,里面包含了来自多个国家的田间小麦图像,标注的是麦穗的包围框。这个数据集也是很多毕设项目的直接数据来源。
但拿到原始数据后不能直接开训,因为YOLOv5需要的是YOLO格式的txt标注文件,每行格式是“类别id 中心点x归一化 中心点y归一化 宽度w归一化 高度h归一化”。而COCO格式是JSON文件,Pascal VOC格式是XML文件。如果原始数据是COCO格式,就需要写一个转换脚本:
import json import os # coco标注转yolo格式 def coco_to_yolo(coco_json, img_dir, output_dir): with open(coco_json, 'r') as f: coco_data = json.load(f) # 构建图像id到文件名映射 img_map = {img['id']: img['file_name'] for img in coco_data['images']} img_size_map = {img['id']: (img['width'], img['height']) for img in coco_data['images']} # 按图像分组标注 anns_by_img = {} for ann in coco_data['annotations']: img_id = ann['image_id'] if img_id not in anns_by_img: anns_by_img[img_id] = [] anns_by_img[img_id].append(ann) # 转换并写入txt for img_id, anns in anns_by_img.items(): img_w, img_h = img_size_map[img_id] txt_path = os.path.join(output_dir, img_map[img_id].replace('.jpg', '.txt')) with open(txt_path, 'w') as f: for ann in anns: x, y, w, h = ann['bbox'] # coco的bbox是左上角坐标和宽高,需要转成中心点格式 cx = (x + w / 2) / img_w cy = (y + h / 2) / img_h nw = w / img_w nh = h / img_h f.write(f"0 {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}\n")这个转换逻辑很简单,但有一个细节很容易被忽略:COCO的bbox和YOLO的bbox坐标定义不一样,前者是“左上角x、左上角y、宽、高”,后者是“中心点x、中心点y、宽、高”,转换的时候一定要先加半个宽高算出中心点。我见过很多同学没搞清楚这个区别,标注文件全错了,训练出来的模型预测框位置完全不对。
3.2 数据配置yaml的写法
YOLOv5训练时需要指定一个数据配置文件,告诉它训练集、验证集路径,类别数量和类别名称。小麦麦穗检测只有一个类别,所以配置非常简单。但路径这块特别容易踩坑,YOLOv5对路径的处理比较“直白”,一般是相对于yaml文件所在目录的路径,写成绝对路径最稳妥。配置文件内容如下:
# data/mw.yaml train: data/images/train val: data/images/val nc: 1 names: ['wheat']注意train和val参数指向的是包含图片的目录,而不是单个文件列表。YOLOv5会自动读取目录下所有图片,并根据同名原则找到对应的txt标注文件。如果你的数据目录里有子文件夹,YOLOv5也能递归扫描,但建议保持目录结构的简洁。
还要注意图片和标注文件必须同名且在同一层目录下。如果标注文件在另一个目录,YOLOv5是不认识的,除非你用/path/to/annotations这样的方式在yaml里指定,但YOLOv5原生并不支持标注目录和图像目录分离。所以准备数据时,把图片和txt放在一起,是最省心的方式。
3.3 训练参数怎么定:从超参数到图像尺寸
YOLOv5的训练参数直接影响模型效果和训练时间,有几个关键参数需要重点理解。
首先是--img,即训练时的输入图像尺寸。YOLOv5默认是640,但麦穗数据集里很多图像是1024x1024或者更大。直接缩放到640会丢失很多小目标细节,所以在显存允许的情况下,建议用--img 640或者--img 800。但如果你的显存不够(比如只有6GB),就得退回640甚至512,不然会爆显存。这是一个权衡:图像越大,小目标检测效果越好,但训练时间和显存消耗也越大。
其次是--batch-size。常规经验是在显存允许范围内尽量设大一点,因为batch size太小会导致训练不稳定,但太大的batch在单卡上又可能直接OOM。6GB显存跑YOLOv5s、640分辨率,batch-size可以设16;如果是YOLOv5m,设8比较保险。具体的显存占用可以用nvidia-smi实时监控。
然后是--epochs。这个数值没有绝对标准,但麦穗检测这种二分类小目标任务,一般300个epoch以内就够了。我习惯用早停策略(YOLOv5自带--patience参数),比如设置--patience 20,如果连续20个epoch验证集mAP没有提升,就自动停止训练。这样既节省时间,也能防止过拟合。
最后是预训练权重--weights。强烈建议使用yolov5s.pt做预训练,而不是从零训练。从零训练不仅收敛慢,而且最终效果往往不如微调。麦穗的形态和通用物体特征在某些层面是共通的,预训练权重能提供一个很好的初始状态。
一个完整的训练命令参考如下:
python train.py \ --data data/mw.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 200 \ --patience 20 \ --project runs/train \ --name wheat_experiment3.4 训练完成后的模型评估怎么看
训练结束后,YOLOv5会在runs/train/wheat_experiment/下生成一系列文件,其中最重要的是weights/best.pt和weights/last.pt。best.pt是在验证集上mAP最高的权重,last.pt是最后一个epoch的权重。做毕设部署时,一定要用best.pt,别用错了。
评估指标方面,YOLOv5训练过程中会打印每个epoch的precision、recall、mAP@0.5、mAP@0.5:0.95等指标。对于麦穗检测,我主要看mAP@0.5,因为在农业场景下,IoU阈值0.5已经能很好地衡量检测框的位置准确性。如果mAP@0.5能达到0.9以上,说明模型在验证集上表现优秀;如果只有0.6-0.7,可能是数据量不足、标注质量不高或者学习率设置不合适。
训练完成后,用val.py做一次正式验证:
python val.py \ --data data/mw.yaml \ --weights runs/train/wheat_experiment/weights/best.pt \ --img 640这个命令会输出详细的评估报告,并且把验证集上的预测结果可视化保存下来,方便你直观判断模型哪里做得好、哪里做得不好。
4. Flask Web服务实现:把模型封装成可访问的接口
训练出模型只是毕设的第一步,真正让项目“看起来完整”的关键是Web服务。这部分的代码量并不大,但要注意的细节非常多,尤其是模型的加载时机和并发访问时的线程安全问题。
4.1 Flask应用结构设计与路由规划
Flask应用的主程序可以很简单,但要对项目的可扩展性有所考虑。我的建议是:不要在单个文件里塞太多逻辑,至少把模型加载部分和图像处理部分抽出来。一个合理的Flask应用结构如下:
web_app/ ├── app.py ├── detector.py # 模型加载与推理封装 ├── templates/ │ └── index.html └── static/ ├── uploads/ └── results/detector.py里面封装一个麦穗检测器类,负责加载模型、执行推理、返回结果。app.py只负责Flask路由和页面渲染。这样分层的好处是,如果你以后想换一个检测模型或者换一种推理方式,只需要修改detector.py,不需要动Web层的代码。
路由规划方面,核心就两个:首页路由GET /渲染上传页面,以及上传接口POST /detect处理图片并返回结果。
4.2 推理代码的封装思路
推理封装是整条链路里最核心的一段代码,直接决定上传图片后能否正确返回检测框。这里最推荐的方式是用torch.hub加载本地YOLOv5源码。加载本地仓库的好处是离线可用,不依赖网络,而且这个代码结构和训练时用的源码完全一致,不容易出现版本冲突。
# detector.py import torch import cv2 import numpy as np import os class WheatDetector: def __init__(self, weights_path, device='cpu'): # 加载本地yolov5仓库 self.model = torch.hub.load( './yolov5', # 本地yolov5源码目录 'custom', # 自定义模型 path=weights_path, # best.pt的路径 source='local', # 从本地加载 force_reload=False ) self.model.conf = 0.25 # 置信度阈值 self.model.iou = 0.5 # NMS的IoU阈值 self.model.device = device def predict(self, image_path): # 推理 results = self.model(image_path, size=640) # 获取结果中的信息 detections = results.pandas().xyxy[0] # 绘制标注框 img = cv2.imread(image_path) for _, row in detections.iterrows(): x1, y1, x2, y2 = int(row['xmin']), int(row['ymin']), int(row['xmax']), int(row['ymax']) conf = row['confidence'] label = f"wheat {conf:.2f}" cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) return img, detections这里有几个关键点要特别说明。
第一个是torch.hub.load的force_reload参数,第一次运行如果设为True,它会重新加载模型并检查是否有缓存更新,这在调试阶段很有用,但每次启动都重新加载会比较慢。生产环境(或者演示时),建议设为False,启动时间会缩短很多。
第二个是推理时默认的size=640,这个要和训练时的--img保持一致,否则检测效果会受到影响。如果你训练时用了800,推理时也用800,这样模型看到的图像尺度和训练时是一致的。
第三个是模型加载时机。千万别在每次请求时都加载一次模型,那会导致前端等很久。应该把模型的加载放在Flask应用启动时,通过全局变量或者应用上下文持有。我的做法是直接实例化一个全局的WheatDetector对象,只加载一次。
4.3 Flask主程序与前端交互逻辑
app.py的代码结构很清晰:接收上传文件、保存图片、调用检测器推理、把结果图通过模板渲染展示。这里有一个小细节:为了让浏览器能显示结果图,需要把结果图保存到static目录下,然后用URL路径传递给前端模板,而不是直接把图片字节流塞给前端。
# app.py import os import uuid from flask import Flask, request, render_template, jsonify from detector import WheatDetector app = Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 限制上传大小16MB # 全局实例化检测器,只在应用启动时加载一次 detector = WheatDetector(weights_path='models/best.pt', device='cpu') UPLOAD_FOLDER = 'static/uploads' RESULT_FOLDER = 'static/results' os.makedirs(UPLOAD_FOLDER, exist_ok=True) os.makedirs(RESULT_FOLDER, exist_ok=True) @app.route('/') def index(): return render_template('index.html') @app.route('/detect', methods=['POST']) def detect(): if 'file' not in request.files: return jsonify({'error': '没有上传文件'}), 400 file = request.files['file'] if file.filename == '': return jsonify({'error': '文件名为空'}), 400 # 生成唯一文件名,避免覆盖 ext = file.filename.rsplit('.', 1)[-1].lower() if ext not in ['jpg', 'jpeg', 'png', 'bmp']: return jsonify({'error': '不支持的图片格式'}), 400 upload_name = f"{uuid.uuid4().hex}.{ext}" upload_path = os.path.join(UPLOAD_FOLDER, upload_name) file.save(upload_path) # 推理 result_img, detections = detector.predict(upload_path) # 保存结果图 result_name = f"result_{upload_name}" result_path = os.path.join(RESULT_FOLDER, result_name) cv2.imwrite(result_path, result_img) # 统计信息 count = len(detections) result_data = detections[['xmin', 'ymin', 'xmax', 'ymax', 'confidence']].to_dict('records') return render_template( 'index.html', original_image=f'uploads/{upload_name}', result_image=f'results/{result_name}', count=count, detections=result_data ) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=False)前端页面index.html只需要一个文件上传控件、一个提交按钮和一个图片展示区域。用原生HTML加简单的条件判断就够了,不需要引入前端框架。当Flask通过render_template传入检测结果后,页面里用Jinja2模板渲染出结果即可。这里体现了Flask的好处:模板和后端逻辑的整合非常自然,不需要单独写前端API调用代码。
4.4 上线前必须处理的安全与并发问题
很多毕设项目做到能跑就收工了,但如果你想让项目更完整,这些问题值得提前考虑。
首先是文件上传安全。一定要校验文件扩展名,并且把上传文件存储到独立目录,不要和源码混在一起。更安全一点的做法是,不信任原始文件名,用UUID重新生成文件名。我在上面的代码里就是这么做的,既避免用户在文件名里做文章,也防止中文文件名引起的编码问题。
其次是图片大小限制。app.config['MAX_CONTENT_LENGTH']限制了请求体大小,防止有人传一个几百MB的大图把服务拖垮。这个限制要根据你的服务器内存来定,我设置16MB是够用的。
然后是并发时的线程安全问题。Flask开发服务器默认是单进程多线程,每个请求会在一个独立线程中执行。如果检测器中的模型是只读推理,一般不会有问题,但如果你在多个线程中同时调用同一个模型的forward方法,理论上可能出现不确定行为。最稳妥的做法是给预测函数加一个线程锁:
import threading # 在WheatDetector初始化时 self.lock = threading.Lock() # 在predict方法中 def predict(self, image_path): with self.lock: results = self.model(image_path, size=640)这样虽然牺牲了并发能力,但保证了稳定性。如果你的并发要求更高,可以考虑用gunicorn加多个worker进程,每个进程单独加载一份模型,但这属于生产级部署的范畴,毕设够用就行。
5. 常见问题排查与实测经验
整个项目跑下来,我整理了遇到频率最高的一批问题,做成一个速查清单。如果你在复现过程中卡住了,优先对照这个表排查。
5.1 环境与依赖报错排雷表
| 报错信息 | 原因 | 解决方法 |
|---|---|---|
ModuleNotFoundError: No module named 'torch' | PyTorch未安装 | 按显卡版本安装对应PyTorch |
AttributeError: 'NoneType' object has no attribute 'shape' | 读取图片失败,很多情况下是中文路径问题 | 确保所有路径不含中文,用英文路径重命名 |
CUDA out of memory | 显存不够 | 减小batch-size或img尺寸,或换小模型 |
TypeError: __init__() got an unexpected keyword argument 'device' | YOLOv5版本和PyTorch版本不匹配 | 核对YOLOv5源码版本要求,升级或降级PyTorch |
FileNotFoundError: [Errno 2] No such file or directory: 'best.pt' | 权重路径错误 | 检查模型文件是否存在于指定路径,用绝对路径最保险 |
Host is unreachable或模型下载超时 | 自动下载权重/数据集时网络不通 | 手动下载权重放到对应目录,或用国内镜像源 |
| Flask启动后浏览器无法访问 | 监听地址不对 | app.run(host='0.0.0.0'),不要只写127.0.0.1 |
| 前端上传图片后页面转圈 | 模型推理太慢或报错未捕获 | 终端查看Flask运行日志,确认是否进入detect路由 |
其中中文路径问题在国内用户里出现频率极高。Windows下默认用户名可能是中文,YOLOv5的某些模块在读取路径时对中文支持不好,报错还很隐晦。最省事的方案就是:项目放英文纯路径下,比如D:\wheat_det\,不要放在桌面或中文目录里。
5.2 检测效果不好的调优手段
如果模型训练完了,但在实际图片上检测效果不理想,常见表现是漏检多、框不准、把背景误检成麦穗。这个问题要从几个方向排查。
第一个方向是数据质量。检查标注框是否贴紧了麦穗边界,是不是很多框框得过大或过小。麦穗在图像中可能是细长条,标注框如果框成一整片,模型学到的是“框出大片区域”而不是“框出单个麦穗”,效果自然差。如果数据集里小目标占比高,考虑使用YOLOv5的--multi-scale参数做多尺度训练,让模型在不同尺度上都见过麦穗。
第二个方向是推理参数。调试时可以把detector.model.conf调低到0.1,看看是不是置信度阈值设得太高导致漏检;如果出现大量低置信度误检,再把阈值调回0.25或者更高。model.iou影响NMS的去重强度,麦穗这类密集目标,IoU阈值可以调高到0.6,减少相邻麦穗被合并成一个框的情况。
第三个方向是图像预处理。如果原始图像光照极不均匀,直接训练和推理的效果都会打折。可以考虑在推理前先做一下直方图均衡化,或者归一化处理。但要注意,训练时如果没做这个处理,推理时临时加上也不一定更好,保持训练和推理的一致性才是关键。
遇到过最典型的一个案例:训练时用的是原始大图,推理时Flask代码里用了cv2.resize把图片压到320后传给模型,结果检测效果大幅下降。后来排查了半天才发现是推理尺寸和训练尺寸不一致。这个坑提醒大家,修改任何代码之前,先检查输入图像的尺寸是否符合模型预期。
5.3 项目扩展的实际方向
做完整套系统之后,如果你想让毕设更有亮点,有几个扩展方向可以参考。
第一是把模型换成更新的YOLOv8或者YOLOv10,对比检测效果和推理速度。这个方向操作起来不算难,因为Ultralytics的API设计得很统一,改一改detector.py里面的加载方式就能跑通,答辩时还能多讲一个“模型选型实验对比”的模块。
第二是做一个“批量检测”功能,支持用户一次性上传多张图片,后台用一个任务队列逐个处理。这个功能在真实农业场景中很实用,比如检测一整块地的多张航拍照片。实现上可以用Falsk加Celery,但毕设级别的话,用线程池和简单的任务列表就足够了。
第三是加入统计报表功能,检测完之后按检测数量、置信度分布生成统计图表。前端可以用ECharts画图,后端只需要把检测结果保存到JSON或者SQLite里。
我个人的建议是,选一个方向做深,不要贪多。毕设答辩最怕的是“什么都有,但什么都不深”,与其做五个功能都很粗糙,不如把一个功能做到极致,把其中的技术细节讲清楚。
6. 实测操作过程还原:从零复现一遍完整项目
为了让你能按图索骥地把项目跑通,我把整套复现的操作步骤从头到尾写一遍。这里以一台8GB显存的Windows电脑为例,操作步骤在Linux和Mac上基本一致,只是安装命令会有些差异。
6.1 推理演示的快速复现步骤
如果你暂时没有训练计划,只是想把整个项目跑起来看看效果,可以按以下步骤操作。
第一步,安装Miniconda(如果没装的话),然后创建并激活虚拟环境。Python版本建议3.10。
第二步,安装PyTorch。假设你的GPU驱动支持CUDA 11.8,执行:
pip install torch==2.0.1 torchvision==0.15.1 --index-url https://download.pytorch.org/whl/cu118没有GPU就执行:
pip install torch==2.0.1 torchvision==0.15.1第三步,在项目根目录下安装其余依赖:
pip install -r requirements.txt如果requirements里包含torch,建议先手动把torch相关行删掉,或者安装时加--no-deps。
第四步,确认models/best.pt文件存在于指定目录。如果项目没有提供权重,可以用你自己训练好的,或者下载一个开源的麦穗检测权重。
第五步,启动Flask应用:
python app.py看到* Running on http://0.0.0.0:5000就说明启动成功了。浏览器打开http://127.0.0.1:5000,上传一张包含麦穗的图片,等待几秒钟,就能看到检测结果和麦穗数量。
6.2 用自己的数据训练麦穗模型的完整流程
如果你手头有自己的小麦图片数据集,或者从GWHD数据集取了一部分,想从头训练一个属于自己的模型,操作步骤如下。
第一步,把数据集整理成YOLOv5要求的格式。参考前面的转换脚本,将标注转换成YOLO格式的txt,确保每张图片都有对应的txt文件,且两者放在同一目录下。然后把数据集按比例划分成训练集和验证集,常见比例是8:2或者9:1。
第二步,创建数据配置文件data/mw.yaml,内容参考前面提到的配置。
第三步,从官方仓库下载预训练权重yolov5s.pt,放到项目根目录。
第四步,执行训练命令:
python train.py \ --data data/mw.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 200 \ --patience 20 \ --name wheat_custom训练过程中可以用TensorBoard查看loss曲线、mAP变化,YOLOv5默认会在runs/train/wheat_custom/下生成训练日志。
第五步,训练完成后,用val.py验证模型效果,然后修改detector.py里的weights_path,指向自己训练好的best.pt,重启Flask应用即可。
这套流程看起来很常规,但每个环节都有需要注意的细节。数据划分时要保证训练集和验证集不来自同一张图像的不同裁剪区域,避免数据泄漏;训练集和验证集的类别分布要尽量一致,防止验证集里出现训练集没见过的形态特征。
7. 实测中的性能调优和体验优化
项目跑通之后,性能优化是一个很实际的话题。尤其是把模型部署为Web服务后,用户上传图片的等待时间直接决定演示体验。我实测下来,一个YOLOv5s模型在CPU上推理一张640x640的图片,大约需要500到1000毫秒,在GPU上则只需要30到60毫秒。这个差异在答辩演示现场会非常明显。
7.1 模型推理加速的几个实用手段
如果你是在本地笔记本上用CPU做演示,推荐几个加速手段。
第一个是使用TensorRT或者ONNX Runtime替换PyTorch原生推理。YOLOv5官方提供了export.py脚本,可以把模型导出为ONNX格式。
python export.py --weights models/best.pt --include onnx导出后,在detector.py中使用onnxruntime进行推理,代码逻辑略有变化,但推理速度在CPU上通常能提升1.5到2倍。不过ONNX的部署代码需要自己写前处理和后处理,工程量比直接用PyTorch大不少。如果是为了答辩演示,可能不值得折腾,但如果你想在论文里写一笔“部署优化”,这个是值得做的。
第二个是切换推理尺寸。如果对精度要求不是特别苛刻,把推理时的size从640降到480,推理速度会明显加快。但要注意验证一下检测效果没有明显下降。
第三个是确保推理时不执行不必要的计算。比如用YOLOv5自带的detect.py时,它会输出很多调试信息;在Flask内调用时,把日志级别调高,避免不必要的IO操作拖慢速度。
7.2 前端交互的体验优化
性能优化不只是后端推理速度,前端体验也很重要。我见过很多毕设项目,上传图片后没有任何反馈,用户以为卡死了,其实是在等待推理结果。最简单的优化是在前端加一个loading状态:
<form id="uploadForm" enctype="multipart/form-data"> <input type="file" name="file" accept="image/*" required> <button type="submit">开始检测</button> </form> <div id="loading" style="display:none;">检测中,请稍候...</div> <script> document.getElementById('uploadForm').addEventListener('submit', function() { document.getElementById('loading').style.display = 'block'; }); </script>这个改动成本极低,但对演示体验的提升非常明显。
另一个体验优化是显示检测到的麦穗数量统计。前端页面在展示结果图片的同时,把count变量显示出来,比如“检测到 128 个麦穗”。这个数据在Flask的render_template中已经传过去了,前端直接展示就行。
8. 最后再分享一点项目实操心得
做这个小麦麦穗检测项目,我最大的感受是:毕设项目的难点往往不在算法本身,而在于把算法工程的每一个细节都做扎实。很多人以为模型训练完就万事大吉了,实际上从权重文件到用户能访问的Web服务,中间隔着一层又一层容易被忽视的细节。
我在实际调试中花时间最多的不是YOLOv5,反而是Flask和前端之间的数据传递。比如结果图路径的拼接、静态文件目录的注册、上传文件名的编码问题,这些看似简单的地方,一旦出错,报错信息往往又不太直观。我的建议是,遇到问题先看Flask的终端日志,把异常信息完整贴出来,再结合报错关键字去查,比盲目改代码高效得多。
另外,做这种项目一定要养成“小步快跑”的习惯。不要等全部代码写完再启动测试,而是每完成一个功能就启动一次Flask,手动上传一张图验证。比如先实现“上传图片并保存”,再实现“加载模型并推理”,最后实现“结果展示”。每一步都验证通过再往下走,这样即使出问题,范围也限定在最近改动的几行代码里。
关于模型这部分,如果你是用自己的数据集训练的,记得保留训练过程中的输出文件。答辩时老师很可能会问训练集的规模、标注方式、最终精度,这些数据跑一遍val.py就都有了。提前准备好这些数字,比临时翻训练日志要从容得多。
还想多说一句:系统的最终效果很大程度上取决于标注质量,而不是模型复杂度。如果你发现训练了很久模型效果依然一般,先回头看数据集里有没有标注错误、漏标或者框不贴合的情况。我一直觉得,数据质量才是这个项目的真正上限,模型只是把数据里的信息表达出来而已。
本文还有配套的精品资源,点击获取