ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

电梯电动车识别实战:YOLO微调与跟踪去重报警方案

电梯电动车识别实战:YOLO微调与跟踪去重报警方案 简介面向电梯监控场景的电动车与自行车识别项目基于电梯内视角数据集对YOLO预训练模型微调同时提供基于检测与基于跟踪两种处理方法可直接用于毕设/课设、工程实训与大作业场景。整个压缩包共一百三十四个文件体积约十六点九六MB其中三十四个Python脚本承载检测/跟踪核心逻辑三十四个YAML文件用于模型与训练配置JPG与PNG图片提供样本展示Markdown文档辅助阅读Dockerfile可快速复现运行环境。目前已有六十五人浏览学习项目代码经过严格测试可直接运行复现适合初学者及有基础者修改扩展。压缩包内含完整源码、工程文件与说明文档基于检测的方法会逐帧输出标注图像基于跟踪的方法则增加去重处理两者结合可解决电梯场景中重复计数问题。项目内还附有训练日志、结果CSV与Jupyter教程便于复盘实验过程和撰写设计报告代码结构清晰也适合作为课程设计、毕业设计或学科竞赛的扩展框架。1. 电梯里的电动车识别这个项目解决什么问题电梯监控里抓电动车违规进梯难点从来不是“能不能识别”而是“一天下来到底报了多少次”。物业保安不可能眼睛盯着几十路画面靠的是算法把电动车进电梯的那一刻捞出来。这个资源做的就是这件事用电梯轿厢内视角采集的数据集对 YOLO 预训练模型做微调最后拿到一套能跑通的推理链路。项目里同时给了两条路线——逐帧检测版把每个有目标的帧都画框存图另一条在跟踪的去重逻辑上加了一层同一辆车只报一次警。对于做毕设、课设或大作业的人来说完整源码、Dockerfile 和 tutorial.ipynb 都在包里按顺序跑就能复现而对工程实践更关心的是它把“误报”和“重复报警”这两个电梯场景的高频坑也摊开了。2. 模型选型与两套推理方案为什么微调 YOLO 而不是重新训练2.1 微小目标与固定视角下迁移学习的价值电梯轿厢有个天然的优势摄像头位置基本固定视角变化小背景几乎没有大范围移动。这和自动驾驶场景完全不同不需要模型每帧去适应全新的环境。但反过来它的劣势也明显——电动车和自行车往往是俯视或斜视角度车身占画面比例不大而轿厢里的扶手、镜面反光、乘客身体遮挡又特别多。在这样的数据量级下如果从零开始训练一个检测网络准确率会非常难看因为模型需要同时学会“什么是车”和“什么是电梯内部环境”这对一个冷启动的网络来说负担太重。微调预训练模型的做法本质上是把 COCO 或大规模数据上学到的通用视觉特征直接搬过来。预训练模型的浅层已经在学边缘、纹理、颜色块这些基础特征这些特征在电梯场景下依然有效。我们真正需要动的是高层语义部分——把“这是一辆自行车”和“这是一辆电动车”的区分能力重新校准到电梯这个环境里。这样做的好处有两点一是收敛速度肉眼可见地快通常几十轮就能到一个能用的状态二是对标注样本量的要求低很多几百张电梯内部视角的图就能把模型拉到实用线以上。这个项目微调时用的就是 YOLO 系列的预训练权重我一般会先用 nano 或 small 尺寸跑通链路确认数据没问题后再换大模型刷精度。项目正文里能看到 results.csv 和 TensorBoard 日志events.out.tfevents.*说明训练过程是被完整记录下来的这正好可以拿来做早停判断和答辩时的过程展示。2.2 检测与跟踪一个去重问题引发的分叉刚开始接触这个项目的人最容易忽略的点就是逐帧检测其实不等于能用的系统。电梯里一辆电动车从进门到停稳往往要停留几秒到十几秒按 25 到 30 帧每秒算一个目标会触发上百个检测框。如果每帧都推送报警监控室的提示音会直接变成噪音。这就是项目里把方案拆成两条路线的根本原因。基于检测的方法逻辑最直白模型对每一帧跑推理只要检测框置信度超过阈值就把这一帧的标注图保存下来。它的价值在于“取证”——比如事后回查这辆车什么时候进的、进了之后在哪个位置停留逐帧图都能对上。但它的短板是“去重”完全不在考虑范围内同一个目标会生成大量重复结果。基于跟踪的方法则在检测结果上再加了一层目标关联逻辑把同一辆车在不同帧之间的检测框串成一条轨迹一个目标出现期间只输出一次报警。两条路线的取舍其实是个典型的工程问题。我用下面这个表来对比对比维度基于检测的方法基于跟踪的方法输出粒度每个有目标的帧都返回标注图每个目标出现只报一次去重能力无按轨迹或位置匹配去重实现复杂度低直接推理即可中需要维护目标状态适用场景事后取证、逐帧分析实时值守、异常统计失败风险大量重复报警目标匹配错误导致漏报我自己在实盘里通常的做法是先跑检测方法对齐模型的识别能力确认 mAP 没问题之后再切换到跟踪方法做报警收敛。两个都做还有一个好处就是答辩或演示的时候可以现场对比同一段视频的两种输出结果这个直观差异比任何图表都有说服力。2.3 项目资源里这些文件分别对应什么拿到包之后不要急着跑代码先按文件清单把脉络理清。里面的 events.out.tfevents.* 是 TensorFlow/TensorBoard 的训练日志它记录了每一轮训练在训练集和验证集上的损失与指标变化。results.csv 是同一份信息的表格版我习惯用 pandas 直接读它做指标曲线分析比启动 TensorBoard 更快。两个 Dockerfile 版本加上 dockerfile 主文件说明作者已经做了部署层的分层CPU 版用来在没显卡的机器上跑通链路GPU 版用来喂训练arm64 版面向边缘设备。tutorial.ipynb 则是交互式入口里面应该有从环境检查到推理演示的完整操作顺序。这个资源文件组合暗示了一个重要的工程习惯训练过程和部署过程是分开交付的。你可以在有 N 卡的机器上训练在普通的 CPU 机器或者 Jetson 盒子上推理两边环境互不污染。后面第三、四章我会分别展开环境搭建和训练细节那才是真正要动手的部分。3. 环境准备Dockerfile-cpu/gpu/arm64 三条安装路径与 notebook 快速验证3.1 三个 Dockerfile 的定位差异先看 Dockerfile 这一组文件dockerfile基础版、Dockerfile-cpu、Dockerfile-gpu、Dockerfile-arm64。很多人第一次看到四个 Dockerfile 会懵其实它们解决的是同一个问题在不同硬件上的表现差异。CPU 镜像适合做两件事一是没有独立显卡的笔记本上快速跑通代码链路二是做纯验证性的推理实验。代价是训练速度慢得让人失去耐心所以 CPU 镜像主要服务于“复现结果”而不是“重新训练”。GPU 镜像则绑定 CUDA 和 cuDNN 的版本训练时大部分算子会落到 TensorRT 或 CUDA 加速上这也是我实际训练时用的配置。arm64 镜像对应的是 ARM 架构设备常见的是 Jetson Nano/Xavier、树莓派这类边缘盒子这类设备算力有限但功耗低适合做电梯机房里的常驻推理节点。镜像文件目标硬件典型用途dockerfile任意基础环境通用链路验证Dockerfile-cpux86 / 纯 CPU教学复现、轻量推理Dockerfile-gpuNVIDIA 显卡模型训练、高吞吐推理Dockerfile-arm64ARM 边缘设备电梯侧实时报警盒子判断自己该用哪个文件的逻辑很简单跑训练就是 GPU 镜像只是想把 notebook 跑完、看看框画得准不准用 CPU 镜像就够了要往带屏幕的嵌入式设备上部署再看 arm64。3.2 用 Docker 构建镜像并验证 GPU 可用性我习惯在项目根目录直接构建避免路径映射问题。假设我们把整个工程目录映射到容器内的 /workspace构建命令如下# 构建 GPU 版本镜像Dockerfile 指定为 Dockerfile-gpu docker build -f Dockerfile-gpu -t elevator-yolo-gpu:latest . # 构建完成后启动容器挂载数据与代码目录 docker run -it --gpus all \ -v $(pwd):/workspace \ -v /path/to/datasets:/datasets \ --shm-size8g \ elevator-yolo-gpu:latest \ bash这里--gpus all是 Docker 19.03 之后启用 GPU 支持的标准写法前提是本机已经装好 nvidia-container-toolkit。--shm-size8g是容易被忽略的参数PyTorch 的 DataLoader 在跑多进程数据加载时默认的共享内存只有 64MB数据量大一点就会报 “BrokenPipeError” 或直接卡死调大共享内存能省掉很多玄学问题。-v把当前工程和外部数据集分别挂载进去这样容器里改代码、宿主机同步可见训练产出的权重也能直接留在宿主机上。进入容器后先做一次硬件自检python -c import torch; print(torch.cuda.is_available()); print(torch.cuda.get_device_name(0))如果输出第一行是 True、第二行是你的显卡型号说明环境没问题。这里常见的问题是你装的是 CPU 版 PyTorch哪怕 GPU 镜像构建成功torch.cuda.is_available() 也可能是 False。这种情况去检查 Dockerfile 里 pip install 的 torch 版本是否带 cu 后缀不要只看镜像构建成功就往下走。3.3 不走 Docker本地 conda 环境的替代路线有些人的机器上 Docker 跑不起来或者不想忍受镜像下载的耗时。项目里有 tutorial.ipynb这意味着作者自己也留了本地运行的口子。我通常的做法是 conda 建独立环境避免把系统 Python 搞坏conda create -n elevator python3.10 -y conda activate elevator pip install ultralytics torch torchvisionultralytics 这个包自带 YOLO 的训练和推理封装notebook 里大概率就是 import 它来加载权重。这里要留意 torch 和 torchvision 的版本要跟本机 CUDA 驱动匹配驱动版本过低会导致导入 torch 时直接报错。如果只是为了跑推理CPU 版 torch 也不是不行只是帧率会比较感人。我一般在没有 N 卡的机器上就用 CPU 版先把代码流程走通真正调参数去 GPU 机器上做。3.4 tutorial.ipynb 的验收路径环境就绪后tutorial.ipynb 是验证整个工程能不能跑的最小闭环。我建议按这个顺序执行前几个单元格一般是 import 检查和路径设置确认无报错。加载预训练微调权重文件观察权重是否成功读入模型结构。用测试图片跑一次推理如果 notebook 会把画了框的图片保存到指定目录打开看框的位置和类别是否正确。有视频的话跑一小段片段重点看推理是否卡顿、结果文件是否按预期输出。跑完 notebook 不代表训练权重就是好的它只能证明“代码链路没断”。真实模型效果怎么样要看第四章的训练监控和推理测试。如果 notebook 里某个单元格因为缺包报错先回去查环境依赖不要立刻怀疑代码有问题。4. 数据与训练电梯视角标注格式、关键超参与 results.csv 读法4.1 数据组织与标注文件格式这个项目用的是电梯轿厢内视角数据集从项目描述来看检测目标是电动车和自行车两类。在 YOLO 框架下数据目录的组织方式非常固定我建议严格按下面的结构来放datasets/ images/ train/ val/ labels/ train/ val/ data.yaml labels/train/0001.txt其中 data.yaml 里的内容决定了训练时模型读哪些数据、分几个类别。典型内容如下path: /path/to/datasets train: images/train val: images/val names: 0: e-bike 1: bicycle这里有个设计决策需要你自己拍板电动车和自行车是分成两个独立类别还是合并成一个“非机动车”类别。从电梯违规管理这个语义出发两者都是不受欢迎的进梯对象合并成一个类可以让模型专注学“这一类物体的共性”样本量也更充足。但从答辩效果看分开两个类更能体现数据集的细致程度也能展示模型对相似类别的区分能力。这个项目既然标题里明确写了“电动车以及自行车”我建议默认用两个类来训练如果发现类间混淆严重再降级合并。标注文件是 YOLO 的归一化 txt 格式每一行对应一个目标0 0.482 0.531 0.218 0.364 1 0.713 0.627 0.152 0.289字段含义是类别 id、归一化中心点 x、归一化中心点 y、归一化宽度 w、归一化高度 h。这四个坐标值都除以了图片宽高所以取值范围在 0 到 1 之间。我在做标注质量检查时会写一小段脚本把标注框反画回图片上看是否贴合车身重点检查自行车和电动车的标签有没有串——因为这两种车在俯视角度下长得确实有点像尤其是车把和座椅的位置关系容易让人标错。4.2 微调命令与关键超参数数据准备好之后用 ultralytics 官方 CLI 就能启动微调。关键不是命令本身而是参数为什么这么设置。我从实践角度拆一下yolo detect train \ modelyolov8n.pt \ datadata.yaml \ epochs100 \ imgsz640 \ batch16 \ patience30 \ freeze10 \ device0modelyolov8n.pt指定预训练权重这是整个“微调”过程的起点。如果你把它换成 yolov8s.pt 或更大的版本精度通常会涨但显存占用和推理延迟也会跟着涨。电梯场景对实时性要求不低所以我的习惯是先用 nano 版本跑通全流程确认数据没有低级错误后再升级到 small 版本做最终训练。imgsz640是输入分辨率。电梯监控常见的画面里电动车出现在中远距离时占画面比例并不大如果显存允许我会把 imgsz 提到 768 甚至 960这对小目标检测的提升非常直接。代价是训练时间变长推理帧率下降。freeze10表示冻结模型前 10 层参数。预训练模型浅层的通用特征边缘、纹理在电梯场景下依然有效冻结它们可以减少反向传播的计算量同时防止小数据集上出现特征遗忘。如果数据量确实很少比如只有两三百张我甚至会冻结到 15 层只让最后几层去适应新类别。参数常用值作用备注imgsz640输入分辨率小目标多时上调到 768batch16单卡批量大小显存不足时降为 8epochs100训练轮数配合 patience 早停patience30指标不涨的容忍轮数防止无效长训freeze10冻结浅层数数据少时加大device0指定 GPU 编号CPU 用 devicecpu4.3 训练监控从 results.csv 和 TensorBoard 判断模型状态项目里提供 results.csv这是 ultralytics 训练过程的自动产物。每一轮训练后它都会追加一行记录关键列包括 train/box_loss、train/cls_loss、metrics/precision(B)、metrics/recall(B)、metrics/mAP50(B)、metrics/mAP50-95(B)。我一般用 pandas 直接读这个文件画曲线import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(results.csv) plt.plot(df[epoch], df[metrics/mAP50(B)], labelmAP50) plt.plot(df[epoch], df[metrics/mAP50-95(B)], labelmAP50-95) plt.xlabel(epoch) plt.ylabel(metric) plt.legend() plt.show()读这张图时我会看三个信号mAP50 在最早 20 轮内有没有快速爬升。如果没有大概率是 learning rate 设置问题或者数据标注有硬伤。mAP50-95 是否随着训练缓慢逼近 mAP50。两者差距越大说明目标边界框回归越不稳这时候增加 imgsz 或调整 anchor 配置比继续加轮数更有效。后期是否出现过拟合迹象。判断标准是训练 loss 持续下降但验证指标持平或下降这时候 patience 会帮你自动停下。TensorBoard 的读法是把事件文件丢给它一条命令就能起服务tensorboard --logdirpath/to/events.out.tfevents.* 的父目录浏览器打开后重点看 scalars 面板跟 results.csv 是同一份数据但交互式查看方便很多。这个项目里保留了完整的 TensorBoard 事件文件答辩时可以直接截图展示训练曲线的变化过程比贴一张最终测试图有说服力得多。5. 避坑清单五条高频翻车记录与处理办法5.1 训练 loss 下降但 mAP 卡在 0.5 附近现象训练过程中 loss 曲线非常漂亮地往下走但训练到中后期mAP50 怎么都跨不过 0.5 左右这道坎。原因电梯内摄像头视角特殊车身往往有透视畸变且目标在画面中偏小。默认的 640 输入分辨率和预设 anchor 对这种尺度匹配不好网络学会了“大概有个东西”但学不会“这个东西的边界在哪”。解决先把 imgsz 从 640 升到 768同时检查数据集中目标框的宽高比分布如果大量目标是扁长的用 yolo 自带的 autoanchor 重新聚类 anchor。这一步通常比疯狂加 epoch 有效得多。5.2 检测框在镜面反光处碎了现象电梯门是不锈钢镜面电动车靠近时镜面里会出现一个倒影模型把倒影也当成一辆车或者真实车身和倒影重叠导致检测框时大时小。原因训练数据的正样本里缺少“近距离镜面反光”这种特殊场景模型没有学会区分实体和倒影。解决在训练数据里加入反光样本或者用数据增强里的随机擦除Random Erase模拟遮挡与反射干扰。我试过最直接的办法是采集一段电梯门从打开到关闭的连续视频帧挑出有倒影的帧加入训练集效果立竿见影。5.3 跟踪方法把同一辆车重复报警现象把算法切到基于跟踪的方法后一辆电动车从进梯到出梯系统仍然报了两三次。原因跟踪去重的匹配逻辑太粗糙。常见实现是只用当前检测框和上一帧检测框的 IoU 做匹配但电动车视角一旦变化比如车头转向IoU 可能骤降目标就被当成新目标。解决把匹配逻辑从“IoU 大于阈值”改成“IoU 加权 中心点距离加权”并且允许目标短暂消失几帧。我一般在实现里加一个“消失帧数”参数目标消失不超过 15 帧就继续认为它还在场直到它真正离开画面才结束轨迹。5.4 Docker 镜像构建慢或推理速度只有几 FPS现象按教程构建 GPU 镜像构建过程跑完发现推理速度还不如 CPU 版甚至 nvidia-smi 看不到 Python 进程在占用显卡。原因很可能基础镜像里装的是 CPU 版 PyTorch。镜像构建成功不等于 GPU 可用需要在容器内确认 torch 是否能调用 CUDA。解决进容器先跑python -c import torch; print(torch.cuda.is_available())如果是 False重装带 cu 后缀的 torch 版本。这一步是纯环境问题不要花时间在代码里找原因。5.5 测试集 mAP 很高但实际电梯画面乱报现象训练集的验证指标很好看mAP50 能到 0.9 以上但拿到另一个电梯的监控视频里行人背包被当成电动车、安全帽也被框出来。原因训练集和测试集分布不一致。电梯内视角数据集可能只在某一台特定电梯里采集新场景的亮度、墙壁颜色、镜头畸变不一样模型的泛化能力被高估了。解决把推理置信度阈值从默认的 0.25 往回调一点试比如调到 0.4 看误检是否明显下降更根本的办法是加入新场景的少量样本做增量微调。我在交付时都会明确告诉对方跨电梯部署前一定先采集新场景的 100 到 200 帧做验证。6. 进阶把逐帧检测升级成跟踪去重的一次报警逻辑6.1 轻量级去重实现项目里基于跟踪的方法核心是给每个检测目标建一个状态对象维护它的位置和历史。下面这段是典型的轻量级实现思路class ElevatorTarget: def __init__(self, box, frame_id): self.box box self.last_seen frame_id self.gone 0 def track_and_deduplicate(results, frame_id, active_targets, iou_thr0.35, gone_limit15): for box in results.boxes: matched False for tid, target in list(active_targets.items()): iou compute_iou(box.xyxy, target.box) if iou iou_thr and target.gone gone_limit: target.box box.xyxy target.last_seen frame_id target.gone 0 matched True break if not matched: new_id generate_target_id() active_targets[new_id] ElevatorTarget(box.xyxy, frame_id) for tid, target in list(active_targets.items()): if target.last_seen frame_id: target.gone 1 if target.gone gone_limit: alert_once(tid, target.box) del active_targets[tid]这段代码的逻辑是把同一辆车在连续帧中的检测框匹配到同一个 target 上。iou_thr0.35控制匹配宽容度阈值太低会把相邻的两辆车并成一辆阈值太高又容易让同一辆车因为视角变化而分裂成两个 ID。gone_limit15表示目标连续 15 帧没有匹配到任何检测框才判定它已经离开这能扛住电梯监控里常见的短暂遮挡。6.2 三段式验证法我去实际部署这类系统时不用整段视频做验收。我会把测试视频切成三段进电梯、关门、到楼层。进电梯段看的是目标能否在第一时间被抓住关门段看的是遮挡和反光是否导致目标丢失到楼层段看的是目标离场后是否成功触发一次报警并清除 ID。三段合起来才算验证完整。我第一次交付这个功能时就是没做轨迹连续性验证直接拿逐帧结果给了物业结果一天报了上百条重复告警。从那以后我每次跑完跟踪逻辑都会强制走一遍这三段视频盯三个数字报警总数是否等于目标总数、单目标是否只报一次、有没有出现跨段误报。希望帮到你。本文还有配套的精品资源点击获取
返回列表