资讯中心

YOLO11+PyQt5实现花卉图像分割与摄像头实时识别

📅 2026/9/28 17:07:39
YOLO11+PyQt5实现花卉图像分割与摄像头实时识别
简介基于Python和PyTorch的YOLOv11花卉图像分割识别项目面向深度学习初学者、图像识别开发者及科研人员。资源包含完整数据集、训练代码、PyQt界面和摄像头实时识别模块解决复杂背景下的花卉图像精准分割与实时识别需求。压缩包共726个文件大小42.15MB涵盖285张jpg图片、279个txt标注文件、139个json配置、4个pt模型权重、3个yaml参数文件及3个Python脚本结构清晰图片与标签一一对应配置与权重文件齐全。代码遵循「划分数据集→训练→界面识别」三段式流程01划分数据集.py完成训练集/验证集划分02train.py调用YOLOv11训练并保存权重03pyqt.py通过PyQt加载模型实现实时摄像头花卉识别。附带requirements.txt依赖清单和训练可视化结果如batch预测图、标签相关图、results.csv便于复现环境与效果评估配套数据与权重均可直接用于推理验证和二次开发。已有208人学习下载适合希望快速上手目标检测与图像分割实战的开发者参考学习整体运行路线明确开箱即可体验完整识别流程。1. 花卉图像分割识别项目是什么从数据到摄像头的一条线拿到“花卉图像分割识别”这个需求很多人的第一反应是训练一个分类模型判断画面里是玫瑰还是月季。但分类只回答“这是什么品种”回答不了“花在哪里、花瓣占多大面积、画面里重叠的几朵花怎么拆开”。标题这条技术路线用的是 YOLO11 做实例分割——每朵花输出一个 mask 轮廓叠加上摄像头输入和 PyQt5 桌面界面就变成一套能实时看、能统计数量、能大致算面积占比的小工具。适合理科生做毕业设计、农业信息化方向的工程师、想给植物表型测量搭原型的人。下面按数据准备、训练、界面集成、避坑、验证五段讲完每段都能直接抄。2. 数据集准备与标注花的掩码怎么来、怎么转成 YOLO11 分割格式2.1 数据集选型公开分类数据为什么不能直接训练公开的花卉数据集大多是分类标注比如 Oxford 102 Flower 这类每张图只有一个品种标签没有像素级轮廓。分割训练要的是“每朵花的边界多边形”所以最可靠的做法是两条路一是拿分类数据集自己标注二是用预标注工具先生成 mask 再人工修正。我一般用第二种先把一批图丢给通用分割模型生成候选掩码再在标注工具里删改工作量能省一半左右。标注口径决定训练上限。标题强调的是“花卉识别”不是“叶片识别”所以建议只描花器官花瓣和花蕊作为一个整体实例叶子、茎、花盆都不进标注集。如果后续要算叶面积再单独开一类。重叠的花朵也要每朵单独描一个实例YOLO11 训练时允许 mask 重叠推理时靠 NMS 处理标注时不用刻意避开。类别数量一开始控制在 510 类就比较合适。类别太多会明显拉长标注和训练迭代时间先把整条链路跑通再逐步扩类。2.2 labelme JSON 转 YOLO11 分割 txt可抄的转换脚本标注工具输出的格式五花八门labelme 的 JSON 最常见而 YOLO11 分割训练要的是“每张图一个同名 txt每行一个实例”的格式第一列是 class_id后面是按顺序排列的多边形顶点归一化坐标。写一个通用转换脚本把labelme_to_yolo这条路趟平。import json from pathlib import Path CLASS_NAMES [rose, tulip, sunflower, daisy, orchid] # 与 yaml 的 names 严格对齐 def labelme_to_yolo(json_path: str, out_dir: str) - None: with open(json_path, r, encodingutf-8) as f: data json.load(f) w, h float(data[imageWidth]), float(data[imageHeight]) lines [] for shape in data[shapes]: if shape.get(shape_type) ! polygon: # rectangle / circle 先手动转成多边形再导出否则训练大概率出问题 continue # label 必须出现在 CLASS_NAMES 里否则说明标注别名没对齐 cat CLASS_NAMES.index(shape[label]) pts shape[points] norm [] for x, y in pts: # 浮点除法不能写成 x // w nx max(0.0, min(1.0, x / w)) ny max(0.0, min(1.0, y / h)) norm.append(f{nx:.6f}) norm.append(f{ny:.6f}) lines.append(f{cat} .join(norm)) out_path Path(out_dir) / (Path(json_path).stem .txt) out_path.parent.mkdir(parentsTrue, exist_okTrue) out_path.write_text(\n.join(lines), encodingutf-8)说明两点。归一化必须用浮点除法写成x // w会让所有坐标变成 0 或 1训练出来的 mask 全是整幅图或者一条线这是转换阶段最高频的翻车原因。其次CLASS_NAMES.index(shape[label])如果抛ValueError说明标注里的名字和后面的flowers.yaml没对齐与其猜测不如让它直接报错把问题暴露在转换阶段。转换完的目录结构按 YOLO 约定组织flowers/ images/train/ images/val/ labels/train/ labels/val/ flowers.yaml图片和 txt 保持同名txt 里每行对应一张图里的一朵花。目录组织对了后面训练命令很少要改路径。2.3 按类别做 train/val 划分固定随机种子少踩坑很多项目直接random.shuffle所有图片再八二分这样做在花数据集上有隐患如果某个品种的图片恰好集中在某个光照条件下拍完随机切分可能让该品种全部跑进验证集。按 label 里出现的类别集合做分层划分更稳。import random from pathlib import Path random.seed(42) def split_by_class(label_dir: str, train_ratio: float 0.8) - tuple[list, list]: label_files sorted(Path(label_dir).glob(*.txt)) groups {} for f in label_files: cls_ids set() for line in f.read_text(encodingutf-8).strip().splitlines(): if line.strip(): cls_ids.add(int(line.split()[0])) groups.setdefault(tuple(sorted(cls_ids)), []).append(f) train_files, val_files [], [] for cls_ids, files in groups.items(): random.shuffle(files) n_val max(1, int(len(files) * (1 - train_ratio))) val_files.extend(files[:n_val]) train_files.extend(files[n_val:]) return train_files, val_files固定random.seed(42)每次切分结果一致方便复现和对比实验。同一组图片里如果一朵花被标了两次比如两张图来自同一段视频分层逻辑保证它们大概率落进同一侧避免验证集虚高。训练集和验证集的标注质量同样重要尤其是 val 里的错误 mask 会直接拉低 mAP让你误判模型能力。转换完成后抽 20 张图把 mask 画在原图上肉眼过一遍比训练后的任何调参都更省时间。3. 用 Python深度学习训练 YOLO11 分割模型命令与关键参数3.1 环境配置Python、torch、ultralytics 的安装顺序环境配置的坑多半出在 torch 和 CUDA 版本匹配上。常见做法是先去 PyTorch 官网把当前 CUDA 对应的 torch 装好再装 ultralytics顺序反过来容易把 torch 覆盖成 CPU 版。# 先确认 python 版本3.10 或 3.11 最省心 python --version # 有 GPU 就先装 GPU 版 torchdevicecpu 的机器跳过这一步 pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 # 再装 ultralytics自动带上核心依赖 pip install ultralytics第一次训练时会自动下载yolo11n-seg.pt权重网络正常就能拉到。装完跑一句yolo predict modelyolo11n-seg.pt sourcehttps://ultralytics.com/images/bus.jpg验证环境能出检测框说明推理链路通这时再进训练排错面会小很多。3.2 训练最小命令yaml 结构、参数表和收敛判据先把数据集配置写对flowers.yaml是训练命令的入口path: ./flowers train: images/train val: images/val names: 0: rose 1: tulip 2: sunflower 3: daisy 4: orchidyaml 里 names 的索引必须和上一章转换脚本里的CLASS_NAMES顺序完全一致这是整个训练最容易出错又最不容易被发现的地方。训练命令用 ultralytics 官方 CLI 就够yolo segment train dataflowers.yaml modelyolo11n-seg.pt epochs100 imgsz640 batch16 device0等价的 Python 写法方便在脚本里改参from ultralytics import YOLO model YOLO(yolo11n-seg.pt) # 从官方的分割预训练权重开始 results model.train( dataflowers.yaml, epochs100, imgsz640, batch16, device0, patience20, cacheFalse, )关键参数按这个表调别一上来全开参数默认值说明花卉场景建议epochs100总训练轮数100 起步看 mAP 是否还有上升趋势imgsz640输入分辨率显存紧张用 480花瓣边缘要求高用 800batch16每批图数显存不够逐级减半16 → 8 → 4patience50早停耐心值2030 合适避免过拟合device0GPU 编号CPU 训练把 batch 降到 4imgsz 用 480cacheFalse是否把图缓存进内存内存小就保持 False收敛判断以验证集的 mask mAP50 为准不要盯着 train loss。训练日志里seg_loss和box_loss在下降同时mAP50(B)和mAP50(M)在涨说明在正常学习。最容易被忽略的是早停patience 设太大最后几十个 epoch 只是来回震荡白白浪费时间我在小数据集上一般用 20。3.3 模型选型n/s/m 三档怎么选显存和精度怎么权衡YOLO11 分割系列从 n 到 x模型体积递增速度和精度此消彼长。花卉场景里花瓣边缘很细n 档的 mask 边缘容易出现锯齿适合快速验证流程正式项目用 s 档精度、显存、推理速度最均衡如果要对花朵做形态学测量比如测花瓣长宽比再上 m 档。显存参考n 档在 6GB 显卡上 batch16 都能跑s 档建议 8GB 以上m 档直接上 12GB。没有独显的时候用 Google Colab 的免费 GPU 跑 s 档也能接受只是数据上传和权重下载会多花点时间。训练完看runs/segment/train/weights/目录best.pt是按验证 mAP 选出的last.pt是最后一个 epoch。部署一律用best.pt别贪 last。4. PyQt5 界面与摄像头实时识别从视频流到桌面窗口4.1 摄像头推理streamTrue 才叫实时命令行推理和摄像头实时推理是两码事。摄像头场景最容易犯的错是每帧循环里重新加载模型或者把每一帧当成独立图片丢进model.predict()。正确做法是模型加载一次用streamTrue拿生成器。from ultralytics import YOLO import cv2 model YOLO(best.pt) cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) for r in model.predict(sourcecap, streamTrue, conf0.25, iou0.45, imgsz640, verboseFalse): ann r.plot() # 原图 mask 框BGR 格式 cv2.imshow(flower segment, ann) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()sourcecap传的是摄像头对象streamTrue让 predict 返回生成器每迭代一次取一帧内部维护解码和推理状态。imgsz对帧率影响最大640 是平衡点要更流畅就降到 480。conf0.25是置信度门槛iou0.45是 NMS 阈值这两个参数在界面里应该做成可调的而不是写死。4.2 PyQt5 集成QThread 信号回传界面不卡死把推理塞进 Qt 主线程的后果是窗口转圈、按钮点不动。 PyQt5 里做实时视觉的标准方案是 QThread 跑推理循环用信号把 QImage 回传主线程刷新界面。核心代码可以抄下面这个骨架。import sys import cv2 import numpy as np from PyQt5.QtCore import QThread, pyqtSignal, Qt from PyQt5.QtGui import QImage, QPixmap from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QPushButton, QVBoxLayout, QWidget from ultralytics import YOLO class InferThread(QThread): frame_ready pyqtSignal(QImage) def __init__(self, model_path: str, cam_id: int 0): super().__init__() self.model_path model_path self.cam_id cam_id self.running True def run(self): model YOLO(self.model_path) # 在子线程里加载不卡主线程 cap cv2.VideoCapture(self.cam_id) # VideoCapture 也在子线程里打开 cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) for r in model.predict(sourcecap, streamTrue, conf0.25, iou0.45, img640, verboseFalse): if not self.running: break img r.plot() # BGR rgb cv2.cvtColor(img, cv2.COLOR_BGR2RGB) rgb np.ascontiguousarray(rgb) # 确保内存连续QImage 才能正确解析 h, w, ch rgb.shape qimg QImage(rgb.data, w, h, ch * w, QImage.Format_RGB888) self.frame_ready.emit(qimg.copy()) # copy 很重要防止缓冲区复用 cap.release() def stop(self): self.running False主窗口只负责显示信号里带来的图片不做任何推理class MainWindow(QMainWindow): def __init__(self): super().__init__() self.label QLabel(摄像头画面显示在这里) self.btn_start QPushButton(开始识别) self.btn_stop QPushButton(停止识别) layout QVBoxLayout() layout.addWidget(self.label) layout.addWidget(self.btn_start) layout.addWidget(self.btn_stop) container QWidget() container.setLayout(layout) self.setCentralWidget(container) self.thread InferThread(best.pt) self.thread.frame_ready.connect(self.show_frame) self.btn_start.clicked.connect(self.thread.start) self.btn_stop.clicked.connect(self.thread.stop) def show_frame(self, qimg: QImage): self.label.setPixmap(QPixmap.fromImage(qimg)) def closeEvent(self, event): self.thread.stop() self.thread.wait() event.accept() if __name__ __main__: app QApplication(sys.argv) win MainWindow() win.show() sys.exit(app.exec_())两个细节值钱。第一QImage构造时rgb.data要求数组内存连续np.ascontiguousarray守住了这一步第二emit(qimg.copy())把图像数据复制一份否则 QImage 持有的是同一个缓冲区下一帧推理完上一帧还没绘制的画面会被覆盖界面会出现闪图和撕裂。关闭窗口时thread.stop()只是把标志位置 False还要thread.wait()等子线程真正退出否则程序会报QThread: Destroyed while thread is still running关一次崩一次。4.3 界面参数与数量统计conf、iou、按类别计数界面里至少要有两个可调参数置信度阈值 conf 和 NMS 的 iou。花卉场景容易把相似颜色的背景误判成花瓣conf 我一般默认 0.35 而不是 0.25宁可少框几个也不要满屏错框。重叠比较多的花丛里 iou 保持 0.45 别往上调调高了会把挤在一起的两朵花并成一朵。统计每类数量是这个界面最有实用价值的功能在子线程里加几行if r.boxes is not None: cls_ids r.boxes.cls.cpu().numpy().astype(int) counts {} for c in set(cls_ids.tolist()): counts[model.names[c]] int((cls_ids c).sum()) print(counts) # 实际项目里把 counts 通过另一个信号发到主线程的 QLabel这套“摄像头读取 → 推理 → 信号回传 → 界面显示”的骨架不只是能用在花卉分割上广告牌分割、医学图像分割里凡是需要桌面端实时预览的项目都是同一个结构把模型换掉就能平移。5. 训练与部署避坑五个让项目翻车的细节5.1 训练阶段类别错位、空标签与 NaN坑一训练日志一切正常loss 在降mAP 也在涨但预测时玫瑰全被识别成月季。现象是“所有类别错一位”或“错成固定类别”。原因是 yaml 的 names 索引和转换脚本里的CLASS_NAMES列表顺序不一致txt 里的 class_id 是转换脚本按它自己的列表写的yaml 却是另一套顺序。解决方式也很机械训练前跑一段小脚本遍历labels/train下所有 txt统计出现过的 class_id 集合再跟 yaml 里的 names 逐条比对。发现了就改 yaml别改标签文件。坑二训练到一半某个 batch 报错或seg_loss突然变成 NaN。常见原因是数据里混进了空标签文件——txt 文件存在但内容是空的或者只有 class_id 没有坐标点。空标签会让模型不知道该学什么梯度直接异常。解决方式是在训练前统一扫描from pathlib import Path for txt in Path(flowers/labels/train).glob(*.txt): lines [ln for ln in txt.read_text(encodingutf-8).splitlines() if ln.strip()] if not lines: print(f空标签: {txt})扫出来的空 txt 先删除或重标再开始训练。顺便看一下有没有坐标数量少于 6 个的实例多边形的点数太少也会让 mask 监督不稳定。坑三验证 mAP 一直卡在很低的位置但训练 loss 看起来很漂亮。这通常是数据本身的问题比如同一个品种的图片在 train 和 val 里高度相似val 虚高或者某个品种的 val 图片里出现了 train 没见过的角度和光照val 偏低。前者是数据泄漏后者是数据覆盖不足。解决方式是训练前用分层划分训练后把 val 按类别分别算 mAP单类别 mAP 低的品种单独补拍、补标。5.2 部署阶段帧率崩、界面卡、显存爆坑四摄像头推理一开始流畅几分钟后帧率掉到个位数内存占用持续上涨。原因是每帧都对图片做了一次独立的model.predict(frame)predict 内部会重新走一遍预处理和结果组装旧对象没释放内存越堆越高。解决方式是模型全局加载一次用model.predict(sourcecap, streamTrue)这种生成器写法让预测器复用内部状态。另外注意摄像头分辨率不要盲目上 1920推理分辨率imgsz和采集分辨率是两个维度采集 1280、推理 640 是最省 CPU 的组合。坑五PyQt 界面点“开始识别”后直接白屏然后 Windows 提示“无响应”。原因就是推理逻辑放在了主线程里model.predict是阻塞调用Qt 的事件循环被卡死。解决方式就是前面写的 QThread 信号回传。还有一个隐蔽坑VideoCapture必须在子线程的run()里创建如果在构造函数里打开再传给子线程不同线程间的资源归属在某些平台会随机出问题表现出来就是偶尔爆cv2.error: ... is not a valid capture。显存爆炸是最后一类高频踩坑。报错是torch.cuda.OutOfMemoryError: CUDA out of memory常见于 batch 设大又开了cacheTrue。解决方式先降 batch从 16 降到 8还不行降到 4同时把cacheFalse。6GB 显存跑 s 档时imgsz 也要从 640 降到 544 或 480。6. 换到自己的花卉数据集三个必须验证的指标项目跑通之后别急着交付。换到自己拍的图集时我会用三个验证点判断模型能不能真正用起来。验证一是 mask 贴合度。挑三种画面单朵大花、花丛重叠、花朵被叶子半遮挡各拿 20 张跑一次预测并保存叠加图。通过标准是单朵花 mask 边缘没有明显锯齿和空洞重叠场景能区分出两朵花的边界半遮挡场景不会把叶子划进花朵 mask。如果重叠场景粘连严重先调高conf和iou到 0.4 / 0.5 再测如果边缘锯齿明显把imgsz从 640 提到 800 重训。验证二是端到端延迟。用下面这段统计“取一帧结果 绘图 显示”的间隔这就是用户实际感知的帧率import time import cv2 from ultralytics import YOLO model YOLO(best.pt) cap cv2.VideoCapture(0) cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) time.sleep(1) # 等摄像头曝光稳定 prev time.perf_counter() for r in model.predict(sourcecap, streamTrue, conf0.35, imgsz640, verboseFalse): now time.perf_counter() print(ffps{1.0 / (now - prev):.1f}) prev now _ r.plot()桌面级实时识别最低要 15 FPS低于这个数先把imgsz降到 480再不行从 s 档降回 n 档。这个取舍要在验证前跟需求方讲清楚实时性和 mask 精细度二选一时摄像头场景优先保实时。验证三是类别稳定性。把模型拿去拍训练时没见过的场景不同光照、手机随手拍、花盆换了个颜色。重点看是否出现同类花在不同背景下忽对忽错。如果新环境下错得离谱不是模型问题是训练集覆盖不够把错样本挑出来补标后做一次增量训练几十张就能见效。我的习惯是验证通过后用模型跑一段 3 分钟的视频存档记录当时的best.pt和参数配置。这样一个月后有人问“为什么当时效果这么好”我能原样复现而不是靠记忆猜测。希望帮到你。本文还有配套的精品资源点击获取

看完文章,想为自己的企业也做一次专业网站诊断?

尧图顾问免费为您评估现有网站,并给出建站/改版建议与报价方案。

免费获取方案