ARTICLE DETAIL

资讯详情

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

电梯开关门与有无乘客检测:610张YOLO/VOC数据集训练实战

电梯开关门与有无乘客检测:610张YOLO/VOC数据集训练实战 简介面向电梯运行监控与智能安防场景的目标检测数据集可用于训练电梯门开闭状态识别与轿厢内有无乘客判断两类任务也方便迁移到楼宇自动化相关视觉项目。压缩包共1832个文件、约27.8MB内含三个文件夹分别存放图片、XML标注和TXT标注610张JPG原图同时配有VOC格式XML与YOLO格式TXT两种主流标注格式可灵活选用省去手动转换。数据集包含关门、空轿厢、开门、轿厢内有人四类标签累计1230个矩形标注框其中开门与有人状态样本占比更高适合研究样本不均衡时的检测表现。图片清晰且未做增强标注经过人工核对格式规范可直接用YOLOv5、YOLOv8等常见框架训练与评测。目前已有160人学习下载适合目标检测初学者、算法工程师及需要电梯场景数据支撑实验或产品验证的开发者。1. 电梯开关门与有无乘客检测610张数据集够不够先看任务粒度在电梯场景里做视觉检测很多人拿到“数据集电梯开关及有无乘客检测数据集yolovoc格式610张.zip”后第一反应是“610张太少”。其实这个判断要看任务粒度如果电梯机位固定、灯光稳定、画幅基本不变610张针对门状态和乘客位置的检测任务完全可以跑通第一版模型。这个压缩包同时给出YOLO和VOC两种标注核心价值是跳过高成本的数据准备直接进模型迭代。它适合三类人想快速验证电梯视觉方案的算法工程师做门状态和乘客检测毕设的学生以及需要给现场测试脚本搭数据底座的部署人员。数据量小不等于做不成关键是把格式、类别分布和训练策略这三件事先想清楚。2. 数据集内幕VOC和YOLO格式到底差在哪标签怎么对得上拿到zip解压后常见目录结构是一套VOC风格目录加一套YOLO风格目录外加一个类别清单。很多新手直接把VOC的Annotations文件夹丢给YOLO训练报错后才回头改格式。这一章先把两种格式的物理结构和语义差异讲透再做一次标签体检避免带着错标签跑上百个epoch还不知情。2.1 一份数据两套标注VOC的XML和YOLO的txt各自长什么样VOC格式的标准目录是Annotations/、JPEGImages/、ImageSets/Main/。每个XML文件里存着图片尺寸、目标类别和边界框坐标边界框用xmin/ymin/xmax/ymax表示左上角和右下角。YOLO格式则简化成每张图一个同名txt文件每行一个目标内容是class x_center y_center width height四个数值均为相对图片宽高的归一化比例范围0到1。这种设计让YOLO系模型读取时不用再解析XML直接进损失函数做回归。先看一个VOC XML的核心结构用Python把它打印出来import xml.etree.ElementTree as ET xml_path VOC2007/Annotations/电梯_001.xml tree ET.parse(xml_path) root tree.getroot() size root.find(size) print(图片宽高:, size.find(width).text, size.find(height).text) for obj in root.findall(object): name obj.find(name).text 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) # VOC格式左上角右下角YOLO需要中心点宽高 x_center (x1 x2) / 2 / int(size.find(width).text) y_center (y1 y2) / 2 / int(size.find(height).text) w (x2 - x1) / int(size.find(width).text) h (y2 - y1) / int(size.find(height).text) print(f{name}: xc{x_center:.4f} yc{y_center:.4f} w{w:.4f} h{h:.4f})这段代码做了最基本的换算把VOC的左上角和右下角转成YOLO的中心点加宽高。注意两个坑一是float()转换不能省VOC里存的是字符串二是最终归一化时的分母要用size里的宽高不能用图片本身的像素尺寸因为标注可能基于缩放后的图像。如果直接写成int超过1000像素的边界坐标会被截断导致框偏移。YOLO的txt文件内容通常是这样的0 0.452158 0.533333 0.368697 0.722222 2 0.780134 0.504630 0.118279 0.585185第一列是类别ID后面四列是归一化的中心点x、中心点y、宽、高。这个文件没有图片尺寸信息因此YOLO训练时会把txt和图片配套读取图片尺寸由imgsz参数缩放归一化坐标天然兼容不同分辨率。正因为归一化同一份txt可以配合不同尺寸的图训练这也是YOLO格式比VOC更适合深度学习框架直接读取的原因。2.2 610张图的类分布与场景分布先做一次标签体检不要拿到标注就开训。先统计类别数量这一步能发现很多隐藏问题。写一个简单的统计脚本扫描YOLO格式的标签目录import os from collections import Counter labels_dir YOLODataset/labels/train cnt Counter() for txt in os.listdir(labels_dir): with open(os.path.join(labels_dir, txt), r) as f: for line in f: cls_id line.split()[0] cnt[cls_id] 1 class_names [door_open, door_closed, person] for idx, name in enumerate(class_names): print(f{name}: {cnt.get(str(idx), 0)} 个目标框)运行后如果发现door_open比door_closed少很多说明正负样本失衡严重如果person框只有几十个那“有无乘客”这个任务基本不可靠。需要明确610张图不等于610个有效样本如果某个类别只有几十个框模型很难学到稳定特征。这时候要么补充该类别数据要么先用数据增强硬撑同时调整损失函数里的类别权重。单纯靠batch重复采样效果有限。场景分布也要体检。电梯场景最怕的是模型记住背景而不是记住门和人。理想的610张数据应该覆盖不同楼层按钮背景、不同光线、不同摄像头高度。如果文件名是摄像头抓拍的连续帧还需要检查连续帧是否占比过高——如果610张里有300张来自同一段视频的连续截图模型看到的其实是同一个场景的不同位移泛化性会打折扣。可以用下表对照自己的数据情况覆盖维度理想情况只有610张时的底线光线条件白天/黑夜/开门时灯光变化至少覆盖两种光照方向摄像头视角正对/侧对/俯视至少两种相机安装高度门状态过渡半开/全开/全闭/关门瞬间至少包含半开样本用于关键帧识别乘客姿态站位/坐轮椅/抱小孩/带行李有乘客样本不作强制但必须承认这是最短板如果发现某个维度完全缺失训练前先做定向采集比训练后补错更省时间。比如没有半开门样本模型就会把半开门当成人这种错误在电梯监控里是很尴尬的。3. 把VOC转成YOLO格式并划分训练集转换脚本与四个边界坑数据集自带了YOLO格式可能不需要你自己转换。但很多老项目原本只有VOC标注或者需要把新采集的电梯数据合并进来掌握转换逻辑能让你不被格式绑架。这一章给出一个可复用的转换脚本然后讲清楚划分训练集时最容易被忽略的四个边界坑。3.1 从xml到txt转换脚本与归一化坐标计算建议写一个通用脚本遍历Annotations目录对每个XML生成对应的txt同时保留一份类别清单。下面的脚本可以直接跑import xml.etree.ElementTree as ET import os from pathlib import Path def convert_voc_to_yolo(xml_dir, txt_dir, class_names): os.makedirs(txt_dir, exist_okTrue) class_to_id {name: i for i, name in enumerate(class_names)} for xml_file in Path(xml_dir).glob(*.xml): tree ET.parse(xml_file) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.findall(object): name obj.find(name).text if name not in class_to_id: print(f跳过未知类别: {name} in {xml_file.name}) continue 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) # 防止边界越界剪辑到图像范围内 x1 max(0, min(x1, img_w)) x2 max(0, min(x2, img_w)) y1 max(0, min(y1, img_h)) y2 max(0, min(y2, img_h)) if x2 x1 or y2 y1: print(f跳过无效框: {xml_file.name} {name}) continue xc ((x1 x2) / 2) / img_w yc ((y1 y2) / 2) / img_h w (x2 - x1) / img_w h (y2 - y1) / img_h lines.append(f{class_to_id[name]} {xc:.6f} {yc:.6f} {w:.6f} {h:.6f}) if lines: txt_path os.path.join(txt_dir, xml_file.stem .txt) with open(txt_path, w) as f: f.write(\n.join(lines)) # 示例调用 convert_voc_to_yolo( xml_dirVOC2007/Annotations, txt_dirYOLODataset/labels/train, class_names[door_open, door_closed, person] )脚本做了四个防御动作过滤不在类别清单里的标签、修剪越界框、跳过宽高为0的无效框、打印异常信息。第四个防御很重要如果某个XML里的坐标写反了x2 x1会把它拦下来而不是等训练时算出一个负数宽带去污染损失函数。如果你的zip里包含没有标注的背景图对应txt文件不存在是正常的读取数据时跳过即可。3.2 训练集/验证集划分不要按文件夹随机要按电梯场景划分常见错误是直接把所有图片打乱按8:2随机划分。但电梯监控视频是连续帧相邻帧的差异可能只有几个像素随机划分会让同一条视频的多个片段同时出现在训练集和验证集模型在验证集上看到的样本和训练集高度相似mAP虚高一到现场就露馅。正确做法是以场景组为单位划分同一台电梯、同一天、同一段连续视频算一个场景组划分时保证场景组不重叠。如果zip内的文件名带电梯编号和时间戳可以按前缀分组。假设文件名格式是elevator_03_20240512_143200.jpg写一个分组脚本import random from pathlib import Path image_files list(Path(images).glob(*.jpg)) scene_groups {} for img in image_files: parts img.stem.split(_) scene_key _.join(parts[:2]) # 电梯编号 日期作为场景组 scene_groups.setdefault(scene_key, []).append(img) scene_list list(scene_groups.keys()) random.seed(42) random.shuffle(scene_list) split_idx int(len(scene_list) * 0.8) train_scenes scene_list[:split_idx] val_scenes scene_list[split_idx:] train_imgs [img for s in train_scenes for img in scene_groups[s]] val_imgs [img for s in val_scenes for img in scene_groups[s]] print(f训练集 {len(train_imgs)} 张验证集 {len(val_imgs)} 张场景不重叠)这个思路能避免数据泄露。另一个容易忽略的地方划分完要检查每个场景组在所有类别上都有出现否则验证集可能没有person类那“有无乘客”这个任务的验证指标就是空转。如果发现验证集缺少某个类别可以把包含该类别的场景组强制加到验证集再重新随机分配其余场景。3.3 数据校验可视化标注把张冠李戴扼杀在训练前转换和划分之后必须先可视化抽查这是zui直接的校验手段。用OpenCV把YOLO标签画到原图上import cv2 import os label_dir YOLODataset/labels/train image_dir YOLODataset/images/train class_names [door_open, door_closed, person] color_map {door_open: (0, 255, 0), door_closed: (0, 0, 255), person: (255, 0, 0)} for txt in os.listdir(label_dir)[:30]: img_path os.path.join(image_dir, txt.replace(.txt, .jpg)) img cv2.imread(img_path) h, w img.shape[:2] with open(os.path.join(label_dir, txt), r) as f: for line in f: cls_id, xc, yc, bw, bh map(float, line.split()) x1 int((xc - bw / 2) * w) y1 int((yc - bh / 2) * h) x2 int((xc bw / 2) * w) y2 int((yc bh / 2) * h) color color_map[class_names[int(cls_id)]] cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.putText(img, class_names[int(cls_id)], (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 1) cv2.imshow(check, img) cv2.waitKey(0) # 按任意键下一张 cv2.destroyAllWindows()看三件事门框是否贴住门板而不是包住整个电梯墙面半开状态是否被标成door_open乘客框是否框住人而不是框住一个黑影。如果发现标签大量不对齐这个数据集在使用前需要先修标签否则训练出来的YOLO模型会把门缝当人或者把乘客当成门上的装饰。可视化代码也可以顺手输出标注统计图比如每类的框宽度分布用来判断anchor设计是否合理。4. YOLO训练电梯检测模型的参数调优与常见问题避坑有了干净的数据集接下来就是跑训练。这一章不重复基础命令行专注两个问题参数怎么设不翻车以及610张小数据集下哪些YOLO坑最容易出现。电梯场景看似简单实际训练里的玄学一点都不少。4.1 预训练权重与模型选型yolov5s还是yolov8s先看显存和速度电梯检测属于固定相机场景不需要超大模型。优先选yolov5s或yolov8s。如果只做门状态加人检测yolov5s足够如果还要做乘客属性分析或后续接时序模型再考虑yolov8m。预训练权重建议从官方渠道下载对应版本的yolo预训练模型下载不要用不明来源的“魔改版”否则模型结构和代码版本不匹配第一轮就可能遇到bn崩溃。yolov5s大约14M参数在1080Ti上batch16 imgsz640一个epoch能跑进80秒。如果显存只有8G把imgsz降到512batch降到8训练时间不会增长太多因为电梯图像内容本身比较单一。4.2 训练参数epoch、batch、imgsz、损失函数怎么设不翻车610张图建议训练150到200个epoch。少于100个epoch模型还没收敛超过250个epoch验证集容易过拟合。开启预训练权重后收敛速度会明显加快。数据增强要克制电梯门框是垂直结构上下翻转和旋转90度会破坏“门”的语义建议关闭mosaic以外的旋转增强只保留缩放和亮度调整hsv的饱和度、明度、色相偏移分别设到0.01、0.01、0.02。损失函数方面YOLO默认的CIoU损失已经够用不必额外引入EIoU除非你的标注框是细长门缝需要重点优化宽度误差。另一个关键参数是anchor。电梯门是扁宽的框乘客是细高的框默认anchor对这两类都有覆盖但如果你发现门框的recall低可以用自己的标注数据跑一次k-means重算anchor再写进模型的anchor配置里。注意重算anchor时要用训练集的全部框而不是采样一部分否则小类别的框会被忽略。训练前先准备好数据配置一个最小的data.yaml如下# data.yaml train: YOLODataset/images/train val: YOLODataset/images/val nc: 3 names: [door_open, door_closed, person]train和val路径建议写绝对路径避免不同操作系统下的斜杠问题。nc必须和类别数一致names顺序必须和标签ID对应。如果你用的是yolov8字段名略有不同可能需要写成train和val加names但核心结构一致。yaml写错会在第一个epoch报错先校验再开训。训练命令行参考python train.py --data data.yaml --weights yolov5s.pt --epochs 200 --batch-size 16 --img 640 --device 0 --workers 4重点参数说明--batch-size受显存限制8G显存建议8--img是训练分辨率不要直接拉到1280电梯门框不需要那么大--workers在Linux下可以设8Windows下设0或2否则DataLoader会报错。训练过程中盯住val/objectness和每个类的mAP曲线如果前50个epoch指标一直不动先检查数据加载是否正常再看预训练权重是否真的加载进去了。4.3 避坑清单数据集只有610张时最常见的5个翻车点以下按“现象 → 原因 → 解决”记录都是真实场景里反复出现的坑。现象1训练到一半loss变成nan随后所有指标归零。原因学习率过大或者某个标注框太小导致梯度爆炸。解决把初始学习率从0.01降到0.001并确保开启warmup。如果还出现nan检查损失函数里是否有无效框干扰必要时把上一章的无效框过滤逻辑加进数据加载前。现象2验证集精度很高但拿到真实电梯里把门上的反光条当成门。原因训练集里门框边界和背景反光区域太接近模型学到了纹理而不是结构。解决补充背景负样本或者把门框标注得更紧让绿框不要包住周围墙面。本质上这不是模型问题是标注边界问题。现象3person类一个都检不出来。原因610张图里乘客框数量太少类别不平衡极端。解决复制少数类样本做mosaic增强或者挂载COCO预训练权重——这类权重已经见过大量人迁移到电梯小样本数据上冷启动效果比从头训练好很多。如果数据集里完全没有乘客那就该明确这个数据集的“有无乘客”只能靠门状态间接推断别硬凑。现象4验证集上混淆矩阵总合不唯一某个类大量被归为背景。原因很多门框本身就是背景的一部分且背景没有参与损失计算模型无法区分“门框”和“门框上的纹路”。解决输出混淆矩阵png看具体错分到哪个类针对错分样本采集更多半开状态或者在损失函数里对door_open类调整权重。混淆矩阵总合不唯一并不代表训练出错关键看错分的绝对值有没有收敛。现象5YOLO训练中bn崩溃loss出现周期性抖动。原因batch太小BN统计量不稳定特别是小目标较多的数据。解决batch至少8起步显存不足时用梯度累积来等效放大batch同时固定随机种子让结果可复现。注意1080Ti 8G显存跑yolov5s如果imgsz640 batch16可能刚好爆显存先调整batch再考虑换模型。5. 验证与进阶地图指标之外用视频帧序列测出真实鲁棒性5.1 错检漏检的定位混淆矩阵和置信度阈值怎么调训练结束后不要只看mAP。先运行验证集生成混淆矩阵。如果door_open和door_closed互相混淆说明模型没抓住门缝的物理特征需要补半开状态样本。如果乘客被识别成门说明类间特征重叠。此时可以调节置信度阈值门状态检测建议阈值不低于0.5乘客检测可以降到0.3因为电梯是固定机位错检的风险远小于漏检。用验证脚本分别对两个阈值跑一轮对比误报率和召回率选现场可接受的那个点。5.2 连续帧决策电梯门状态检测要加时序平滑实际部署时单帧检测会抖动。常见做法是用3到5帧做投票连续3帧检测到door_open且置信度超过阈值才判定门开中间偶尔漏一帧用前后帧插值补上。这比一味调高置信度阈值更经得起现场考验因为电梯门有物理惯性不可能在50毫秒内完成开合。乘客检测同理人站在门中间可能被部分遮挡用多帧跟踪框做IoU关联能稳定“有无乘客”的判定。我一般会在推理脚本里加一个deque保存最近5帧的类别序列当某种状态计数大于等于3时更新终态。这个技巧看起来简单但能把误报率压下一半以上。训练完先拿一段没见过的电梯视频做回放测试别急着上硬件这一步才真正检验数据集的含金量。希望帮到你。本文还有配套的精品资源点击获取
返回列表