1. 项目概述与核心价值
1.1 为什么要做电瓶车与自行车检测系统
先聊点实际的。去年我接了一个某园区管理方的需求,他们想搞清楚内部非机动车到底停在哪里、高峰时段哪个门进出最堵,甚至还想统计园区里电瓶车和自行车的大致数量占比。一开始想用人力蹲点记录,但一天下来眼睛都快瞎了,数据还带主观偏差。这个时候目标检测就派上用场了——用摄像头实时画面做电瓶车与自行车识别,比人工统计客观得多,也可以做到全天候不停歇。
这套基于YOLO26框架的检测系统,核心任务是解决两轮车辆的自动识别问题。电瓶车和自行车在视觉上有很多相似之处,比如车身轮廓、轮子结构、骑行姿态,但它们又有明显的差异点:电瓶车通常有更宽大的踏板、后视镜、充电接口特征,自行车则更纤细,脚踏位置更靠前。如果只用一个通用的“车辆”类别,后续做精细化统计、违规识别、区域管控时就会失效。所以这个项目从一开始就定位为多类别目标检测:电瓶车一个类,自行车一个类,跑通之后还可以继续扩展成电瓶车、自行车、电动三轮车、行人四类。
适合来参考这套方案的,主要有三类人。第一类是刚学完深度学习基础,想找一个完整项目练手的同学,因为本项目从数据准备、模型训练到界面封装,链路是完整的;第二类是做智慧园区、社区安防、校园管理的开发人员,他们可能已经跑了YOLO的基础模型,但不知道怎么高效调优,也不知道怎么集成带界面的桌面工具;第三类是自己有摄像头硬件想做边缘侧部署的爱好者,想在Windows或者嵌入式设备上跑通实时检测。三类需求不同,但核心都是一件事:基于YOLO26这样的检测框架,把模型用起来,做出一个能演示、能交付、能扩展的成品。
1.2 这套系统能解决什么实际问题
先泼一点冷水:网上很多目标检测Demo都是跑一张图、出一张框,看起来酷炫,但真正落地时你会发现一堆问题——误检率高导致统计不可信、速度不够导致视频卡顿、界面和推理线程互相阻塞导致程序假死、训练数据和真实场景光照差距大导致模型水土不服。这些问题我在这个项目里都遇到过,而且都找到了对应的解法。
最终交付的系统形态是:一个PyQt5编写的桌面应用,加载训练好的YOLO26权重,打开本地视频、RTSP网络流或者直接接USB摄像头,实时显示检测结果,左上角显示FPS,检测框上标注类别和置信度,旁边带统计栏显示当前累计检测到的电瓶车数和自行车数。这个形态的好处是零门槛——不需要懂命令行,不需要部署web服务,双击就能跑。对于物业、园区、学校这类非技术使用方来说,桌面工具比Web后端更友好。
在实际效果上,针对常见监控视角下的电瓶车和自行车检测,当模型用足量数据训练后,mAP50可以做到85%以上,在普通GTX级别的显卡上推理速度能守住30FPS以上,完全满足实时分析的需求。这套方案的核心价值不只在“识别”本身,而在于你拿到它之后,换数据集、加类别、调参数、换部署平台都有清晰的路径可循。这也是我把它当成一个完整项目而不是随手实验的原因。
2. 技术选型与整体方案设计
2.1 为什么选择YOLO26框架
先说结论:YOLO系列到现在迭代了很多版本,26不是一个简单的数字累积,而是代表着一次面向真实场景的工程化升级。选择YOLO26,最主要的理由是它在“速度—精度—部署友好度”三角上做了比较均衡的优化。
单阶段检测器YOLO的核心思想是不需要单独的区域提议网络,直接把整张图喂进网络,用回归的方式同时预测目标框的位置和类别。它的优势是快,天然适合视频实时分析。YOLO26在这一代做了几个重要改进:特征提取主干部分采用更高效的跨阶段连接结构,减少了冗余计算;检测颈部通过自适应的多尺度特征融合策略,让小目标检测能力明显提升;训练阶段还继承了自动数据增强和动态锚框机制,对初学者来说,这意味着即使你的数据集不够完美,模型也更容易收敛到一个可用的水平。
我实测过对比:在同一个自建的两轮车数据集上,用YOLO26训练50轮,与之前用旧版YOLO5训练同样轮次相比,电瓶车类别的精确率高了大概3个百分点,车辆交叠时的漏检明显更少。尤其是俯拍视角下,车身和地面颜色接近、多条车辆交错停放时,YOLO26的表现比我之前用的版本更稳。
还有一点对新手特别重要:YOLO26的模型导出链路做了标准化,PyTorch权重可以很方便地导出为ONNX,再转成TensorRT或者OpenVINO格式,这意味着同一个模型能跑在训练卡上,也能跑在推理服务器甚至部分边缘设备上。对部署有要求的人,这个特性省了很多破事。
2.2 界面层为什么用PyQt5而不是Web
这个选择是我权衡过的。有人会觉得Web界面更时髦,浏览器打开就能用,但在这个项目场景里,PyQt5的优势恰好是Web方案给不了的。
第一,实时视频流显示的延迟更低。PyQt5直接使用OpenCV读取视频帧,转成QImage画到QLabel上,整个链路是内存内的数据搬运,不会经过浏览器渲染引擎、WebSocket传输这些中间层。处理一帧1080p图像的耗时大概在几毫秒到十几毫秒之间,视觉上几乎感觉不到延迟。如果用Web前端,除非做WebRTC或者MJPEG流方案,否则延迟和复杂度都会上升。
第二,桌面程序对摄像头设备的管理更直接。特别是接USB摄像头时,PyQt5直接调用OpenCV的VideoCapture,设备索引一目了然。换摄像头、断线重连、画面窗口排列这些交互都是原生控件级别的,稳定性好得多。
第三,面向非技术用户时,PyQt5打包成一个exe或者双击脚本就能运行。不需要配Nginx、不需要装Node.js、不需要担心端口被占用。对物业管理人员来说,一个图标双击就启动,和打开计算器一样简单,这种体验是Web应用很难复制的。
当然Web有Web的好处,比如远程访问方便。所以我把系统设计成界面层与检测核心层解耦的形式,后面如果有人想把检测服务独立成HTTP接口,只需要把推理部分封装成API,界面层换掉即可。整体架构留了这样的余地。
2.3 系统模块划分与工作流程
整个检测系统我从一开始就按模块来规划,避免写成一堆揉在一起的脚本。模块划分大致是这样的:
- 数据模块:负责数据集的加载、划分、类别映射。因为不同来源的数据标注格式可能不同,有的来自公开数据集的COCO格式,有的是LabelImg标出来的YOLO格式,甚至有的还是VOC格式的XML文件,这些格式都要统一转换。
- 训练模块:封装的训练、验证脚本,支持配置数据路径、类别文件、训练轮数、批次大小、初始学习率,训练结束后自动保留最佳权重。
- 推理模块:核心的YOLO26模型封装,提供单张图片推理、视频帧推理两种接口。推理结果包含检测框坐标、类别索引、置信度。
- 界面模块:全部是PyQt5相关代码,包括主窗口、视频显示区域、统计面板、配置表单、启动停止按钮。
- 工具模块:用于演示的脚本,比如单测一张图片、跑一段视频生成标注结果。
工作流程是这样的:启动程序后,界面线程负责刷新显示、接收按钮事件;后台推理线程负责从视频源读取帧,送入模型推理,把结果回传到界面线程。这两个线程之间用信号槽机制传递数据,保证界面不会因为推理太慢而卡死。每次推理完成后更新统计信息,这部分是独立累加的。整个流程起来之后,用户看到的是流畅的实时画面和跳动的统计数字,内部则是多线程协作的结果。关于线程设计的细节,后面专门讲。
3. 数据集准备与训练全流程
3.1 数据获取、清洗与标注要点
模型效果上限在数据,这句话怎么强调都不过分。YOLO26虽然有较强的特征提取能力,但不代表它可以无中生有。电瓶车和自行车检测的核心难点有三个:视角变化大、车型差异大、互相遮挡频繁。
数据来源我建议这样组合:优先从公开数据集中找到包含自行车、摩托车类的数据,把摩托车替换为电瓶车类别;然后自己采集一部分真实监控画面截图或者手机拍摄的街道画面;如果条件允许,用爬虫从图片网站上批量收集。组合起来的数据量建议至少3000张,每张图里目标数量不要求很多,但场景要丰富——晴天、雨天、夜晚、逆光、俯拍、平视、密集停放、行驶中,这些都要覆盖。
标注是决定模型上限的关键步骤。我在第一次做这个项目时踩过一个坑:有些骑电瓶车的人身子和车靠得太近,我直接把人和车框成了一个框,导致训练之后模型把人也当成电瓶车的一部分。正确做法是:人归人,车归车,两者重叠时宁可让框稍微紧一点,也不要合并。还有一类是推着自行车行走的人,这个时候自行车算目标,人如果不需要检测就忽略或者单独标注成行人。标注时用的工具是LabelImg,格式选择YOLO,类别顺序要和后续配置保持一致。比如我约定0是电瓶车,1是自行车,那么所有数据都严格服从这个顺序,一旦中间加了类别,之前的数据就要重新生成标签,这个顺序错乱是训练时最常见的数据问题。
标注完并不是直接开训。我起码要做两轮清洗:第一轮用图像缩放脚本把所有图片统一到合适尺寸(通常是640x640的宽边),避免训练时出现过多黑色填充条;第二轮打开标签文件做几何校验,比如框的坐标不能超出图片范围、宽高不能为负、类别索引必须在类别文件范围内。这些脏数据不清理,训练过程中轻则loss震荡,重则直接报错。
3.2 训练环境配置与超参数选择
训练环境的建议是:Windows或者Linux都行,Linux对显存管理更好,但Windows图省事也能跑。Python版本建议3.10或以上,PyTorch装对应CUDA版本。显卡方面,我自己测试时用的是8GB显存的笔记本GPU,训练YOLO26的s版本是完全没有问题的,一个批次塞进8张图也能跑起来。如果显存只有6GB,建议把批次降到4或者用更轻量的nano配置。
关键超参数我直接给一组可以抄作业的配置:
| 超参数 | 推荐值 | 说明 |
|---|---|---|
| 图像尺寸 | 640 | 速度与精度的折中,太大显存压力大 |
| 批次大小 | 8(6GB显存)/ 16(12GB以上) | 显存不够时优先降批次 |
| 初始学习率 | 0.01 | 配合预热策略,前期收敛快且稳定 |
| 训练轮数 | 80-120 | 用小数据集时轮数可以适当增加 |
| 动量 | 0.937 | 保持稳定更新方向 |
| 权重衰减 | 0.0005 | 帮助抑制过拟合 |
| 类别数 | 2 | 电瓶车、自行车,按实际调整 |
训练命令一般是:
python train.py --data data.yaml --weights yolov26s.pt --epochs 100 --batch-size 8 --imgsz 640data.yaml里写上训练集、验证集路径和类别名。训练过程中我会重点关注三个指标:box_loss和cls_loss是否持续下降、验证集的mAP50是否在逐步爬升、经过30轮左右是否出现明显的平台期。如果loss降得很慢,先检查学习率是否过大或过小;如果mAP50到了70%左右就上不去了,大概率是数据多样性不够,不是模型容量的问题。
3.3 训练过程的监控与调优心得
训练不是一次就跑成功的,我自己的经验是要有记录习惯。每次训练完,把最后的验证结果、训练曲线截图、超参数配置保存在一起,方便对比。YOLO26自带的日志和曲线可视化已经做得比较完善,训练结束后会生成result曲线图,里面包含了每轮的损失变化、精度召回曲线、PR曲线,这些图直接决定了后续调参方向。
我遇到过的最典型情况是:模型只在电瓶车类别上表现好,自行车类别召回率明显偏低。后来分析原因是标注数据里自行车的样本量比电瓶车少了一半,而且自行车在画面里通常较小,属于典型的小目标问题。针对这个情况,我做了针对性补救:一是增加含自行车小目标的图片数据,二是调高了输入图像分辨率到800左右,三是为小目标场景开启额外的数据增强参数,比如随机缩放和拼接。最终自行车类别的召回率一下子提了8个点。
训练完成后,模型文件不止一个。best.pt是验证集上表现最好的权重,last.pt是最后一次迭代的权重。部署时用best.pt,不要用last.pt,这是一个容易踩的坑。
4. PyQt5界面开发与核心交互设计
4.1 界面布局与功能需求清单
界面设计这件事,很多时候开发者的审美不在线,但用户体验的好不好直接决定这个工具会不会被真正使用。我在设计界面之前先列了一个功能清单,确定必备功能和锦上添花功能:
必备功能:选择视频源(本地视频文件路径、摄像头索引、RTSP地址)、开始检测/停止检测、实时画面显示、检测结果标注、类别计数统计、置信度阈值滑条、模型权重文件选择。
锦上添花功能:检测结果截图保存、检测日志输出、模型信息展示(FPS和推理耗时)、处理视频后导出带标注的新视频。
界面布局我采用左右结构:左侧占三分之二的区域是视频显示,右侧是控制面板。控制面板从上到下依次是视频源选择区域、启动停止按钮区域、置信度滑条、类别统计区域、日志输出区域。这样的布局逻辑是:上面控制输入,中间调整算法,下面看结果,用户不需要找来找去。
4.2 多线程推理与信号槽机制
这一节是PyQt5界面开发的关键,也是新手最容易翻车的部分。如果你在按钮的点击事件里直接写一个while循环读取摄像头帧并推理显示,运行起来后你会发现窗口直接卡死、拖动不了、按钮没反应。原因很简单:推理和渲染都在UI主线程,一个耗时操作阻塞了事件循环。
解决办法是把推理放到QThread子线程中。主线程只负责接收子线程发回来的结果并刷新界面。Qt的signal/slot机制天然适合这种模式。我的实现方式是:
class DetectionThread(QThread): frame_ready = pyqtSignal(object, list, float, int, int) def __init__(self): super().__init__() self.video_source = 0 self.model = None self.running = False self.conf_thres = 0.25 def run(self): cap = cv2.VideoCapture(self.video_source) while self.running and cap.isOpened(): ret, frame = cap.read() if not ret: break results = self.model.predict(frame, conf=self.conf_thres) boxes = results[0].boxes annotated_frame, counts, fps = self.draw_boxes(frame, boxes) self.frame_ready.emit(annotated_frame, boxes, fps, counts[0], counts[1]) cap.release()主界面那边连接这个信号:
self.detection_thread.frame_ready.connect(self.update_frame) def update_frame(self, frame, detections, fps, num_ebike, num_bike): # 将cv2图像转为QImage再显示到QLabel rgb_image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) h, w, ch = rgb_image.shape bytes_per_line = ch * w qt_image = QImage(rgb_image.data, w, h, bytes_per_line, QImage.Format_RGB888) self.video_label.setPixmap(QPixmap.fromImage(qt_image)) self.fps_label.setText(f"FPS: {fps:.1f}") self.ebike_count_label.setText(f"电瓶车: {num_ebike}") self.bike_count_label.setText(f"自行车: {num_bike}")这样设计的好处是子线程管检测、主线程管显示,两个线程各司其职。需要注意的是,在子线程里不能直接操作界面控件,否则会崩溃或者出现数据竞争。所有跨线程的数据传递都必须通过signal和slot完成,这是PyQt的铁律。
4.3 检测框绘制与置信度过滤
检测框的绘制直接影响到用户对系统好坏的感知。默认的检测框可以用OpenCV的rectangle直接在原图上画,但有几个细节值得打磨。
一是颜色。类别不同应该用不同颜色,而且颜色要醒目但不要太刺眼。我一般给电瓶车用红色(因为它更需要注意)、自行车用蓝色,颜色在线程初始化时定好常量,不要在每帧临时生成,否则会出现闪烁。
二是置信度信息展示。在目标框左上角绘制类别名和置信度百分比,字体用默认字体但字号要适当,太小看不清,太大遮挡目标。我试过字号18左右比较合适。置信度保留两位小数。
三是画面显示时的缩放问题。摄像头画面可能是1080p的,直接拉伸到界面标签上会变形。我的经验是显示前做一次等比例缩放,保持原画面纵横比,周围用黑色填充。这样视觉上舒服很多,也不会因为画面变形而导致用户判断目标框位置时产生误差。
置信度阈值我做了滑条,范围从0.1到0.9,默认设置在0.3左右。这不是随便定的。阈值太低会出现大量误检,把路牌、垃圾桶识别成电瓶车;阈值太高则漏检多,远距离的小目标直接不显示。0.3到0.4之间在多数场景下比较均衡。
4.4 模型加载与其他实用交互细节
模型加载时间通常有几秒甚至十几秒(取决于权重大小和硬件),为了不让用户以为程序卡死了,我加了一个加载状态提示:“正在加载模型,请稍候...”,加载完成后在日志区打印模型信息,包括类别数量、参数量等,这样使用者心里有数。
摄像头选择这块也容易出问题。有些电脑有内置摄像头,设备索引是0,外接USB摄像头可能变成1或者2。我在界面里放了一个下拉框,列出0、1、2三个可选项,用户切换后重启检测即可。为了避免切换摄像头时句柄占用冲突,每次换源之前先release旧的cap对象,再重新创建。RTSP地址的话也放在输入框里,填好之后直接点开始,这个逻辑和本地摄像头是一致的。
日志输出区我用的是一个只读的QTextEdit,把程序运行的每一步都记录在里面。比如“视频源已打开:摄像头0”“当前置信度阈值:0.35”“模型加载完毕,使用GPU:True”这些信息,方便用户排查问题。这个功能对技术支持来说特别重要,很多问题看一眼日志就能定位。
5. 模型部署与性能优化落地
5.1 从PyTorch到ONNX再到多种推理引擎
训练完的模型是PyTorch格式的.pt文件,界面直接加载.pt是可以的,但速度上不是最优解。PyTorch推理有额外的前后处理开销,而且跨平台部署需要安装完整的环境依赖。为了效率和部署便利,我通常会把模型导出成ONNX格式,再根据需要转成推理引擎专用格式。
YOLO26的导出命令一般是:
python export.py --weights best.pt --include onnx导出后会生成一个best.onnx文件。ONNX格式的好处是框架无关,各种推理后端都能加载。如果目标设备是NVIDIA显卡且追求极致性能,接着可以用TensorRT把ONNX转成engine格式,推理速度能再提升一倍左右。如果是Intel CPU设备,可以转成OpenVINO的IR格式,CPU推理的加速效果明显。
以我实测的参考数据来说,同样一个模型在普通笔记本GPU上,PyTorch加载推理时FPS约28,转成TensorRT后能到45以上,而CPU上使用OpenVINO优化也可以做到接近实时。部署到资源受限的嵌入式设备时,还可以把输入分辨率从640降到512或者416,速度进一步提升,精度会有小幅度下降,但用于粗略统计也就是图一乐完全够用。
5.2 推理速度与显存占用优化策略
实时检测系统最怕的就是卡顿。我排查性能瓶颈时有一套比较系统的方法:先测原始读取视频帧的耗时(不含推理),再测推理耗时,再测画框显示耗时,分别打点记录。很多时候你会发现卡顿的根源不在模型推理,而在视频帧读取和图像显示转换上。
视频帧读取这块,如果OpenCV读取的网络流卡顿,优先检查网络带宽和拉流地址的编码格式。H.265编码的流在某些环境下解码慢,可以转换为H.264。摄像头分辨率也不需要无脑拉最高,720p对电瓶车和自行车检测来说往往已经足够,把节省下的资源用来跑推理更合理。
显存占用优化我是这么做的:推理时不需要梯度计算,用torch.no_grad()包裹推理段,减少显存开销;推理前把输入tensor转移到模型设备上,避免反复的CPU与GPU数据拷贝;批量推理时优先使用固定维度输入,避免动态shape带来的额外开销。如果把上面这些细节都做到位,8GB显存的显卡可以同时跑两个模型实例都不吃力。
5.3 实际部署中适配摄像头的几种常见情况
部署到真实场景时,摄像头的情况千奇百怪,我归纳成三类典型情况,分别说一下处理思路。
第一类是USB摄像头直连电脑。这种最简单,只要确认设备索引正确即可,系统运行时占用CPU和GPU都有余量。唯一要注意的是长时间运行时摄像头偶尔会掉线,OpenCV读取不到帧,我加了断线自动重连的逻辑:连续30帧读不到就释放cap,等待1秒再重新打开。第二类是RTSP网络摄像头,比如海康、大华这类。这类摄像头需要提供正确的RTSP地址,包括用户名密码。实际测试时发现有些设备默认画质参数太低,需要登录摄像头后台把码流调到合适档位,码率太低会导致远处的小目标根本不清楚。第三类是录播的视频文件,比如从监控系统导出的MP4。这种处理最稳妥,不会掉线,但要注意帧率设置,实际编码的帧率可能和文件名标注不一致,解析时要以容器实际数据为准。
部署完成后,我做了一个压力测试:连续运行一个下午,检测了近十万帧视频流,界面一直稳定,内存占用没有持续增长,GPU显存稳定在2GB左右。这个结果才算达到可交付标准。
6. 常见问题排查与调试经验
6.1 从训练到界面全链路的问题速查表
这个项目涉及的环节多,随便哪个小地方出错都可能导致整个流程卡住。我把整个开发过程中遇到的问题整理成速查表,按阶段分类,方便你对号入座。
| 阶段 | 常见问题 | 可能原因 | 解决措施 |
|---|---|---|---|
| 数据准备 | 训练时报错无法读取标签 | 标签类别索引越界 | 检查classes的顺序,并重新生成标签 |
| 数据准备 | 训练集图片打不开 | 文件路径含中文或特殊字符 | 统一改成英文路径,并检查图片完整性 |
| 训练 | loss值不下降 | 学习率过大或过小 | 初始学习率回到0.01,观察前10轮loss趋势 |
| 训练 | 显存溢出 | 批次过大或图像尺寸过大 | 降低batch size,或降低输入分辨率 |
| 推理 | 检测不到任何目标 | 置信度阈值过高 / 模型未正确加载 | 先降到0.1测试,再检查模型路径与类别名 |
| 推理 | 目标类别识别混乱 | 类别顺序不一致 | 确保训练时data.yaml顺序与推理时labels顺序一致 |
| 界面 | 窗口卡死无响应 | 推理放在了主线程 | 改用QThread子线程,用信号回传结果 |
| 界面 | 画面模糊看不清 | 显示时被随意拉伸 | 保持纵横比缩放显示 |
| 部署 | 加载模型时间过长 | 初始化设备时必要开销 | 加加载提示,并预留足够等待时间 |
这张表是我踩过坑的总结,不是从文档里抄来的。建议你把表格存下来,项目出现问题时先对照一遍,八成能省下大半天的时间。
6.2 我踩过的三个典型坑
第一个坑:训练数据类别顺序与配置文件不一致。我最初在标注工具里设置的类别是“ebike”“bike”,训练配置里也是这个顺序,但后来从别处源码拉过来一个模型,它的类别文件里顺序反了,是“bike”“ebike”。结果模型训练得挺漂亮,一上界面推理发现自行车和电瓶车的标签互相调换了。导出模型阶段的日志其实有警告,但我没注意,最后在界面上才发现。这个教训是:更换任何已有权重后,第一步就要打印类别列表核对。
第二个坑:单线程界面卡死。第一次集成界面时我图方便,把检测逻辑直接写在按钮槽函数里,结果一运行视频画面就卡得像幻灯片一样。当时在办公环境测试,有同事路过说了一句“你这界面怎么拖不动”,我才意识到问题严重性。后来重新设计了线程模型,把检测逻辑拆分到独立线程,这个问题才彻底消失。现在我的习惯是:任何耗时操作超过几十毫秒,就必须放进子线程,UI线程永远只做轻量刷新。
第三个坑:RTSP摄像头掉线后程序直接崩溃。第一次部署到现场时,设备网络波动了一次,程序瞬间退出,所有统计清零。后来加了异常重连机制和状态检测,遇到断流时先尝试重连三秒,失败再告警,而不是直接崩溃。这类工程化细节,往往比模型本身更影响实际使用率的感受。
6.3 效果验证与交付前自检清单
做完一个检测系统,怎么判断它”合格“了?我用一套逐条过一遍的办法来验收。
第一,精度自检。准备一组没参与训练的真实场景图片,人工数一遍目标数量,再跑模型统计结果,计算检出率和误检率。我一般要求电瓶车检出率不低于85%,自行车不低于75%,否则说明数据或模型还有问题。第二,速度自检。用实际视频源连续跑5分钟,观察FPS曲线是否稳定,平均值是否满足实时预期。第三,长时间稳定性自检。连续运行至少1小时,检查内存占用是否增长、界面是否掉帧、日志是否有异常报错。第四,用户体验检查。把程序交给非技术人员试用,观察他能否不经过指导就完成打开视频、开始检测、查看统计这三个核心动作。如果卡住了,说明界面设计还有问题。
交付时我会给最终用户准备一份简短的说明文档,内容是:双击start.bat启动程序;选择视频源;点开始检测;查看统计结果。再附一个常见问题清单。技术再强,用户不会用,项目就等于白做。
7. 项目扩展方向与最后几勺私货
当前系统跑通的是电瓶车和自行车两类目标的检测与统计,但这套骨架能扩展的内容远比这个多。我给几个我实际想过也验证过可行性的方向。
第一个方向是加行为分析。检测到目标只是第一步,目标在哪出现、停留了多久、是不是停在禁停区域,这些信息才更有决策价值。我试过在检测基础上叠加一个区域划定功能,人工在画面上画一块禁停区,只要检测框与禁停区重叠面积超过某个阈值,就触发提示或者记录一次违停事件。这个扩展对社区管理很有意义,技术上只是多了个简单的多边形碰撞判断,逻辑不复杂。
第二个方向是数据集上的持续迭代。模型上线后,每天的实时画面都会产生大量新数据。我设计了一个简易的数据回流机制:把置信度低于某个阈值的检测结果自动截图保存,隔一段时间人工筛选标注后重新加入训练集。这个闭环做法能持续提升模型在特定场景下的表现,属于终局的工程玩法。
第三个方向是轻量化部署到边缘盒子。如果需要覆盖多路摄像头,而每路摄像头都配一台高性能电脑显然不现实,将模型转换为TensorRT或OpenVINO格式后,部署到Jetson系列或算力更高的边缘计算设备上,是可以做到的。对于两路以内的摄像头,一块主流边缘板卡加上优化后的模型就能稳稳跑实时。
我的整体体会是:目标检测项目的价值,靠的是模型的精度和速度各占一半,另一半则取决于你工程化落地的能力。模型再准,部署时缺一个合理的线程设计就让人觉得卡;界面再美,没有一系列可靠的数据清洗方法就让训练效果不稳定。写这篇复盘的过程对我来说也是一次整理,之前一些零散的经验被系统地梳理了一遍。即使你不在图像领域,这套“选型—数据—训练—封装—部署—调优”的工程思路,也是值得借鉴的。
最后再分享一个小技巧:任何时候都要保持可视化输出的习惯。训练时看loss曲线,推理时看检测框,原始结果永远是调试最强有力的依据,而不是靠猜测和运气。拿着这套项目经验,换一个场景,换一批数据,你能做的事会多出很多。