拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

电梯开关及乘客检测数据集:从YOLO/VOC格式到实战避坑指南

电梯开关及乘客检测数据集:从YOLO/VOC格式到实战避坑指南

简介:一套面向电梯场景目标检测任务的数据集,包含电梯开关门状态以及轿厢内有无乘客两类核心识别维度,适合训练安防监控、智能楼宇或电梯运维场景中的视觉检测模型。资源共610张清晰拍摄的jpg图片,并同时提供VOC格式的xml标注和YOLO格式的txt标注,使用者可依据框架直接切换,无需额外转换格式。压缩包总体积约27.8MB,内含1832个文件,其中jpg图片610张、xml标注610个、txt标注612个,四类标注分别为closed_elevator、empty_elevator、open_elevator、people_in_elevator,累计矩形框1230个,各类别样本分布较均衡,可用于模型训练与验证。已有160人学习下载,适合有一定目标检测基础、需要标准格式数据快速开展训练或算法评测的开发者使用。

1. 电梯开关及有无乘客检测数据集:610 张图凭什么够用

如果你负责楼宇智能化或安防项目,大概率会卡在同一个问题上:项目方要“电梯门开关状态 + 轿厢有没有人”两个结果,用来做梯控联动和困人告警,但现场视频不能直接外发训练,手里只有一台相机和两周交付时间。这时候一份像“数据集电梯开关及有无乘客检测数据集yolo+voc格式610张.zip”这样的数据包就会很有吸引力:它把两个任务合并成一整套标注,并且同时给出 yolo 和 voc 两种格式,省去你从头标框再互转格式的半天功夫。610 张图不算多,但对“门开/门关/有人”这类状态判别任务来说,只要场景覆盖到位、框标得干净,完全够做一次及格线以上的电梯检测基线,再靠现场数据迭代。这个标题真正能帮你解决的问题,是把“电梯门开关”与“有无乘客”变成两个可训练的目标检测任务,而不是让你拿一套通用模型硬上。

2. 先读标注再定类别:yolo+voc 双格式数据集的体检清单

拿到任何检测数据集,我的习惯都不是直接开训练,而是先把目录结构和标签文件读一遍。很多项目翻车都是因为类别名和实际含义对不上,或者标注文件和图片不在同一层目录,YOLO 训练器报了 None 还不知道错在哪。

2.1 第一件事是拆包看目录结构,而不是直接开始训练

电梯这个场景里,标注对象通常只有三类:门开、门关、人。有的数据集会把门框、楼层按钮也标进去当作辅助类,也有的会把“有人”直接写成 person。先做一次目录体检,能看到图片和标签是否一一对应,也能确认 yolo 和 voc 两份标注是不是同一个版本。

cd elevator_dataset find . -maxdepth 3 -type d | sort echo "--- 图片数量 ---" find . -name '*.jpg' | wc -l echo "--- 标签数量 ---" find ./labels -name '*.txt' | wc -l echo "--- 类别定义 ---" cat labels/classes.txt

这个命令只是看包的结构。图片数量和标签数量应该大致相等,如果图片 610 张、txt 只有 580 份,说明有 30 张图漏标或标注文件没生成完整。classes.txt 是 YOLO 项目里常见的类别清单,一行一个类别名,顺序就是类别索引。读到这三类时,要先想清楚 door_open 和 door_close 的边界:门开到一半算开还是算关?电梯里有人但门正在关,person 和 door_close 能不能同时出现?

2.2 三个类别怎么定义:door_open、door_close、person 的边界

电梯门开关检测最容易踩的第一个坑是边界定义不一致。有人在标注时把门缝超过 20 厘米记为 open,有人把门完全静止才算 open,还有人把门套区域整个框进 box。同样 610 张图,三种标准训练出来的模型行为完全不一样。所以拿到这份数据,我先建议你打开几张有代表性的图,把框和图片叠起来看,不要只信 classes.txt 里的名字。

常见做法是:door_open 框住两侧门扇打开后露出的通口区域,door_close 框住闭合后的门缝和门板重叠区域,person 框住人体可见部分,包括被门挡住一半的躯干。如果标注里还有 elevator door 这种父类,就把它当背景处理,或者合并到 door_open/door_close 里。这里有一个小经验:门在中间状态时,按地面投影和门扇夹角来判断,夹角大于约 30 度就算 open,小于 30 度算 close。配置文件里后面要不要做类别合并,取决于原数据标注粒度。

2.3 用一段 bash 统计各类别数量,防样本严重偏斜

