资讯详情 YOLO26实战:电瓶车与自行车两分类目标检测系统全流程
📅 2026/10/17 16:55:39
上个月在做一个园区智能安管的验证项目甲方提的需求很具体电瓶车进电梯要报警而自行车推进去应该被放行。第一次拿现成的通用目标检测模型直接上测试结果电梯口十来秒的视频里模型把推车进电梯的人连人带车一起框住自行车倒是框出来了真正要拦的电瓶车却因为停在暗光角落被漏检。那一刻我就意识到通用模型认识80类日常物体但很多实际场景要的并不是这里有个东西而是这到底是一辆电瓶车还是一辆自行车。后来我把重心从拿来即用转向训练自己的检测器基于YOLO26这个较新的YOLO系列框架专门做了电瓶车与自行车的两分类目标检测系统配套PyQt5桌面界面覆盖数据准备、模型训练、精度评估和部署打包全流程。这篇就是对这个项目从零到一的完整复盘适合正在做目标检测相关毕设、园区安防项目或想系统上手YOLO实战的开发者参考。我会把我踩过的坑、反复验证过的参数和界面工程的细节都写在里面。1. 为什么要单独训练一个电瓶车与自行车检测器1.1 通用模型最大的问题是类别里根本没有电瓶车通用目标检测模型通常是在COCO这类公开数据集上训练的80个类别里只有bicycle(自行车)和motorcycle(摩托车)没有专门对应电瓶车/电动自行车的类别。电瓶车的外观恰好是自行车和摩托车的混合体它有自行车的两轮骨架和脚踏又有摩托车的宽座垫和踏板。模型在特征层面很难把它干净地归到某一类于是表现就很尴尬——有些场景把电瓶车识别成摩托车有些场景直接识别成自行车还有些场景把它和旁边的人一起框成一个人车混合目标。这个问题在真实安防业务里是致命的。物业和园区要拦截的是电瓶车进电梯、进楼道这类行为因为电瓶车电池火灾风险远高于普通自行车但推自行车进出是居民正常生活需求如果模型把自行车误报成电瓶车保安一天要被假警报折腾几十次。业务方需要的是在电瓶车和自行车之间做清晰划分而不是让保安去看画面自己判断。1.2 不同应用场景对检测结果的容忍度完全不同我做了几个典型落地场景的梳理发现同样是两分类检测不同场景对误检率和召回率的要求差异很大应用场景核心需求误报代价漏报代价电梯阻车电瓶车进电梯立刻报警自行车放行保安频繁出警系统被嫌弃电瓶车进电梯安全隐患失守楼道消防通道占用检测识别停放的载客电瓶车需要人工复核浪费人力消防隐患漏管园区停车场统计统计电瓶车/自行车数量与占比统计不准确影响管理决策与误报类似路口/出入口流量分析区分骑行人群类型数据偏差数据不全从表里能看出来电梯阻车场景是要求最高的一类既不能漏报警也不能天天误报。这种情况下拿通用模型凑合着用是不现实的。为这两类目标单独训练一个检测器看起来是多花了一些时间实际上是把模型能发现目标和业务能接受结果这两件事彻底分开后者才是项目能不能交付的关键。所以我给所有想直接抄通用模型来做的朋友一个建议如果业务端要在两三个细分类之间做区分而公开数据集里没有对应类别那不要犹豫数据采集和重新训练的投入一定小于后面无休止的误报处理。2. YOLO26框架选型为什么我最终用了它2.1 我理解中的YOLO26与上一次大版本迭代的差异客观说YOLO系列每一次大版本更迭都不是一夜之间的革命而是在骨干结构、特征融合、训练策略上做了相当务实的调整。我这次使用的YOLO26给我的直观感受有三点第一对结构复杂、轮廓不规整的目标召回更稳。电瓶车这个目标很有意思它不像行人那样有个清晰的人形轮廓也不像小汽车那样是一个规则的矩形块。电瓶车的车把、后视镜、脚踏、后备箱各种凸起加上骑行者腿部与车身的遮挡很容易让检测器的特征提取分散注意力。YOLO26在新版本里加强了对这种尺度变化大、局部特征碎的目标的关注我在自己的数据显示这类目标的漏检比之前用的版本少了一些。第二小目标表现改善明显。监控视频里目标往往离摄像头很远一辆电瓶车在画面里可能只有二三十个像素高尤其是园区出入口这类广角场景。25到26这轮迭代在多尺度特征融合上做了调整对远处小目标的召回有可感知的提升。第三训练和部署生态高度统一。这次用的框架依然沿用YOLO系列经典的训练命令、模型配置和导出流程无论是datasets的yaml配置、训练参数调整还是导出onnx、engine格式都不需要额外学习一套新东西。对做工程的人来说这种零迁移成本本身就是选型理由。2.2 与老版本对比的实测体感我在同一批电瓶车/自行车数据上和之前常用的旧版模型做了对比测试。不敢说精确的基准数据只能说在完全一致的数据集和参数下体感差别是明显的对比维度之前常用的YOLO系列版本YOLO26我的主观评价电瓶车漏检率暗光场景较高暗角易丢明显下降更换后的第一周就感觉到了自行车与电瓶车混淆两类混淆时有发生混淆减少业务上最看重的指标小目标检测远距离一般更好广角画面更实用推理速度快同级别相差不大基本不影响帧率部署生态同一套生态同一套生态流程零学习成本当然我的对比不是严格控制的学术实验只能说在同一个项目、同一批数据、同样的业务容忍度下YOLO26的综合表现更符合我的预期。如果你手头有自己熟悉的版本不需要盲目追新但如果你想选一个当前状态下省心的框架它确实值得试。2.3 模型型号怎么选YOLO系列的n、s、m、l、x五个型号本质是在推理速度和检测精度之间做取舍我按实际落地场景给的选型建议是YOLO26n模型最小CPU上勉强可跑适合边缘盒子或者对帧率要求不高的场景精度会掉一些。YOLO26s我用的是这个。精度和速度最均衡6G以上显存的GPU就可以训练和推理是大多数项目的稳妥起点。YOLO26m显存充足、对精度有更高要求时选需要8G以上显存会更舒服。YOLO26l / x追求极致精度但它俩更适合服务端离线分析不适合直接部署在普通电脑上做实时检测。我的建议是除非你的硬件条件真的很受限否则第一次训练从s型号入手跑通全流程之后再根据实际帧率和精度决定要不要换更大或更小的模型。3. 环境搭建与数据集构建训练前最容易翻车的两道坎3.1 环境版本对照与安装步骤整个项目的第一步是搭环境。很多人在这一步就卡住大部分原因是Python版本、PyTorch版本和CUDA版本对不上。我这次用的是下面这套组合供参考组件推荐版本说明Python3.10太老的3.7、3.8对新版PyTorch支持不好PyTorch2.x训练效率比1.x有明显提升CUDA Toolkit11.8 或 12.1取决于你显卡驱动支持哪个开源YOLO训练库最新稳定版pip直接安装即可显卡驱动尽量更新老驱动经常是环境问题的根源创建独立虚拟环境是必须做的我吃过大亏——直接在系统Python里装后面某个依赖库升级把另一个项目搞挂排查了半天。建议这样操作conda create -n yolo26 python3.10 conda activate yolo26 pip install ultralytics pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118安装完之后先用一张图片快速验证环境是否正常yolo predict modelyolo26s.pt sourcetest.jpg如果这条命令能正常输出检测结果说明训练库、模型加载和基础依赖都通了。这一步很值得花两分钟能帮你把环境问题和项目代码问题分开后面排查会省很多力气。3.2 数据从哪来采集、抽取与标注规范数据是这类检测项目的命脉我用的是三分法一是自己实地采集。拿着手机或运动相机在园区出入口、电梯间、停车场拍不同角度、不同时段的视频然后按帧抽取图片。粗抽之后再用脚本按间隔去重比如每5帧保留一帧避免连续帧高度相似导致训练集冗余。二是公开数据集里找自行车类别。像COCO这类公开数据集里有大量bicycle标注可以抽取出来作为自行车的底料但要注意它的拍摄视角偏生活化和监控视角差异较大只能作为补充。三是少量从网络上合法获取公开图片。天气、光照、角度都尽量多样化。关于数据量我建议每类至少1000张以上两类样本量不要相差3倍以上否则模型会天然偏向样本多的那一类。如果现场条件只能采集500张就靠数据增强翻转、亮度变化、随机裁剪来凑但增强只是补救真实场景的多样性才是模型泛化能力的关键。标注环节我重点提醒几个规范类别定义要提前写死。我这次定义的是e-bike电瓶车包含外卖踏板车、电动自行车、bicycle自行车包含共享单车、普通自行车。三轮电瓶车和儿童滑板车一开始就约定不算避免标注员凭感觉乱标。遮挡目标要标哪怕只露出一半车身也要标不能只标完整目标。模型部署后遇到最多恰恰是遮挡场景。模糊目标可以舍弃。画面里已经完全看不清轮廓的目标强行标上去只会给模型送噪声。同一个目标在连续帧中如果看起来变化不大只标一帧即可让标注资源花在更多样化的画面上。标注完成后的目录结构长这样ebike_bicycle_data/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/每一张jpg图片对应一个同名txt文件txt里每一行是类别id x_center y_center width height坐标是相对于图片宽高的归一化值。3.3 数据集YAML配置文件的写法训练依赖一个YAML文件把数据路径和类别映射关系告诉框架。我的配置文件是这样写的path: /home/user/ebike_bicycle_data # 数据集根目录改成你自己的路径 train: images/train val: images/val test: images/test nc: 2 names: 0: e-bike 1: bicycle这里有两个容易出错的地方。第一个是path字段如果用相对路径框架会从当前工作目录去拼接一旦你换了终端启动位置就会找不到数据。所以我都是写绝对路径。第二个是names的顺序0和1的顺序直接对应训练出的模型输出类别顺序如果数据和配置不一致训练时损失会异常升高但框架不会主动提醒你只能靠人工反复核对。4. 训练全流程从命令参数到看懂日志曲线4.1 训练指令与关键参数选择数据准备好之后训练本身其实是整个项目里最省心的部分。我的训练命令如下yolo train \ modelyolo26s.pt \ dataebike_bicycle.yaml \ epochs200 \ imgsz640 \ batch16 \ patience30 \ device0 \ cacheTrue几个参数我展开说一下这比照抄命令更重要modelyolo26s.pt用预训练权重做迁移学习起点。不要一上来就从yolo26s.yaml这种随机初始化权重开始训电瓶车和自行车虽然是新类别但底层特征轮子、车架、把手、路面和预训练模型见过的场景高度相关迁移学习能让收敛速度成倍提升。imgsz640YOLO系列默认输入尺寸。如果画面里目标普遍较小可以试试imgsz960但训练和推理都会变慢别盲目加大。batch16受显存限制。6G显存跑s模型时batch16是安全的。如果显存不够减小batch不要减少imgsz。patience30早停耐心值。如果验证集损失连续30轮不下降训练自动结束。这个参数帮我省了很多时间尤其后期调参时不用干等200轮跑完。cacheTrue把数据集缓存到内存里能显著加快每个epoch的数据加载。磁盘够大就开着前提是内存别太小。4.2 训练日志怎么读别只盯着Loss训练启动后终端每轮都会输出一行日志包含box_loss、cls_loss、dfl_loss、precision、recall、mAP50、mAP50-95这些指标。新手最容易犯的错误是只看loss降了就觉得万事大吉。我的读日志习惯是第一看mAP50和mAP50-95。mAP50是IoU阈值0.5下的平均精度业务场景往往更关心它mAP50-95更严格对定位精度要求高是学术上常用的指标。如果mAP50已经到0.9x而mAP50-95还在0.5左右说明模型大致找到了目标但框的位置不够准。第二看precision和recall的平衡。电瓶车检测场景我宁可精确率稍微低一点也要保障召回率因为漏掉一辆电瓶车进电梯的后果比误报一次严重得多。训练结束后可以根据业务倾向通过调整置信度阈值来平衡两者。第三看train loss和val loss的剪刀差。如果train loss一直降、val loss却开始反弹基本就是过拟合了。这时候回看patience是否有触发早停若没有就说明数据多样性还不够需要补数据而不是盲目加训练轮数。训练过程中框架会在runs/detect/train目录下自动生成一系列图表包括results.png损失和精度曲线、confusion_matrix.png混淆矩阵、PR_curve.pngPR曲线等。训练结束后我建议先看混淆矩阵它比mAP数字更能直观暴露电瓶车被识别成自行车这类业务不可接受的问题。4.3 评估模型不能只看平均值训练完成后用一套训练时没见过的独立测试集做最终评估命令很简单yolo val modelruns/detect/train/weights/best.pt dataebike_bicycle.yaml但评估的重点在于分场景拆开看。我会把测试图片按白天、夜间、逆光、遮挡四类分组分别统计识别效果而不是只看一个总mAP。很多项目总mAP很漂亮一放到夜间场景就露馅原因就是测试集里白天图片占了大头。这个习惯帮我提前暴露了不少问题建议你也按自己的部署场景把测试集分层。我这次自建验证集上mAP50整体在0.93左右但夜间和逆光子集的mAP50会掉到0.85附近说明后续真正要攻坚的是低光照条件下的特征表达这也直接决定了部署阶段的优化方向。5. PyQt5界面开发把模型变成能点着用的软件5.1 界面布局与功能设计模型训练完还只是一堆权重文件业务方要的是一个能双击运行、能开摄像头、能出统计结果的软件。PyQt5是这个环节最稳的选择跨平台、控件丰富、打包方案成熟。我的界面布局比较朴素但够用界面区域功能顶部工具栏打开图片、打开文件夹、打开视频、打开摄像头、停止检测、导出结果左侧主显示区实时显示原始画面和检测框叠加结果右侧信息面板当前画面各类目标数量、检测耗时、帧率底部状态栏当前模型路径、推理状态提示功能设计上最核心的一条原则是所有耗时的操作都不能卡界面。打开一个大视频文件、运行模型推理、批量处理文件夹任何一个操作如果直接放在主线程鼠标都会转圈转到怀疑人生。5.2 多线程推理不让界面卡死的核心设计PyQt5的界面是基于事件循环的如果主线程里跑一个耗时操作界面会直接无响应。摄像头实时检测场景尤其明显——一帧推理少说几十毫秒多则几百毫秒放在主线程里画面就变成幻灯片。我的做法是单独写一个QThread子类把采集和推理全部放进工作线程只通过信号把结果传回主线程更新界面。核心骨架如下import cv2 from PyQt5.QtCore import QThread, pyqtSignal from PyQt5.QtGui import QImage class DetectThread(QThread): frame_ready pyqtSignal(QImage) result_ready pyqtSignal(dict) def __init__(self, model_path, source0): super().__init__() self.model YOLO(model_path) self.source source self.running True def run(self): cap cv2.VideoCapture(self.source) while self.running: ret, frame cap.read() if not ret: break results self.model(frame)[0] # 在frame上绘制检测框 boxes results.boxes class_counts {} for box in boxes: x1, y1, x2, y2 box.xyxy[0].int().tolist() cls_id int(box.cls[0]) conf float(box.conf[0]) label f{self.model.names[cls_id]} {conf:.2f} cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(frame, label, (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) class_counts[cls_id] class_counts.get(cls_id, 0) 1 # OpenCV的BGR图像转成QImage rgb_image cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch rgb_image.shape qimg QImage(rgb_image.data, w, h, ch * w, QImage.Format_RGB888) self.frame_ready.emit(qimg) self.result_ready.emit({ e-bike: class_counts.get(0, 0), bicycle: class_counts.get(1, 0), }) cap.release() def stop(self): self.running False self.wait()这个线程类的关键点有三个_model在run之前加载如果放到QThread构造函数里加载窗口起来会卡很久放在run开头一般加载两三秒至少界面已经能显示了。stop方法必须配合running标志点击停止检测时先置running为False再wait()等待线程真正退出避免关闭软件时后台线程还在跑导致程序闪退。QImage和OpenCV的像素格式转换OpenCV读取是BGR顺序直接显示会让画面偏蓝必须先转成RGB。5.3 主窗口的调用逻辑主窗口这边的工作就是监听按钮事件、启动线程、接收信号刷新界面。核心逻辑大概是class MainWindow(QMainWindow): def __init__(self): super().__init__() self.init_ui() self.detect_thread None def open_camera(self): if self.detect_thread is not None: return self.detect_thread DetectThread(best.pt, source0) self.detect_thread.frame_ready.connect(self.update_frame) self.detect_thread.result_ready.connect(self.update_stats) self.detect_thread.start() def update_frame(self, qimg): self.video_label.setPixmap(QPixmap.fromImage(qimg)) def update_stats(self, stats): self.ebike_label.setText(f电瓶车: {stats[e-bike]}) self.bicycle_label.setText(f自行车: {stats[bicycle]})这里我踩过的一个坑重复点击打开摄像头按钮会new出多个线程同时访问摄像头导致摄像头资源冲突。解决方式就是加一个线程存活判断如果已有线程在跑就直接忽略新的请求。另外切换图片检测和摄像头检测时一定要先停掉旧线程再启动新线程。6. 部署与落地模型导出和EXE打包全流程6.1 模型导出pt、onnx、engine怎么选训练得到的是best.pt权重程序开发阶段直接用PyTorch框架加载没问题但到了部署阶段要考虑运行效率和平台兼容性。我整理了一下pt格式最灵活二次训练方便但必须依赖PyTorch环境环境体积大。onnx格式跨平台最好CPU上和GPU上都能跑适合绝大多数业务部署甚至可以直接接到边缘设备。engine格式NVIDIA的TensorRT专用格式推理速度最快但只在有NVIDIA显卡的环境下能用。导出命令很简单yolo export modelbest.pt formatonnx dynamicTrue yolo export modelbest.pt formatengine device0导出onnx时我建议加上dynamicTrue这样输入图像的尺寸不受640x640限制界面里处理任意分辨率画面更灵活。导出完成后一定要用一张真实的测试图片验证一下from ultralytics import YOLO model YOLO(best.onnx) results model(test.jpg)注意导出后模型的检测结果理论上应该和pt几乎一致如果发现精度明显下降先怀疑是不是数据集里目标太小量化或输入尺寸变化导致的这时需要回看训练参数而不是继续强行部署。6.2 PyInstaller打包EXE的完整流程与暗坑界面和模型都整理好之后最后一步是打包成可执行的EXE。我用的是PyInstaller命令如下pyinstaller -w -F main.py \ --add-data best.pt;models \ --hidden-import PyQt5.sip \ --name EbikeDetector几个参数逐一说明-w打包后的程序不显示命令行窗口。-F打包成单个exe文件方便分发但启动时会先把资源解压到临时目录启动速度略慢。--add-data把模型文件打进包里分号前面是源文件路径分号后面是包内目标路径。Windows下分隔符是分号Linux下是冒号这个细节常让人困惑。--hidden-importPyQt5有时会有一些动态导入的模块比如sip不显式声明的话打包后运行时找不到。打包好后有四个高频问题几乎每个项目都会遇到一是资源路径找不到。打包后的exe解压到临时目录你的best.pt不在程序当前目录而在临时目录里。正确的取资源方式是import sys import os def resource_path(relative_path): if hasattr(sys, _MEIPASS): return os.path.join(sys._MEIPASS, relative_path) return os.path.join(os.path.abspath(.), relative_path) model_path resource_path(models/best.pt)二是exe体积巨大。PyTorch和CUDA运行时本身就大打包出来动辄四五百MB甚至更离谱这是正常的不是编译坏了。如果实在在意体积就把模型导出成onnx并用轻量推理后端能压掉一部分但不会根本改变。三是杀毒软件误报。PyInstaller打的exe偶尔会被某些杀毒软件标记尤其带-F参数的单文件版。遇到这种情况只能换电脑管家类软件加白名单或者换用不带-F的目录模式打包误报率会低一些。四是目标电脑缺运行库。PyQt5和OpenCV依赖一些Visual C运行库在没有安装过这些库的机器上会报找不到DLL。解决办法是在目标机器上装一次对应版本的运行库或者用目录模式打包并把必要DLL一起带上。7. 实测效果复盘白天、夜间、逆光和遮挡下的真实表现7.1 分场景实测结果整个系统跑通后我拿真实场景的视频做了分场景测试不只看总体mAP而是把表现拆到业务关心的维度上测试场景表现问题白天园区出入口稳定识别两类目标框位贴合偶尔把行人手中拖的小拖车框进来夜间楼道口能识别但置信度整体偏低自行车与电瓶车轮廓相似偶发混淆强逆光时段有明显漏检目标整体发暗特征丢失严重多目标重叠进出大多数目标能分开两车并肩时偶尔漏框其中一辆从这张表能看到白天场景其实早就没问题了真正影响交付的是夜间和逆光这两个子集。这也验证了我前面的判断——白天样本充足的情况下模型学得很好一旦光照条件变化样本覆盖不足的问题立刻暴露。针对夜间场景我的处理思路不是换模型而是先补数据。我在夜间加拍了一批红外模式下的画面做一个小的二次训练在已有best.pt基础上继续微调夜间子集的效果明显回升。这说明数据多样性的价值比调参高太多。7.2 部署之后还能做哪些优化模型检测能力稳定之后系统还可以往两个方向延伸一是加上目标跟踪。单帧检测只能告诉你当前画面里有什么不能告诉你这辆车是不是刚才那辆、它在画面里待了多久。接一个轻量级跟踪算法比如ByteTrack或DeepSORT思路的简化版就可以统计同一辆电瓶车连续出现在画面里的帧数从而判断它是不是停在楼道里而不是路过的车。电梯阻车场景对停留时间的判定非常重要。二是把模型量化后部署到边缘设备。onnx模型在普通电脑上做实时检测已经够用但真正要装到电梯轿厢、楼道口这种地方还是需要一个小算力盒子。把模型int8量化后部署到嵌入式NPU平台上功耗和体积都更可控。这一步工程量不小但对于真正落地的项目几乎不可避免。实操过程中的几点个人体会做完这个项目我的最大体感是真正消耗时间精力的不是训练模型这一下动作而是数据完整性和工程化这两块。模型训练本身在YOLO系列的开源生态下越来越像填表跑命令谁都能训出一个mAP可观的模型但能不能把数据整理到足够贴近真实场景能不能把多线程界面做好不卡顿能不能把模型打包分发到不会用的用户手里这些才是决定项目能不能真的交付的坎。如果你们也准备做类似的目标检测系统我会建议把一半以上的精力放在现场数据采集和标注规范上这个投入的回报远大于后面疯狂调参。至于界面和打包一句话总结就是所有耗时操作都扔进线程所有资源路径都用临时目录兼容这两个点处理好了项目就已经成功了一大半。