
简介面向人工智能与计算机视觉方向的学习者及毕业设计开发者这一压缩包围绕电梯监控视角下的电动车与自行车识别任务提供了一套可运行、可复现的AI视觉项目方案。资源共134个文件以YAML配置、Python脚本、Jupyter教程、Dockerfile以及图像样本为主整体压缩包仅16.96MB其中YAML与py文件负责模型定义与训练流程ipynb适合分步调试和复现实验Dockerfile支持一键搭建运行环境方便跨平台部署。目前已有158人学习下载。内容覆盖监控图像预处理、卷积神经网络构建与训练、验证集评估指标分析、推理结果可视化展示并附带results.csv等输出样例便于对照模型调参与优化。项目还涉及光照变化、遮挡等实际场景鲁棒性处理能让读者完整理解从数据标注、模型训练到推理部署的关键环节对毕业设计或实际工程落地均有较高参考价值也适合作为目标检测任务入门到进阶的学习素材。1. 这个 zip 解决的是电梯里的低频高危事件不是普通识别任务看到“用于识别电梯监控视角内的电动车以及自行车.zip”这个包名第一反应是物业终于不想让保安盯着 16 块监控屏找电动车了。近两年不少城市把“电动车禁入电梯”写进物业和消防要求单靠人盯根本不现实于是这类识别包成了弱电改造里的热门方向。解压后它要干的事很直接把电梯轿厢摄像头画面实时拉进来框出电动车和自行车按置信度触发语音提醒或门控联动。这个 zip 适合三类人小区智能化改造的集成商、物业弱电工程师以及想拿目标检测练手的算法新人。你搜“电梯监控视角”“识别”多半也是想知道它到底能不能用、怎么落地。但先泼盆冷水电梯里做目标检测和街道车流检测完全是两码事。它要在一个两米见方的封闭空间里用俯拍广角镜头认两轮车还得控制在极低误报率下触发联动。如果你把它当普通 YOLO demo 跑大概率会被反光、形变和遮挡打回来。下文按“场景约束→数据→训练→推理→踩坑”的顺序拆开讲。2. 电梯视角为什么不能用路边抓拍的模型场景约束决定选型2.1 三个硬约束近景大目标、斜俯拍、开关门光变先把电梯摄像头当成一个独立的视觉识别问题来分析。轿厢高度一般在 2.2 到 2.6 米摄像头多半装在门口上沿或角落向下斜着覆盖整个轿厢。这意味着画面主体不是水平方向的车流而是近处的顶面和人的肩部。第一硬约束就是近景大目标一辆电动车推进入口时车占画面宽度的 30% 到 50%和 COCO 数据集里远处车辆那种小目标完全不是一个量级。模型能不能识别物体很大程度取决于训练时见过的尺度分布所以用官方 COCO 权重直接去 predict 轿厢画面要么框得过大要么漏掉。第二个约束是斜俯拍带来的形变。车身在画面里经常是前轮大一倍、后轮小一半的透视状态或者直接只有车头冲镜头。目标检测模型对角度变化有一定的泛化能力但如果你收集的训练数据全是正侧视角那俯视 45 度角进来的车就会变成“玄学识别”。第三个约束是开关门瞬间的光变。电梯门一开走廊灯和自然光突然涌进轿厢摄像头自动曝光还没跟上画面会过曝一到两秒门关上后轿厢内荧光灯又让色温变冷。这种短时光变对普通检测模型干扰很大常见做法是把训练样本按不同时间段抽帧让模型把亮度差异当成噪声而不是特征。这也是为什么我一再强调不要拿网上的电动车图片硬凑训练集。网上素材大多在平视、晴天、均匀光照下拍摄和电梯里的低照度、广角畸变完全是两套分布。你手里的 zip 如果已经带了预训练权重那只是起点真正落地前必须用这栋楼、这台摄像头、这个电梯间的录像做一轮迁移学习否则“能跑”和“能报警”之间差着十条街。2.2 电动车和自行车类别边界不清才是误报根源从技术上看这个项目最容易出问题的不是“有没有车”而是“什么是电动车、什么是自行车”。电梯监控里最常见的两种误报一是外卖骑手的国标电动车它有脚踏板、车身细长后轮上有个不起眼的电机视觉上和山地车几乎一样二是共享单车车架颜色鲜艳轮胎细容易被识别成自行车。如果 zip 里的标签只有“电动自行车”但实际现场跑的时候把轮椅、婴儿车、清洁手推车全框出来了别急着怪模型先看类别定义是不是太含糊。我一般给客户使用的标注规则是有电池仓或明显电机轮/后轴凸出的归为电动车骑上去没有链条的也归为电动车只有链盘、细轮圈且看不出电池仓的归为自行车被遮挡到无法判断的样本干脆不标。关键点是所有标注人员必须用同一份规则否则类别边界就是标签噪声模型学到的是“标注员手滑的规律”而不是真实视觉特征。如果 zip 里预训练的类别只有一类“两轮车”报警逻辑上把电动车和自行车合并反而更稳因为物业关心的是“推车进电梯”不管是电动的还是脚踏的都不该进门控联动只需要一个布尔信号。另外类别不平衡在这类项目里特别常见。小区电梯里自行车出现频次低电动车频次高训练时如果正样本数量差太多可以用类别权重或把少样本做轻度复制但不要因此把所有样本强行均衡。少样本类别如果镜头角度单一模型会对这个角度过拟合换个电梯就失灵。2.3 模型怎么选YOLOv8n 起步YOLOv5 做备选当你决定不沿用 zip 里默认模型或者想从头训练时选型第一原则是“跑得通比指标重要”。我通常先试 YOLOv8n 或 YOLOv8s原因是它们导出 ONNX 和 TensorRT 的过程顺在 Jetson 老平台上踩坑少YOLOv5s 在现成工程里仍然大量存在如果你的 zip 脚本是基于 v5 写的不需要强行迁到 v8。模型优点电梯场景适用点YOLOv8n体积小、推理快、显存占用低对 Nano 或老 TX2 友好电梯目标大精度足够YOLOv8s比 n 更稳对遮挡和形变容忍度更高适配 Xavier/Orin 等主流板卡YOLOv5s老工程兼容好TensorRT 适配资料多现场已有 v5 推理框架时优先复用不建议一上来就上 YOLOv8x 或大体积检测头。电梯里目标大、环境简单大模型的收益很小部署成本和功耗却成倍上涨。训练阶段用少量样本验证时你甚至会发现 YOLOv8n 在电梯场景的 mAP 和 x 差不多原因是图像内容本身就简单难点在视角适配和阈值策略。迁移学习的做法大家应该不陌生用公开默认权重作为初始权重保留 backbone 提取能力微调检测头。这里的坑是预训练模型里的“车”类别大多是轿车、卡车对两轮车的特征表达先天不足所以不要只训几十个 epoch 就赶着上线。一般我会先冻结 backbone 跑 20 个 epoch 看 loss 有没有降再解冻全部参数跑 80 到 120 个 epoch直到验证集 mAP 稳定。3. 从 zip 到能跑的训练集先盘目录再抽帧和转格式3.1 解包后先看这些文件拿到 zip不要急着双击运行先解压看一下工程结构。这类识别项目的常见布局是一个weights/目录放训练好的.pt权重或.engine文件data/里放数据集配置 yamltrain.py和detect.py是训练与推理入口requirements.txt列依赖。如果你发现 zip 里只有一个权重和一段推理脚本缺了训练代码那也一样能用你只需要基于现成的检测器做二次开发不一定要从零训练。重点检查三个东西数据配置里的类别名是否包含e_bike和bicycle推理脚本里的摄像头输入是rtsp://还是本地文件权重文件是 PyTorch 格式还是 TensorRT engine。前两项决定你能不能直接跑起来最后一项决定你跑在 CPU 还是 GPU 上。很多人栽在最后这里只有一个.engine文件换了张显卡就不能加载因为 TensorRT engine 和 CUDA 架构强绑定。如果在 zip 里看到的是.pt那才是相对通用的起点。3.2 用 ffmpeg 从电梯监控录像里抽帧数据准备阶段最好的素材就是现场监控录像。让物业导出 24 小时甚至一个星期的电梯录像最好是包含早中晚、开关门、晴天阴天的片段。抽帧命令我常用这个ffmpeg -rtsp_transport tcp -i rtsp://user:pass192.168.1.64:554/stream1 \ -vf fps1,scale1280:720 -q:v 2 -frames:v 5000 frames/%04d.jpg这条命令从 RTSP 实时流里按每秒 1 帧抽图统一缩放到 1280×720质量参数-q:v 2表示高质量。如果手头是本地录像文件改用按帧间隔抽取ffmpeg -i elevator_2024_11.mp4 -vf selectnot(mod(n,30)),scale1280:720 frames/%04d.jpgselectnot(mod(n,30))表示每 30 帧取 1 帧也就是在 25fps 的录像里每 1.2 秒取一张。电梯事件节奏慢不需要每帧都留抽太多反而让相近帧在训练集里高度重复验证集会有“数据泄漏”的假高分。抽完帧后务必人工过一遍。一个 5000 张的候选集里真正值得标注的可能只有 1000 到 2000 张。模糊的、反光到看不出车体的、电梯空着的、人被车完全挡住的都删掉。删得越狠后面训练越省事。这一步没有捷径但我可以给个血泪经验用 ffmpeg 抽帧时如果画面出现马赛克多半是 RTSP UDP 丢包命令里必须加上-rtsp_transport tcp换成 TCP 传输能解决 80% 的抽帧花屏问题。3.3 VOC/COCO 标注转 YOLO txt三个边界坑标注阶段通常不会直接用 YOLO 的 txt 格式标注工具导出的往往是 VOC XML 或 COCO json。这里需要一个转换脚本。我一般这样写import os, glob import xml.etree.ElementTree as ET CLASSES [e_bike, bicycle] def convert_voc_to_yolo(xml_path, out_path): root ET.parse(xml_path).getroot() w int(root.find(size/width).text) h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in CLASSES: continue cls_id CLASSES.index(name) box obj.find(bndbox) xmin max(0.0, float(box.find(xmin).text)) ymin max(0.0, float(box.find(ymin).text)) xmax min(float(w), float(box.find(xmax).text)) ymax min(float(h), float(box.find(ymax).text)) bw xmax - xmin bh ymax - ymin if bw 0 or bh 0: continue x_center ((xmin xmax) / 2) / w y_center ((ymin ymax) / 2) / h w_norm bw / w h_norm bh / h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {w_norm:.6f} {h_norm:.6f}) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines)) for xml_file in glob.glob(annotations/*.xml): out_txt os.path.join(labels, os.path.basename(xml_file).replace(.xml, .txt)) convert_voc_to_yolo(xml_file, out_txt)逻辑不复杂但三个坑必须注意。第一YOLO 格式的坐标是归一化中心点必须在分母上除以图片宽高有些脚本换算的时候忘了除框就会全部跑到图外。第二VOC 坐标可能有“1-based”和“0-based”两种平台差异转换前先确认工具导出时减没减一统一以像素为单位截断到[0, w]和[0, h]避免出现宽或高为负的坏框。第三CLASSES的顺序必须和后面data.yaml里的names完全一致否则训练时类别名和索引错位训完了输出标签全对不上。如果是 COCO json转换时多一步COCO 的 bbox 是[x, y, width, height]且坐标系从左上角开始需要先把 x、y 当作 xmin、ymin再算xmax x width。不要拿 VOC 的解析习惯去套否则框的左上角会错位。3.4 验证集要按时间片划分训练集和验证集怎么分很多教程没讲清楚。常见做法是不按随机切分而是按录像的时间顺序切前面 80% 的帧做 train后面 20% 的帧做 val。原因是电梯画面高度相似一个推车动作的连续几帧几乎一模一样随机切分会把同一事件的前后帧同时放进训练集和验证集验证集的 mAP 虚高上线后立刻现原形。find frames -name *.jpg | sort all.txt head -n 4000 all.txt train.txt tail -n 1000 all.txt val.txtsort默认按文件名排序抽帧脚本一般会按时间生成序号所以这个顺序就是时间顺序。如果录像跨越多天更应该保证 val 包含完整的不同时段不要只拿一天的尾部否则模型在白天场景过拟合晚上光线一变就翻车。4. 本地跑通最小训练与推理参数这样调才像电梯场景4.1 imgsz、batch、epoch 的推荐起点第一次跑训练不要再沿用默认参数电梯场景有一些特殊设置。我常用的最小训练命令如下yolo detect train \ datadataset/data.yaml \ modelyolov8n.pt \ epochs120 \ imgsz640 \ batch16 \ device0 \ workers4 \ patience20 \ close_mosaic10 \ scale0.4 \ hsv_h0.015 \ hsv_s0.5 \ hsv_v0.4参数说明逐条看。imgsz640是平衡点电梯目标大降到 480 能提速但会让过曝后的细微特征丢失升到 1280 对近景几乎没有收益耗显存却翻倍。batch16取决于显存6G 显卡建议 812G 可以 16报 OOM 就减半不用硬撑。scale0.4是我针对电梯场景的偏好数据增强里尺度变化不要太大因为真实场景目标占比稳定过度缩小会把自行车缩成一个小点模型反而学会找“小目标”到了现场又误检。close_mosaic10表示最后 10 个 epoch 关闭马赛克增强。马赛克增强在通用目标检测里很有效但电梯画面近景大目标马赛克拼出来的样本可能把两辆车的组件拼在一起产生一堆“四不像”训练样本最后阶段关闭它能让模型回到真实的单目标分布上收敛。hsv_h、hsv_s、hsv_v控制颜色增强强度我把色相增强调低到 0.015饱和度和亮度保持在 0.5/0.4这样模型不至于因为夜间偏色而不敢亮。4.2 自己写一个检测循环而不是光用命令行训练完后yolo detect predict可以快速跑个效果但电梯联动场景需要的是持续帧输入和报警逻辑所以我一般会写一个小检测循环from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) def check_frame(frame): results model.predict(frame, conf0.5, iou0.5, imgsz640, verboseFalse) for r in results: for box in r.boxes: cls_id int(box.cls[0]) score float(box.conf[0]) name model.names[cls_id] x1, y1, x2, y2 [float(v) for v in box.xyxy[0]] print(f{name} {score:.2f} ({x1:.0f},{y1:.0f})-({x2:.0f},{y2:.0f}))conf0.5是电梯报警场景的起点。YOLO 默认阈值是 0.25但联动场景误报代价很高如果语音警报天天响保安最后会把设备关掉调到 0.5 宁可漏掉一些低置信度目标也要保证报出来的都是真的。iou0.5是 NMS 阈值控制重叠框合并一般不用动。verboseFalse则是防止每帧输出一堆日志刷屏导致延迟。这个循环里还可以加连续帧确认下一章会专门讲。4.3 导出成 ONNX/TensorRT 之前先确认三个事情调试好之后要往嵌入式设备上部署第一步是导出 ONNXyolo export modelruns/detect/train/weights/best.pt formatonnx opset12 imgsz640 dynamicFalsedynamicFalse是第一个要注意的坑。原来想用动态尺寸做多分辨率结果在旧版 TensorRT 上反复报“无效维度”后来直接固定640×640稳定性好很多。电梯里的目标分布变化不大固定输入尺寸对精度影响很小。第二个坑是导出前的推理阈值。export 过程不保存置信度阈值真正控制阈值是在部署代码里设置的。不要以为导出就能像命令行一样带conf参数TensorRT 拿到的是原始输出过滤逻辑要自己写。第三个坑是 TensorRT 的精度选择。有 NVIDIA 设备时用trtexec转 engine半精度通常够trtexec --explicitBatch --fp16 --onnxbest.onnx --saveEnginebest.engine--fp16在 Jetson 上能带来约 30% 到 40% 的提速电梯场景不需要双精度放心开启。但如果你在未知设备上部署先查一下 GPU 架构是否支持 FP16例如老款 TX1 的 FP16 性能弱强行开可能反而慢。5. 避坑电梯识别最常见的 5 个翻车现场5.1 RTSP 拉流失败H.265 与设备鉴权现象ffmpeg 或推理脚本连接摄像头时报Connection timed out或Unauthorized偶尔连上也经常花屏。原因有两类。一是摄像头默认视频编码是 H.265很多开源库对 H.265 的解码支持不完整而电梯设备厂商默认反而喜欢开 H.265 省带宽。二是 RTSP 地址里的用户密码包含特殊字符没有做 URL 编码导致鉴权失败。解决在拉流地址前指定-rtsp_transport tcp并在摄像头后台把编码改为 H.264。如果密码里有或:先做百分号编码再放进 URL。我遇到最多的是 H.265 问题花屏重传一次能看到但高帧率下丢帧严重换 H.264 后立即稳定。5.2 轿厢反光和高光让模型瞎认现象白天电梯门打开时模型突然把地面反光里的人影框成“自行车”或者对过曝区域里的车漏检。原因是摄像头安装在金属门框旁边门开瞬间阳光直射镜头画面高光溢出导致检测网络在饱和度接近 0 的区域提取不到有效特征。解决分两层。数据层抽帧时不要只抽正常时段专门把正午和傍晚过曝片段也标注进去并保留“有反光但没有车”的负样本增强层把hsv_v和hsv_h调高一点模拟过曝变化。现场层建议把摄像头宽动态或背光补偿打开让曝光曲线更平稳。这不是模型能单独解决的摄像头成像质量有时候占了 50%。5.3 轮椅、婴儿车、清洁手推车全被框出来现象报警广播频繁触发后台抓图一看是轮椅或保洁的手推车。原因是这些物体都有轮子、金属骨架、座面和自行车的外形特征高度重叠。检测模型学的是视觉形状不是物理语义。尤其婴儿车侧面看框架加车轮确实很像自行车。解决先加负样本专门收集轮椅、婴儿车、手推车进电梯的录像标注成背景让模型学会“不激活”。再加规则过滤电梯里这些误报目标的框普遍宽大于高而自行车和电动车是长条形可以在后处理里加一个长宽比条件宽高比大于 0.9 的框降权或丢弃。不要只调置信度否则会把低置信度的真车也一起滤掉。5.4 自行车和电动车在侧后视角互相混现象摩托车正前方开过来框出来的类别是“自行车”共享单车斜放时模型又标成电动车。原因是在特定角度下电动车的电池仓被车身挡住轮胎粗细差异被俯拍视角压缩两类目标视觉可分性本身就低。尤其外卖车有脚踏板是最难区分的。解决一个是规范标注另一个是合并类别。如果你只在报警联动场景里用两类是不是分开并不重要把data.yaml里改成“两轮车”模型代之以统一检测误报反而下降。要硬分也行但要用多角度的样本把每个类别喂足单独做一次训练来验证分类头是否真的学出了电池仓特征。5.5 模型在 Jetson 上掉帧和延迟飘现象在电脑上跑 30ms 一帧部署到 Jetson Nano 上变成 500ms报警延时要一两秒。原因是把图像从摄像头读进来后逐像素复制到 GPU 显存的流程在 CPU 上执行每帧都做BGR-RGB和 resizeCPU 早就满了。另一个坑是每次推理都调model.predict()内部重复创建预处理临时对象内存抖动导致延迟不稳定。解决推理前先把摄像头帧放到固定内存用cv2.dnn.blobFromImage或 ultralytics 的predict(streamTrue)保持预热TensorRT 引擎固定成imgsz640并限制检测频率。电梯里的人或车是慢动作不需要每帧都推理我一般设置每隔 500ms 取一帧把结果缓存住既省功耗又能稳定报警节奏。忘掉“实时”两个字这里“准实时”就够了。6. 把模型接入电梯门控之前先做这 3 个验证第一个验证是用回放录像而不是实时流跑一遍完整逻辑。取一周的录像把每帧的识别结果、置信度、触发报警的时间戳都记录到 CSV然后人工对一遍哪些是电动车哪些是误报漏检出现在哪个时段。这一步能拿到比 mAP 更重要的指标——误报率和漏报率。电梯场景我一般要求误报率每周不超过一次漏报率接近零。第二个验证是连续帧确认。单帧误报是随机的真车进电梯时会在连续多帧里都被检测到。用计数器做确认ALARM_FRAMES 3 alarm_count 0 for frame in camera_frames(): if has_vehicle(model, frame): alarm_count 1 else: alarm_count max(0, alarm_count - 1) if alarm_count ALARM_FRAMES: send_alarm() # 取得连续 3 帧确认再报警 alarm_count 0这段逻辑的效果是单帧误检不会触发连续三帧出现目标才报能过滤掉画面抖动、反光闪烁带来的偶发噪音。第三个验证是模型健康检查。部署环境里显卡驱动或显存泄漏会让推理悄悄卡死我在交付前会写一个定时任务每 10 分钟喂一张固定测试图如果输出为空或显存占用异常就自动重启推理进程。这个习惯帮我在一次电梯改造中避免了一个大坑系统跑了 36 小时后显存泄漏摄像头画面还开着但识别任务已经死了如果没做健康检查物业第二天就会收到“检测系统没用”的投诉。开发这类识别项目我最大的教训是不要过度相信模型指标也别只调阈值。电梯监控视角的难点从来不在算法有多新而在你有没有认真处理过曝光、形变和“看起来像车但不是车”的边界样本。跑 72 小时压力测试观察每一夜的光照变化比多训 50 个 epoch 有用得多。希望这套拆解思路能帮你在自己的项目里少走一段弯路。本文还有配套的精品资源点击获取