简介:面向电梯监控场景的电动车与自行车识别项目,基于电梯内视角数据集对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-cpu | x86 / 纯 CPU | 教学复现、轻量推理 |
| Dockerfile-gpu | NVIDIA 显卡 | 模型训练、高吞吐推理 |
| Dockerfile-arm64 | ARM 边缘设备 | 电梯侧实时报警盒子 |
判断自己该用哪个文件的逻辑很简单:跑训练就是 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-size=8g \ elevator-yolo-gpu:latest \ bash这里--gpus all是 Docker 19.03 之后启用 GPU 支持的标准写法,前提是本机已经装好 nvidia-container-toolkit。--shm-size=8g是容易被忽略的参数,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 python=3.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 \ model=yolov8n.pt \ data=data.yaml \ epochs=100 \ imgsz=640 \ batch=16 \ patience=30 \ freeze=10 \ device=0model=yolov8n.pt指定预训练权重,这是整个“微调”过程的起点。如果你把它换成 yolov8s.pt 或更大的版本,精度通常会涨,但显存占用和推理延迟也会跟着涨。电梯场景对实时性要求不低,所以我的习惯是先用 nano 版本跑通全流程,确认数据没有低级错误后再升级到 small 版本做最终训练。
imgsz=640是输入分辨率。电梯监控常见的画面里,电动车出现在中远距离时占画面比例并不大,如果显存允许,我会把 imgsz 提到 768 甚至 960,这对小目标检测的提升非常直接。代价是训练时间变长,推理帧率下降。
freeze=10表示冻结模型前 10 层参数。预训练模型浅层的通用特征(边缘、纹理)在电梯场景下依然有效,冻结它们可以减少反向传播的计算量,同时防止小数据集上出现特征遗忘。如果数据量确实很少(比如只有两三百张),我甚至会冻结到 15 层,只让最后几层去适应新类别。
| 参数 | 常用值 | 作用 | 备注 |
|---|---|---|---|
| imgsz | 640 | 输入分辨率 | 小目标多时上调到 768 |
| batch | 16 | 单卡批量大小 | 显存不足时降为 8 |
| epochs | 100 | 训练轮数 | 配合 patience 早停 |
| patience | 30 | 指标不涨的容忍轮数 | 防止无效长训 |
| freeze | 10 | 冻结浅层数 | 数据少时加大 |
| device | 0 | 指定 GPU 编号 | CPU 用 device=cpu |
4.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)"], label="mAP50") plt.plot(df["epoch"], df["metrics/mAP50-95(B)"], label="mAP50-95") plt.xlabel("epoch") plt.ylabel("metric") plt.legend() plt.show()读这张图时我会看三个信号:
- mAP50 在最早 20 轮内有没有快速爬升。如果没有,大概率是 learning rate 设置问题或者数据标注有硬伤。
- mAP50-95 是否随着训练缓慢逼近 mAP50。两者差距越大,说明目标边界框回归越不稳,这时候增加 imgsz 或调整 anchor 配置比继续加轮数更有效。
- 后期是否出现过拟合迹象。判断标准是训练 loss 持续下降但验证指标持平或下降,这时候 patience 会帮你自动停下。
TensorBoard 的读法是把事件文件丢给它,一条命令就能起服务:
tensorboard --logdir=path/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_thr=0.35, gone_limit=15): 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_thr=0.35控制匹配宽容度,阈值太低会把相邻的两辆车并成一辆,阈值太高又容易让同一辆车因为视角变化而分裂成两个 ID。gone_limit=15表示目标连续 15 帧没有匹配到任何检测框,才判定它已经离开,这能扛住电梯监控里常见的短暂遮挡。
6.2 三段式验证法
我去实际部署这类系统时,不用整段视频做验收。我会把测试视频切成三段:进电梯、关门、到楼层。进电梯段看的是目标能否在第一时间被抓住;关门段看的是遮挡和反光是否导致目标丢失;到楼层段看的是目标离场后是否成功触发一次报警并清除 ID。三段合起来,才算验证完整。
我第一次交付这个功能时,就是没做轨迹连续性验证,直接拿逐帧结果给了物业,结果一天报了上百条重复告警。从那以后,我每次跑完跟踪逻辑都会强制走一遍这三段视频,盯三个数字:报警总数是否等于目标总数、单目标是否只报一次、有没有出现跨段误报。希望帮到你。
本文还有配套的精品资源,点击获取