610 张图片看着少,如果三类分布变成 500 张关门、80 张开门、30 张有人,那训练结果大概率是开门和有人全部漏检。先把类别数量统计出来,心里有数再谈训练策略。

for f in labels/train/*.txt labels/val/*.txt; do awk '{print $1}' "$f" | sort | uniq -c done | awk '{count[$2] += $1} END {for (c in count) print c, count[c]}' | sort -k2 -n

这个统计把每个 txt 文件里出现的类别索引逐行读出来,再用 awk 按类别累加。如果 labels 目录没有按 train/val 分,先去掉路径前缀,直接labels/*.txt跑一遍。看到的结果如果是 door_close 500、door_open 300、person 200 这类比例,说明原始数据做过一定的均衡,可以直接用。如果 person 只有 40 个框,就要考虑在训练参数里给 person 加类别权重,或者干脆用“有人/没人”作为二分类,减少正样本不足的压力。

3. 把 VOC XML 转成 YOLO TXT:兼容脚本与三个归一化细节

标题既然给了 yolo+voc 两种格式,工作流里最常见的需求是把 VOC 当原始真值,把 YOLO 当训练输入。VOC 格式存的是xmin,ymin,xmax,ymax绝对坐标,YOLO 格式要的是归一化后的x_center,y_center,width,height。两者之间的转换不难,但细节错一个,整个训练就在用错框。

3.1 一个可以直接跑的 VOC 转 YOLO 脚本

下面的脚本按单张图片转换,xml 和图片同名不同后缀,遍历目录就能把整套 VOC 转成 YOLO。

import os import xml.etree.ElementTree as ET CLASSES = ["door_open", "door_close", "person"] # 顺序必须与 classes.txt 完全一致 def convert_voc_to_yolo(xml_path, txt_path): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") width = float(size.find("width").text) height = float(size.find("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") x1 = float(box.find("xmin").text) y1 = float(box.find("ymin").text) x2 = float(box.find("xmax").text) y2 = float(box.find("ymax").text) x_center = (x1 + x2) / 2.0 / width y_center = (y1 + y2) / 2.0 / height w = (x2 - x1) / width h = (y2 - y1) / height # 做出界保护:坐标略超图片范围就按边界钳制 x_center = max(0.0, min(1.0, x_center)) y_center = max(0.0, min(1.0, y_center)) w = max(0.0, min(1.0, w)) h = max(0.0, min(1.0, h)) if w < 0.001 or h < 0.001: continue # 面积几乎为 0 的框直接丢弃 lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(txt_path, "w") as f: f.write("\n".join(lines)) if __name__ == "__main__": xml_dir = "voc_xml" out_dir = "yolo_txt" os.makedirs(out_dir, exist_ok=True) for xml_file in os.listdir(xml_dir): if not xml_file.endswith(".xml"): continue base = xml_file[:-4] convert_voc_to_yolo( os.path.join(xml_dir, xml_file), os.path.join(out_dir, base + ".txt") )

这段代码里,CLASSES 列表顺序就是最终 txt 文件里的数字索引。如果把 door_open 放在 index 0、door_close 放在 index 1、person 放在 index 2,那每行开头是 0、1、2。转换时以 xml 里的 size 节点为基准,因为有的视频抽帧会把原图缩放后再标注,直接用图片实际尺寸读取更保险。钳制部分看起来多余,但电梯监控边缘经常有画面裁切,标注工具偶尔会把框拉到画幅外,钳制之后至少不会让 YOLO 训练报 box 越界的错。

3.2 类别索引、尺寸读取和边界钳制:最容易错的三个参数

每个参数都有一次踩坑机会。类别索引错是最隐蔽的,转换出来的 txt 数字和 classes.txt 对不上,训练时 loss 照常下降,但输出类别永远是错的。解决方法是转换后随手跑一次head -3看每一行首位数字,再回看 xml 的 object name 确认。

尺寸读取错一般发生在图片是竖屏的电梯轿厢内景时。xml 里 width=1080, height=1920,但有些代码写成float(size.find("width").text)和float(size.find("height").text)时忘了 height,顺序反过来,人就从站在原地变成了横躺在电梯里。第三处容易错的是 w 和 h 的钳制,如果框完全在画面外,x_center 钳到 0,w 钳到 0,这个框会退化成一个点,YOLO 训练会出现 loss 突跳。脚本里w < 0.001的判断就是对它做的过滤。

3.3 回画标注框验证转换结果:用 OpenCV 做一次目检

数字对不对,最后要用眼睛确认。转换完随便挑三十张图,把 txt 里的归一化坐标还原成坐标画上去,如果框和电梯门缝、人体轮廓贴合,转换链路才算通。

import os import cv2 def draw_yolo_box(image_path, txt_path, classes, out_path): img = cv2.imread(image_path) h, w = img.shape[:2] with open(txt_path) as f: for line in f: parts = line.strip().split() if not parts: continue cls_id, xc, yc, bw, bh = parts x1 = int((float(xc) - float(bw) / 2) * w) y1 = int((float(yc) - float(bh) / 2) * h) x2 = int((float(xc) + float(bw) / 2) * w) y2 = int((float(yc) + float(bh) / 2) * h) color = (0, 255, 0) if cls_id == "0" else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, classes[int(cls_id)], (x1, y1 - 8), cv2.FONT_HERSHEY_SIMPLEX, 0.8, color, 2) cv2.imwrite(out_path, img)

这里 cls_id 是字符串,转 int 后再去 classes 列表里取名字。打印时顺便把框的面积列出来,如果看到大量宽度不超过原图宽度 3% 的竖条框,那多半是标注只框了门缝没框门板,后面训练时模型会把细缝当成唯一特征,最后变成“见了缝就报开门”。

4. 用 YOLOv8 训练电梯场景:数据集切分、data.yaml 与关键超参数

格式准备妥当后,进入训练环节。610 张图不至于要用大模型硬扛,YOLOv8n 或 YOLOv8s 已经足够。关键在于把数据集结构切好,让训练器知道哪张图带哪份标签,再把超参数调成“小数据集模式”,否则模型会高方差地乱跳。

4.1 切分目录与 data.yaml:让 YOLOv8 直接读 610 张图

同一份数据,YOLOv5 和 YOLOv8 都希望 images 和 labels 按 train/val 分成两个大目录,txt 和 jpg 名字一致。我一般按 8:2 切,也就是 488 张训练、122 张验证。不搞独立测试集,因为 610 张本身就紧,留出测试集后训练数据只剩四百多张,效果波动太大。

mkdir -p images/train images/val labels/train labels/val python3 split.py # 按 8:2 随机划分,并把 txt 同步移动

切分脚本没什么玄学,按文件名随机撒种子就行,重点是要保持图片和标签相同比例移动。电梯数据有个特殊性:连续帧不能同时落进训练集和验证集,否则模型看到的是同一扇门在不同角度下的重复帧,验证 mAP 虚高。如果这份数据集原始来源是按视频抽帧的,建议先把帧号排序,按时间窗口切,而不是纯随机切。

data.yaml 写清楚路径和类别名,yolo 训练命令读它即可。

path: /home/user/elevator_dataset train: images/train val: images/val names: 0: door_open 1: door_close 2: person

names必须与 labels/classes.txt 顺序一致。这里最容易犯的错是 val 路径写成 images/val 但标签文件实际还在 labels/val,YOLOv8 默认会直接把 images/val 下同名 txt 去找,如果不在就报 label 缺失。目录结构里 images 和 labels 是兄弟目录,训练器会自动去 labels/ 下匹配。

4.2 输入尺寸、batch 与 epoch:小数据集最容易表现突兀的三个旋钮

电梯门的宽高比和普通目标不太一样,门开时目标接近一个竖长方形,门关时是一个窄条,person 又是一个明显的竖向目标。imgsz 给 640 能照顾中距离摄像头视角,但不要盲目推到 1280,因为 trainer 内部会对图片缩放,原图里 600 像素高的门在 1280 下并不会多出更多语义信息,反而让训练变慢、batch 变小。

参数推荐值说明
imgsz640兼顾门框与人体细节
batch16~32显存够就用 32,小于 8 容易 BN 抖动
epochs80~120用早停控制,不固定跑满
patience10~15验证 mAP 连续不涨就停
lr00.005~0.01预训练权重下不宜过大
warmup_epochs3小数据集让 BN 先热起来

batch 是 610 张图里最关键的一个参数。电梯场景往往只有一台不贵的 GPU,显存 8G,很多人顺手设成 batch=4,结果训练 loss 能降但验证集表现随机,因为 BN 层在极小 batch 上计算的均值和方差太抖。常见做法是 batch 至少 16,如果显存不足就把 imgsz 降到 480,而不是把 batch 压到 4。epochs 则要看早停脸色,通常 610 张图 80 轮内就收敛了,跑满 300 轮只会过拟合到电梯轿厢的固定纹理上。

4.3 预训练权重和冻结 backbone:小数据集的成熟方案

我建议直接用 YOLOv8n.pt 或 YOLOv8s.pt 开始,而不是随机初始化。电梯门和人的特征在 COCO 预训练权重里已经学得不错,micro 权重在 610 张图上更快收敛。这里有一个容易误导新手的点:预训练权重不等于直接跳过训练,只是给了模型一个更好的起点。

yolo train data=elevator.yaml model=yolov8n.pt \ epochs=80 imgsz=640 batch=16 patience=12 \ lr0=0.005 warmup_epochs=3

yolov8n.pt如果本地没有,训练器会自动下载预训练权重。训练时需要理解几个关键位置:lr0 控制整体学习率,在小数据集上过高会导致 loss 在验证阶段爆掉;warmup_epochs 让学习率从很小的值爬升,BN 层的统计量能先稳定下来。如果动手能力强,前 10 轮可以先冻结 backbone,只训练检测头,让模型先专注学习电梯门的边框分布,第 10 轮之后解冻 backbone 做整体微调。610 张图不足以让 backbone 重新学习通用视觉特征,但足以微调它去关注门缝和人体轮廓。冻结部分在 YOLOv8 里没有直接的一行参数,需要改 trainer 逻辑,一般我会在项目里这样处理:前 10 轮把 backbone 参数 requires_grad=False,之后再置回 True,这样能明显减少过拟合。

4.4 用混淆矩阵和 mAP 看这两个任务的真实表现

训练结束后不要只看总体 mAP,那会把问题藏住。电梯项目真正要看的,是 door_open 与 door_close 之间的混淆程度,以及 person 帧上面的漏检率。Ultralytics 训练完之后会在 runs/detect/train 下生成 confusion_matrix.png,打开它检查“door_open 被预测成 door_close”的格子是不是非零值密集。

yolo val model=runs/detect/train/weights/best.pt data=elevator.yaml

验证命令跑完后,重点看两个数:一个是各类别的 mAP50 和 mAP50-95,另一个是验证集图片上门框框的可视化结果。如果 door_open 的 mAP 高但框势明显整体偏移,说明标注边界把门套也包进去了,模型学到的是门套特征而不是门洞特征。person 这个类在轿厢内通常比在候梯厅好检测得多,因为背景简单、人体站姿完整;如果 person 的 mAP 出奇低,多半是样本数量和遮挡问题,后面靠现场补帧比靠调参快。

5. 电梯场景训练避坑记录:门缝阴影、镜面误检与 BN 崩溃

这套任务看起来简单,实际训练时最容易翻车的几个点都藏在“电梯环境”本身,而不是模型结构里。下面五条是我在多轮实战里反复踩过的坑,每条都按照现象、原因、解决的顺序写清楚。

5.1 开门与关门互相混淆:门缝反光和标注框越界成了“同一个特征”

现象:训练集 mAP 在 0.85 以上,验证图里大门关闭时模型却报出 door_open,而且框的位置都在门缝中线附近。原因:电梯轿厢门关闭时中间会有一条竖直反光线,标注时如果把 door_close 的框画得过大,把门缝反光区域也包进去,模型学到的就是“一道亮线”而不是“两片门板合拢”。解决:把 door_close 的框收窄,只包左右门板重叠区域;door_open 的框放宽到门洞两侧,并配合数据增强里的随机亮度变化。如果原标注已经带了这个毛病,别急着重新标 610 张,先用一个小脚本把关闭状态框的宽度缩到原来的 80%,代价略高但比重标快得多。

5.2 乘客被镜面电梯壁误检:人在反射区域出现多框

现象:ushaped 的轿厢里有三面镜子,实际只有一个人站在中间,模型却输出两个 person 框,一个在人体上,一个在镜面反射里。原因:YOLO 训练时把镜面中的人影也当成了正样本,推理阶段镜面里的残留人影会产生高置信度响应。解决:先看数据集里镜面场景占多少比例。如果只是少数几张,加一两种镜像翻转增强没有用,反而让人影更强。有效做法是把镜面里的标注框去掉,或者在训练 loss 里指定 ignore 区域,更简单的手段是部署端做时序去重:同一目标相邻两帧的 IoU 大于阈值且位置在镜面区域,直接丢弃一个框。这条坑说明 610 张数据集不会自带“电梯壁反光”这个语义,要靠推理端补。

5.3 mAP 在第三个 epoch 突然掉到 0:小 batch 的 BN 崩溃

现象:前两个 epoch loss 下降正常,第三个 epoch 验证 mAP 归零,再往下跑偶尔能恢复但整体像心电图。原因:batch 太小加学习率偏大,BN 层统计量在小批量上剧烈波动,模型参数一次更新过大后开始发散。这是 yolo 训练过程中典型的小数据集不稳定场景。解决:把 batch 从 4 提高到 16 以上,lr0 降到 0.005,并加 warmup_epochs=3。如果显存实在不够,把 imgsz 降到 416,不要降 batch。另一个后悔药是训练时开启cos_lr=True,让学习率平滑下降而不是阶梯跳变,对 BN 抖动有明显抑制。

5.4 验证集挺好、部署翻车:低照度在 610 张里根本没出现

现象:本地验证集 mAP 稳定在 0.9,接上电梯井道相机后关门检测频繁漏报,画面拉亮一看全是夜间低照度、门缝边缘有运动模糊的帧。原因:610 张原始图片大部分是白天或均匀光照,模型没有见过电梯厅昏暗场景,过拟合到特定亮度分布上。解决:不要指望调 epoch 修这个问题,先把摄像头位置和光源条件搞清楚。如果现场是红外补光,把训练集做灰度化和亮度抖动,再在验证时把输入从 BGR 转 GRAY 后拉大对比度。还可以盯着 loss 曲线看,如果 val loss 在 40 epoch 后开始上升而 mAP 还在微升,说明模型已经开始背训练图像,此时立刻用早停把 best.pt 切回来。

5.5 标签里出现 0 面积框:用脚本先踩平再训练

现象:训练日志出现大量corrupt box警告,loss 直接变成 nan。原因:格式转换时有个别 XML 的 bndbox 坐标 xmin 等于 xmax,或者 ymin 大于 ymax,把这些脏框当正样本参与训练就会让损失函数无法计算。解决:训练前跑一遍清洗脚本,凡是 w 或 h 小于等于 0 的框直接扔掉,坐标超过图片边界的钳制到 0~1 之间。跑完之后重新统计每一个 txt 的行数,和 xml 的 object 数量对比,差异集中在某几张图上就手动打开看一眼,大概率是标注工具导出时漏了小数位。

6. 用 610 张数据集守住部署边界:阈值调整与场景扩展

数据集小,模型能做的提升有限,但部署端面仍有很多腾讯飞起的余地。最好的落地方式,是把测试视频流切成帧,用训练好的权重做一次批推,逐帧看置信度分布,再确定报警和联动阈值。

6.1 用测试视频回放调整置信度阈值

YOLOv8 默认 conf=0.25,对 610 张训练集来的模型并不是默认最合适。电梯门状态这种二值任务,宁可让 door_open 的阈值低到 0.15 换召回,也不要把 conf 拉到 0.5 以上导致门开到一半时漏报。person 的阈值则要略高,因为误报会造成门口卡顿的联动动作。

yolo predict model=best.pt source=lift_clip.mp4 conf=0.2 iou=0.4 imgsz=640

跑完看结果视频,重点观察“图像中无人的镜面反射”和“门板关闭瞬间的阴影”这两类帧。如果门开后两秒才第一次出现框,把 conf 降到 0.15;如果关门但框还残留超过半秒,增加 iou 阈值到 0.5,把重叠框合并掉。

6.2 电梯门动作,不减半帧率的时序一致性过滤

单帧检测很容易发生门开状态在某一帧闪断,部署端最简单的办法是加一个长度为 3 到 5 帧的滑动窗口,门开关状态必须连续两帧保持一致才允许翻转。人有没有这个判断同样不能只看当前帧,可以在两帧之间加一个tracking IoU判定,给框分配一个最小存活帧数,少于两帧的框当作噪声丢弃。这样做对 610 张原训练集来说等于免费提升了一个点的稳定性。

这段时间几次做下来,我自己的习惯是:任何小数据集模型上线前,先挑一段 10 分钟现场视频按 30 秒一段拆成帧,用验证集和测试帧各跑一遍,把输出的 JSON 结果按帧号对齐,人工核对所有误报和漏报帧,再将这类帧回灌进训练集做一轮增量微调。电梯开关和有无乘客这类任务,真实死角往往不是模型结构,而是你根本没把现场亮度和镜面反射当成第一优先级来对待。610 张能搭出可用基线,但别停在基线里,把避坑记录和阈值调整都写上项目文档,后面每次加数据都能少踩一遍同样的坑。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表