简介面向电梯监控视角下的电动车与自行车识别场景这份工程资源提供了基于YOLO预训练模型微调的完整解决方案包含检测与跟踪两种技术路线。检测方式会对每一帧中检测到的目标实例返回标注图像跟踪方式则在检测基础上进行去重更适合连续视频监控场景。项目整体共134个文件以Python脚本、YAML配置文件、图像样本为主同时包含Docker部署文件、Jupyter教程、说明文档等压缩包仅16.96MB。目前已有65人学习下载代码经过严格测试可轻松复现。该工程可用于毕业设计、课程设计、工程实训或竞赛项目既能快速搭建电梯内违规停车识别原型也便于替换数据集扩展至其他目标识别任务。配套的说明文档和设计思路对理解YOLO微调、目标检测与跟踪方法有参考价值适合学生与开发者学习实践。1. 把电动车目标检测迁移到电梯监控视角真正的坑不在模型做过安防或社区智能化项目的人都有体会同样是检测电动车园区平视监控和电梯俯视监控完全是两套难度。电梯视角的摄像头装在轿厢顶部一角画面是广角俯拍车身形变严重车把、座椅和踏板的轮廓经常叠在一起再加上电梯内灯光频闪、不锈钢轿厢壁反光、乘客身体遮挡一套在停车场跑得很好的模型搬过来误检漏检率会直接翻倍。这个项目要解决的正是这个具体问题针对电梯监控视角下的电动车和自行车识别做出能稳定触发告警的检测方案。对做毕设或实训的同学来说它的价值在于完整覆盖了“数据准备 → 模型训练 → 部署验证”的闭环并且踩坑点足够典型写论文和答辩都有内容可讲。本文按我实际做这类项目的顺序来拆从选模型和造数据开始到训练参数怎么调再到部署时的高频翻车点最后给出一套可操作的验收方法。2. 电梯监控场景为什么不能直接用通用检测模型先搞懂三个差异2.1 视角差异俯视 60 度到 80 度造成的形变问题常规监控的电动车检测数据集画面大多是平视或略俯视车身侧面完整可见检测器能依赖明显的车轮圆形、车身长条形轮廓来判别。但电梯摄像头通常安装在轿厢后上方向下倾斜约 60 到 80 度画面里电动车是“压缩”的——轮子变成扁椭圆车身长度被透视缩短从侧面看过去最明显的鞍座和尾架反而被车主的身体或车厢壁挡住。这个差异直接影响到模型的泛化能力。用公开数据集训练出来的 YOLO 权重在电梯实景里的典型表现是置信度普遍掉到 0.4 以下漏检多发生在车辆刚推进电梯、只有前半截入画的时刻误检集中在地面倒影、乘客拉杆箱、甚至轮椅等轮式物体上。我的建议是第一版就不要用下载好的通用权重直接上线而是把电梯视角当成一个单独的域来处理从数据层面先做适配。2.2 光线与反光金属轿厢壁是天然的对抗样本电梯轿厢的镜面不锈钢内壁在广角镜头下会产生大量高光和镜像尤其是推车进电梯的瞬间车身会同时出现“实物 镜像”两处模型经常把镜像里的车也算一个目标导致同一次推车触发两次告警。更麻烦的是频闪灯下的色温漂移。电梯内多是荧光灯或 LED 平板灯视频帧之间会出现轻微的亮度跳变如果训练时没有加入亮度抖动和对比度扰动的数据增广模型在夜间或昏暗地下室等候电梯时容易把深色车身的电动车误判为自行车或者干脆漏掉。2.3 遮挡模式人体紧贴车身单帧检测的极限电梯空间狭小人推车进入后人的身体会大面积遮挡车身中部画面里经常只剩车把、前轮或后轮的一小部分可见。这种情况下只看单帧的检测器很容易丢失目标。但做告警类项目通常不能依赖单帧需要配合时序逻辑——连续多帧中都出现“局部的车轮特征”就应该判定为有车进入。这也是本类项目与普通目标检测任务最大的区别你要的不只是“模型在单张图上检得准”而是“模型在电梯内的时序视频流里维持稳定”。所以后续的数据集制作和推理逻辑设计都要围绕电梯内的真实遮挡模式来展开。提示如果导师或甲方给的视频里有电梯开门瞬间、人车混合进出、多人同梯等片段优先保留并重复采样这些样本的价值远高于常规路面的电动车图片。3. 数据准备与标注把电梯内真实视频转成可训练的数据集3.1 选型YOLO 系仍是这类项目的最优起点电动车和自行车在电梯里的目标大小属于中大型不需要太细的纹理特征对模型容量要求不高。综合训练成本、部署难度和社区资料丰富度常见做法是选 YOLOv8 或 YOLOv9 的 s/m 档。YOLOv8s 在单张 RTX 3060 上训练一轮大概 10 到 20 分钟显存占用约 6 到 8GB适合课设和毕设的硬件条件。不建议第一版就上 YOLACT 或 Mask R-CNN电梯场景不需要实例分割精度检测框加一个跟踪 ID 就足够支撑告警业务分割模型在推理端的开销和显存压力反而会让部署环节变得很被动。3.2 标注策略只标两个类还是把行人也标出来训练目标只有电动车和自行车但推理时往往需要第三个隐式类别来辅助判断——“人”。原因在于如果模型完全没见过“人”电梯里人被检成车的概率会显著上升。常见做法是数据集中加一个 person 类标注全部出现的行人推理时只对电动车、自行车触发告警人的检测结果用于抑制误报。标注工具用 LabelImg 或 X-AnyLabeling 都行输出 YOLO 格式的 txt 文件。核心要求是统一标注规范电动车的判定范围包含整车从车轮外沿到车把最外侧后视镜不算。遮挡超过 50% 的车辆不标作为背景参与训练。镜头边缘被截断一半以上的车辆不标。镜像里的车辆一律不标只标实体车。3.3 数据增广配置模拟电梯光照和遮挡的关键参数训练时在 YOLO 的增广配置里要针对电梯视角做调整。默认的增强策略对自然光场景友好但对电梯这种低照度、高反光场景不够。推荐在 ultralytics 的默认配置上改这几个参数# augment 参数基于 ultralytics 默认值修改 hsv_h: 0.02 # 色调扰动调低避免颜色偏移过大 hsv_s: 0.6 # 饱和度扰动加大模拟不同灯光下的色彩漂移 hsv_v: 0.5 # 明度扰动模拟电梯内频闪 degrees: 30 # 旋转角度加大适配俯视广角下的车身任意朝向 translate: 0.2 # 平移扰动适配车辆进电梯时入画不完整的情况 scale: 0.6 # 缩放范围加大适配镜头远近造成的车身大小差异 fliplr: 0.5 # 水平翻转注意电梯监控画面左右镜像并不对称 mosaic: 1.0 # 保持开启对小目标显著 mixup: 0.3 # 混合训练减少镜面反光造成的背景过拟合其中 degrees 和 translate 是我认为最值得调的两项。电梯广角镜头下车身的旋转角度分布范围很宽普通数据集默认的 degrees 只有 0 到 10 度训练出来的模型对斜着推进电梯的车会很不稳定。translate 加大后模型能学会在目标只出现三分之一的情况下依然产生有效预测。注意fliplr 不要无脑开。如果数据集中左右方向的车身特征不对称例如充电口都在右侧、后视镜只有一侧水平翻转会让模型学到错误的特征相关性。先试跑几十个 epoch 看验证集曲线再决定。3.4 数据量与时序切分单帧图和连续帧应该怎么分配训练数据建议控制在 2000 到 5000 张标注图之间。课设场景下从电梯监控视频抽帧是最快的方式每段视频每隔 5 到 10 帧抽一张手动剔除模糊帧和重复度过高的帧保证同一辆车在不同帧里有不同的遮挡状态和入画比例。验证集和测试集必须从不同时间段的视频里抽取不能从同一段视频里随机切。否则会出现数据泄漏模型其实记住了同一辆车的背景特征而非真正的车身特征。推理端的时序逻辑在数据准备阶段就要想清楚。如果计划做“连续 3 帧以上出现检测框才触发告警”那测试集里就应该包含一段连续帧序列不能只用随机抽帧来验收。4. 训练与调参从 YOLOv8s 开始跑通再用三项指标判断能不能用4.1 最小可跑通命令数据切分与首次训练# 目录结构示例 # datasets/elevator/ # images/train / images/val / images/test # labels/train / labels/val / labels/test # 切分数据按 8:1:1 比例注意 --seed 固定保证可复现 python -c import os, random from shutil import copyfile random.seed(42) img_dir datasets/elevator/images/all train_dir, val_dir, test_dir datasets/elevator/images/train, datasets/elevator/images/val, datasets/elevator/images/test os.makedirs(train_dir, exist_okTrue) os.makedirs(val_dir, exist_okTrue) os.makedirs(test_dir, exist_okTrue) imgs os.listdir(img_dir) random.shuffle(imgs) for i, img in enumerate(imgs): dst train_dir if i len(imgs)*0.8 else (val_dir if i len(imgs)*0.9 else test_dir) copyfile(os.path.join(img_dir, img), os.path.join(dst, img)) copyfile(os.path.join(img_dir, img).replace(images, labels).replace(.jpg, .txt), os.path.join(dst, img).replace(images, labels).replace(.jpg, .txt)) 这段脚本把全部图片按 8:1:1 随机切到 train、val、test 三个目录同时把同名标签文件一起拷过去。注意这里只是演示结构真实项目里建议按视频时间段来切而不是按单帧随机切否则验证集会混入同一段视频的相邻帧指标会虚高。固定 random seed 是必要的方便复现训练结果答辩时也能说清楚数据来源。切完后写一个数据集配置文件指向上面三个目录# data/elevator.yaml path: datasets/elevator train: images/train val: images/val test: images/test nc: 3 names: 0: person 1: e_bike 2: bicycle4.2 开始训练用预训练权重还是从零开始yolo detect train \ modelyolov8s.pt \ datadata/elevator.yaml \ epochs100 \ imgsz640 \ batch16 \ device0 \ patience20 \ projectruns/train \ nameelevator_v1 \ seed42用yolov8s.pt做预训练是省时间的主流做法尤其数据量只有两三千张时从 COCO 学到的通用特征能让模型快速收敛到电梯场景。patience20表示验证集 mAP 连续 20 个 epoch 不提升就自动早停能避免无意义的等待也能防止过拟合。device0指定 GPU如果只有 CPU 就改成devicecpu但训练时间会慢很多建议至少租一张云 GPU 来跑。跑完看两个关键指标验证集上的 mAP50 和 mAP50-95以及每个类别的单独 AP。对电梯场景来说电动车类的 AP50 应达到 0.85 以上自行车类可以略低但不应低于 0.75。如果自行车类一直上不去多半是样本里自行车数量太少回到数据准备里补样本调参数解决不了数据不平衡问题。4.3 必调参数不要直接套默认值YOLO 的默认训练参数是针对通用目标检测调优的电梯场景有三个参数值得手动改首先是imgsz。电梯监控如果原始分辨率是 1920x1080直接缩到 640 训练会让车把、车轮这些细节变得模糊。建议先试 800x800 左右如果显存不够再降回 640。实测中 imgsz 从 640 提到 800对遮挡严重的车身小部件识别有明显改善。其次是batch。不要为了显存把 batch 设到 4 以下否则 BN 层统计不稳定训练 loss 曲线会剧烈震荡。显存不够时优先降低 imgsz而不是过度调低 batch。第三是lr0和lrf。数据量小的时候默认的lr00.01对预训练权重来说偏大首轮 loss 容易爆。我一般会设lr00.005配合lrf0.01让后期学习率降得更彻底在数据量有限的情况下能挤出几个点的 mAP 提升。4.4 训练效果评估单看 mAP 不够要看漏检场景分布# 用训练好的权重在测试集上逐个视频片段推理 # 重点观察推车入画的前 5 帧模型是否连续稳定输出检测框 from ultralytics import YOLO import cv2 model YOLO(runs/train/elevator_v1/weights/best.pt) cap cv2.VideoCapture(test_videos/case_01.mp4) fps cap.get(cv2.CAP_PROP_FPS) frame_count 0 detect_log [] while cap.isOpened(): ret, frame cap.read() if not ret: break if frame_count % 3 ! 0: # 每 3 帧检测一次模拟告警系统的抽帧策略 frame_count 1 continue results model(frame, conf0.35, imgsz640, verboseFalse) boxes results[0].boxes for box in boxes: cls int(box.cls[0]) conf float(box.conf[0]) name model.names[cls] if name in (e_bike, bicycle): detect_log.append({ frame: frame_count, class: name, conf: conf }) frame_count 1 cap.release() # 输出结果供人工核查哪些片段里车辆已经入画但模型没有输出这段测试代码的核心思路很简单每个视频按固定间隔抽帧推理把有效检测记录写到列表里之后分析检测记录的时间连续性和覆盖比例。注意conf0.35比默认的 0.25 高一点这是为了贴近真实部署的阈值——实际告警系统不可能用 0.25 那么低的置信度否则误报会吃掉告警的有效性。人工核查的重点不是 mAP 数字而是“车辆刚探进画面时模型能不能抓住”。电梯项目的验收逻辑是车辆推进门的瞬间、车身一半在外一半在内的时候、车身完全被乘客挡住只剩车轮的时候这三个节点的检测表现决定了系统能不能用。5. 部署到实际电梯监控推理框架选择与三个高频翻车点5.1 导出与部署ONNX 还是 TensorRT训练完的 PyTorch 权重不能直接扔到生产环境需要导出成推理框架能加载的格式。对电梯监控这种边缘盒子场景最常见的两种方案是 ONNX Runtime 和 TensorRT。# 导出 ONNX固定输入尺寸方便后续优化 yolo export \ modelruns/train/elevator_v1/weights/best.pt \ formatonnx \ imgsz640 \ opset12 \ simplifyTrue # 导出 TensorRT 引擎需要 NVIDIA GPU 的推理设备 yolo export \ modelruns/train/elevator_v1/weights/best.pt \ formatengine \ imgsz640 \ halfTrueONNX 格式通用性最好跑在 CPU 和 GPU 上都行TensorRT 引擎只适用于 NVIDIA 设备但推理速度能有 2 到 3 倍提升。电梯监控盒子大多是 NVIDIA Jetson 系列用 TensorRT 是正路。halfTrue表示 FP16 精度推理。电梯场景不是高精度测量场景FP16 对检测框位置的影响可以忽略但显存占用直接砍半Jetson Nano 这类小设备也能跑得动。注意导出 engine 之前先在 Jetson 上装好对应版本的 TensorRT宿主机的 TensorRT 版本和 Jetson 的不一致会导致导出后无法加载。5.2 避坑置信度阈值与目标尺寸的联动矛盾现象把阈值从 0.25 调到 0.5 后误报明显减少但推着电动车进电梯时也经常不触发告警来回调试很折磨人。原因电梯里的车经常被遮挡 30% 以上模型输出的置信度天然偏低尤其是只有前轮出现的瞬间置信度往往在 0.3 到 0.45 之间。全局统一的高阈值会把低置信度但正确的检测全部滤掉。解决不要用单一阈值。常见做法是区分场景判定——对大面积完整车身用高阈值0.5对局部可见特征用低阈值0.3配合连续 N 帧确认逻辑。具体到实现就是检测时不设 conf 过滤把所有输出传给上层逻辑由时序模块决定是否告警。5.3 避坑镜像检测框导致的重复告警现象一辆车推进电梯后告警系统在 1 秒内触发了 3 次后台日志显示同时出现了两个重叠度不高但类别相同的检测框。原因不锈钢轿厢壁的镜像被模型识别成了同一个目标。两个框都通过阈值且 IoU 不够高NMS 没有把它们合并。解决在 NMS 之后加一层业务过滤——检测框的坐标如果落在地面反光区域通常是画面下半部分的固定区域且同时存在另一个同类别框就保留置信度更高或框面积更大的那个。“镜像框”和“实体框”在画面上的位置有固定规律电梯内壁在画面左侧时就保留右侧框反之亦然。这个规则写死在业务逻辑里比训练模型去区分镜像可靠得多。5.4 避坑视频抽帧导致的车身撕裂感现象模型在快速推车入电梯时漏检但把整个视频逐帧检测又没问题。原因电梯监控盒子的推理能力有限实际部署时通常是每 3 到 5 帧抽一帧检测。推车动作快时抽帧间隔里车辆已经移动了 20 到 30 厘米关键的单车特征车轮、车把被跨帧略过了。解决把抽帧间隔和检测策略错开。监控盒子空闲时逐帧检测检测到置信度接近阈值的“疑似目标”后切换为连续帧追踪模式直到目标消失或稳定。这种“空闲抽帧 疑似加速”策略在 Jetson 上完全可行比单纯降低抽帧间隔省资源。6. 最后的部署技巧把“告警确认”做成一套可解释的时序规则很多课设和毕设项目做到“模型能检出车”就停了但实际交付的电梯告警系统必须在“检出”和“触发告警”之间加一道闸。老实说模型在单帧上的表现再好到了连续视频里都会暴露出抖动问题——目标在第 3 帧被检出、第 4 帧丢失、第 5 帧又出现。如果每一个检出帧都触告警平台会被噪音淹没。我教学生或做项目时通常把告警判定写成一套简单的状态机核心规则是连续 M 帧内累计出现 ≥K 次有效检测且最近一次有效检测的置信度不低于 C判定为“一次有效告警”。具体数值视现场而定常见的起点是 M5、K3、C0.3。# 告警确认逻辑滑动窗口计数避免单帧抖动触发误报 class ElevatorAlarm: def __init__(self, window5, min_hits3, min_conf0.3): self.window window self.min_hits min_hits self.min_conf min_conf self.history [] def update(self, detections): # 只保留与电动车/自行车相关的检测结果 hits [d for d in detections if d[class] in (e_bike, bicycle) and d[conf] self.min_conf] self.history.append(1 if hits else 0) if len(self.history) self.window: self.history.pop(0) return sum(self.history) self.min_hits # 用法每帧推理后调用 update返回 True 才触发告警 alarm ElevatorAlarm(window5, min_hits3, min_conf0.3) for frame in video_stream(): dets model(frame, verboseFalse) if alarm.update(dets): send_alarm() break这套规则的价值不只是降低误报更重要的是让告警变得可解释。答辩或现场验收时你可以打开日志说清楚“这个告警是因为最近 5 帧里有 3 帧检出了电动车置信度分别是多少”。这比“模型认为有车”要更有说服力对一次现场验收来说就是实质性优势。这类项目做到最后你会发现真正区分“能用”和“能交付”的往往是边界样本的处理电梯里有人推着轮椅模型输出 bicycle 框怎么办有人提着大行李箱反射出金属光泽模型输出 e_bike 框怎么办我现在的习惯是给每个边界情况建一个单独的测试片段文件夹每次模型更新后先跑一遍这些片段再决定是否上线。这比盯着 mAP 数字意义更大。希望你做完这个项目后也能攒下这样一套自己的边界测试集。它会在关键时刻救你一命。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?