ARTICLE DETAIL

资讯详情

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

起重机检测YOLO数据集:从标注格式校验到训练避坑指南

起重机检测YOLO数据集:从标注格式校验到训练避坑指南 简介面向起重机crane目标检测任务提供约2900张已标注图像数据及配套YOLO格式标签类别为单一类别crane适合作为计算机视觉初学者或进阶者的训练数据集。数据已完成训练集、验证集划分可直接用于YOLOv5等主流检测框架的训练与评估也可结合作者发布的改进实战教程进行网络结构优化实验。整个资源压缩包约146.53MB共2000个文件其中1999个为txt格式标签文件另含1个Python脚本用于标签可视化或数据整理便于用户快速检查标注质量与样本分布。目前已有498人浏览学习被用作目标检测入门练习和模型改进的基准数据。拿到压缩包后可获得的不仅是一份标注规范的数据集还包含按YOLO格式存储的边界框坐标、类别编号以及作者预处理脚本能够大幅节省自行采集与标注时间帮助快速启动起重机检测相关研究与工程实践。1. 起重机 crane 图像目标检测一份可直接开训的 YOLO 标注数据YOLO 目标检测项目最耗时间的从来不是调模型而是准备一份能直接开训的标注数据。约 2900 张起重机 crane 图像、单类别、已划分训练集和验证集、统一 YOLO 标注格式——这是我处理过最省心的一类数据集。但省心不等于不用复查文件名带着 Roboflow 导出的哈希后缀标签是归一化坐标的 txt类别索引要对齐 classes 文件。标注格式一错轻则 loss 不降重则训练出来的模型把吊臂当背景。这篇从目录结构拆起讲标注格式怎么读、show.py 怎么复核、四类高频格式坑怎么排查最后落地到 YOLOv5 的 data.yaml 和训练参数适合刚拿到数据准备跑第一次训练的新手也适合想自查标注正确性的熟手。2. 数据集结构拆解文件命名、类别 ID 与训练验证划分拿到数据集第一件事不是解压就跑而是先摸清目录和文件之间的对应关系。这批起重机数据最明显的特征是“一张 JPEG 配一个同名 txt”txt 是 YOLO 格式的标签但文件名里带了一段哈希不看清规则很容易在写 data.yaml 时翻车。2.1 文件对应关系一张 JPEG 配一个同名 txt项目正文列出的文件带典型的 Roboflow 导出痕迹比如n03126707_8705_JPEG.rf.31c56d01198ca7b2ac5c1d1b895409ad.jpg n03126707_8705_JPEG.rf.31c56d01198ca7b2ac5c1d1b895409ad.txt文件名拆成三部分看n03126707是源图像集的类别编号8705是图序JPEG.rf.是 Roboflow 导出时附加的来源标记最后那段 32 位十六进制字符串是 Roboflow 给每个样本生成的唯一 ID。关键结论jpg 和 txt 必须放在同一个目录下且主文件名完全一致因为 YOLO 训练脚本就是靠os.path.splitext换扩展名来找标签的。如果发现某张图片没有同名 txt训练时会弹出WARNING: No labels found in ...。常见原因是导出时漏掉了空标签样本或者是该样本因为没有目标被标注工具直接过滤。处理方法是写一个脚本比对两边文件列表把缺标签的图片单独挪走别让训练集里混入无标注图。提示不要手动去改文件名里的哈希段。YOLO 只关心主文件名一致哈希冲突概率极低改了反而容易破坏 jpg 与 txt 的配对关系。2.2 YOLO 标注格式归一化的 class x_center y_center width height每张图的 txt 文件内容长这样0 0.5123 0.4781 0.3342 0.4156五个字段依次是字段含义取值范围第 1 个类别索引class id0 ~ nc-1nc 是类别总数第 2 个目标框中心点的 x 坐标归一化0.0 ~ 1.0第 3 个目标框中心点的 y 坐标归一化0.0 ~ 1.0第 4 个目标框宽度归一化0.0 ~ 1.0第 5 个目标框高度归一化0.0 ~ 1.0归一化的计算逻辑是x_center_norm (x_min x_max) / 2 / image_width y_center_norm (y_min y_max) / 2 / image_height width_norm (x_max - x_min) / image_width height_norm (y_max - y_min) / image_height这里的除以图像宽高很关键。很多人会写成除以 640因为训练时 imgsz640这是错的。标注时图像是多少像素就除以多少训练时 YOLO 内部会再做一次 letterbox 缩放标签会跟着坐标一起变换不需要在 txt 里提前归一化到 640。如果看到某一行出现0 0 0 0 0说明该标注框的宽或高被算成了零。这种行要直接删掉否则训练时 YOLO 计算 IoU 会出现除零或 NaNloss 曲线呈现诡异的锯齿形。2.3 单类数据集的 classes 文件与 ID 管理摘要里特别注明“类别个数【1】起重机 crane【具体参考 classes 文件】”。单类别数据集最容易犯的错是把类别名直接当 ID 用。YOLO 的 txt 里写的是数字索引不是字符串。classes 文件内容一般是一行一个类名crane这份数据只有一行所以 txt 里所有标签的第一位都应该是 0。如果哪一行出现1就要警惕了要么是标注工具自动补了背景类要么是导出的训练集里混进了其他类。排查方法很简单awk {print $1} *.txt | sort -u正常输出应该只有0。如果出现其他数字用脚本把所有非零首列的行打出来看具体是哪些文件。这类问题在手工合并多个数据源时特别常见crane 类和 tower 类混在一起ID 对不上训练时模型怎么学都学不好。2.4 训练/验证划分怎么核实摘要说已经做了训练集、验证集划分。YOLOv5 的目录分层一般是datasets/crane/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── classes.txt验证划分是否有效不能只看目录里文件的数量要检查三件事第一训练和验证的图片不能有交集用文件名集合求交集即可 第二每个 train/val 目录下图片数和标签数要相等 第三验证集里不能只有简单样本——随机划分下复杂背景、小目标、遮挡场景要均匀分布。如果发现验证集里全是近景大图训练出的模型 mAP 可能虚高换到真实工地场景立刻掉点。校验代码不长import os train_img set(os.listdir(images/train)) train_lbl set(os.listdir(labels/train)) val_img set(os.listdir(images/val)) val_lbl set(os.listdir(labels/val)) print(train img-lbl 差集:, train_img - train_lbl) print(val img-lbl 差集:, val_img - val_lbl) print(train/val 图片交集:, train_img val_img)注意Roboflow 导出默认还会生成_train.txt、_val.txt这类路径清单文件YOLOv5 的 data.yaml 可以直接引用它们也可以不用、只靠目录结构。路径清单文件里的绝对路径换机器后会失效所以我一般建议用目录结构 data.yaml 的相对路径写法。3. 标注可视化复检show.py 脚本的用法与边界2900 张图如果只凭肉眼抽查大概率漏掉坐标越界、标签错位、框尺寸异常这类问题。写一个 show.py 把标注框叠加到原图上是最直接的质检手段。这个脚本在数据集里是自带的但很多人拿到后不知道它能干什么、参数怎么改这一章把它拆开讲清楚。3.1 为什么要自写校验脚本而不是直接开训练YOLO 训练过程中即使标签有问题往往只在日志里打一行 warning训练继续跑最后模型在验证集上的 mAP 惨不忍睹你甚至说不清是模型的问题还是数据的问题。show.py 的核心作用就是在训练前把数据问题暴露出来。脚本的逻辑不复杂读图片 → 读同名 txt → 把归一化坐标还原成像素坐标 → 用 OpenCV 画框和类别文字 → 保存或逐张显示。最大的价值是可以批量跑把有问题的标注以图片形式输出比看日志直观得多。尤其对 crane 这类结构不规则的目标吊臂细长、支撑架镂空标注框稍微偏一点肉眼很容易看出来。3.2 show.py 的代码实现与逐行说明数据包里自带的 show.py 逻辑骨架大致如下具体行数以包内脚本为准import cv2 import os image_dir images/train label_dir labels/train output_dir check_visual os.makedirs(output_dir, exist_okTrue) for img_name in os.listdir(image_dir): if not img_name.endswith((.jpg, .jpeg, .png)): continue img_path os.path.join(image_dir, img_name) label_path os.path.join(label_dir, os.path.splitext(img_name)[0] .txt) if not os.path.exists(label_path): print(f[skip] 缺标签: {img_name}) continue img cv2.imread(img_path) if img is None: print(f[error] 图片无法读取: {img_path}) continue h, w img.shape[:2] with open(label_path, r) as f: lines f.read().strip().splitlines() for line in lines: parts line.split() if len(parts) ! 5: print(f[error] 标签字段数异常: {label_path}: {line}) continue cls_id int(parts[0]) x_center float(parts[1]) * w y_center float(parts[2]) * h box_w float(parts[3]) * w box_h float(parts[4]) * h x1 int(x_center - box_w / 2) y1 int(y_center - box_h / 2) x2 int(x_center box_w / 2) y2 int(y_center box_h / 2) if x1 0 or y1 0 or x2 w or y2 h: print(f[warn] 越界框: {img_name} 类别{cls_id}) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, fcrane:{cls_id}, (x1, max(y1 - 5, 0)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1) cv2.imwrite(os.path.join(output_dir, img_name), img) print(可视化校验图已输出到:, output_dir)四个关键点一是归一化坐标还原时必须用img.shape拿到的原始宽高 h、w不能用训练尺寸。因为标注是在原图上做的只有用原图宽高还原出的像素坐标才能正确画出框。二是越界判断写的是x1 0或x2 w。归一化坐标理论上应该在 0~1 之间但标注工具自动生成时偶尔会产出 1.05 这样的轻微越界值训练时 letterbox 会把它裁掉导致框和目标的边缘对不齐。提前打出来可以决定是修标注还是删样本。三是 OpenCV 的putText在 y1 接近 0 时文字会画到画面外所以写了max(y1 - 5, 0)做下限保护。四是打印信息要分级别缺标签用[skip]坐标问题用[warn]字段异常用[error]。批量跑完后直接按级别筛选日志把问题按严重程度排队处理。3.3 参数改动多类显示、框线粗细与过滤坏标注如果你的使用场景不是单类 crane而是把这份数据集和其他数据合并show.py 要做两处改动。第一处是颜色映射。单类时所有框都用绿色没问题。多类时最好按 class id 生成固定颜色避免同屏多个类别分不清colors [ (0, 255, 0), # class 0 绿色 (0, 0, 255), # class 1 红色 (255, 0, 0), # class 2 蓝色 (0, 255, 255) # class 3 黄色 ] color colors[cls_id % len(colors)]第二处是过滤坏标注。越界的框如果只是轻微超出可以直接画出来供人工判断但 w 或 h 算出来为 0 的框必须单独过滤画出来的话会是一条线影响判断。常见做法是加一个面积阈值if box_w 1 or box_h 1: print(f[error] 尺寸为零: {img_name}: {line}) continue这样跑出来的校验图才不会被无效框干扰。另外注意cv2.rectangle 画框时如果 x2 超过了图片宽度OpenCV 不会报错但画出来的框会被截断所以越界判断必须在画框之前做。提示show.py 默认逐张保存到 check_visual 目录。如果只想抽查而不是全量输出可以加一个sample_ratio参数比如0.2表示每 5 张抽查 1 张省时间省磁盘。我自己跑 2900 张全量也就几分钟但碰到超大数据集时抽样能快很多。4. 标注数据避坑指南四类高频格式陷阱与排查方法跑 YOLO 训练翻车最多的阶段不是训练本身而是数据接入。2900 张图听起来不多但一旦格式问题成片出现改起来足够让人怀疑人生。这一章把我在标注数据上踩过的、以及帮别人排查过的四类高频问题列出来每条都按“现象 → 原因 → 解决”写。4.1 现象训练日志提示 No labels found训练集被跳过训练刚开始终端刷出一排WARNING: No labels found in .../labels/train然后 loss 全程不下降。原因通常是两个目录配对失败。第一种是目录结构写成了 labels 下的子目录层级和 images 不一致比如 images 下有train/和val/两个子目录labels 下却只有train/val 标签全找不到。第二种是 yaml 里train、val路径直接指向了 images 子目录而 YOLOv5 是拿着 images 路径去换 labels 路径目录配错就全盘找不到。解决方法是先确认目录树再用 find 命令比对两边数量find images/train -name *.jpg | wc -l find labels/train -name *.txt | wc -l两个数字必须一致。不一致就拿第 2 章的 Python 脚本定位缺哪个文件缺标签的图片挪出训练目录不要让训练代跑。4.2 现象训练能跑但 loss 曲线在 0.1 附近高位震荡mAP 不超过 0.3loss 能降下去说明流程通但降不到低位多半是标签里有大量低质量框。具体到这份 crane 数据最常见的是两种一是目标框把整个吊臂包含进去但落下了吊钩部分导致学习目标不完整二是归一化坐标里混入了逗号分隔的格式比如0, 0.5, 0.4, 0.3, 0.2YOLO 按空格分割后把0,当成类别索引后面的值整体错位框全画歪。解决方法是写一个解析函数先把每行的逗号去掉再按空格切分兼容两种写法。同时统计每个框的宽高比crane 的框宽高比通常在 0.3~3 之间如果出现 0.01 或 100 这种极端值基本可以判定是格式错位import os for txt_file in os.listdir(labels/train): with open(os.path.join(labels/train, txt_file)) as f: for i, line in enumerate(f, 1): parts line.strip().replace(,, ).split() if len(parts) ! 5: print(f{txt_file}:{i} 字段数{len(parts)}) continue try: vals [float(p) for p in parts] except ValueError: print(f{txt_file}:{i} 无法转float: {line}) continue w, h vals[3], vals[4] if w 0 or h 0: print(f{txt_file}:{i} 宽或高0: {line})4.3 现象训练正常但验证输出把起重机标成了别的类别这种情况在单类数据集里不多见但一旦出现原因大概率在 classes 文件顺序上。YOLOv5 的类别映射不是按文件名排的而是严格按 classes 文件中类名出现的顺序生成索引。如果这份数据集你后来追加了其他类在 classes 文件末尾加了一行新类而某些标签文件里新类的 ID 已经写成了 0那 crane 就会被识别成新类。解决办法是重新统一标签把所有 txt 的类别 ID 按新 classes 顺序重映射。写一个简单的字典映射即可old_to_new {0: 1, 1: 2, 2: 0} # 示例原ID0-新ID1原ID1-新ID2原ID2-新ID0 import os for txt_file in os.listdir(labels/train): path os.path.join(labels/train, txt_file) with open(path) as f: lines f.readlines() with open(path, w) as f: for line in lines: parts line.split() if not parts: continue cls int(parts[0]) if cls in old_to_new: parts[0] str(old_to_new[cls]) f.write( .join(parts) \n) else: print(f[skip] 未知类别ID {cls}: {txt_file})这个脚本在写回前没有加备份。实操建议先cp -r labels labels_bak毕竟改错的成本比一条命令高得多。改完后再用第 2 章的awk命令复查首列分布。4.4 现象图片与标签数量对不上差的正好是坏图解压完 2900 张图数了一遍 jpg 是 2900 个txt 却只有 2896 个。差的 4 个文件对应的图片往往是打不开的坏图、零字节空文件或者命名时带了空格。cv2.imread 在遇到坏图时会返回 None这个在 show.py 里已经兜住了。但如果数据直接进训练YOLO 在数据加载阶段读到 None 就会报AssertionError: img is None。解决方法是先全量清洗一遍import cv2 import os import shutil bad_dir bad_images os.makedirs(bad_dir, exist_okTrue) for root, _, files in os.walk(images): for name in files: if not name.endswith((.jpg, .jpeg, .png)): continue p os.path.join(root, name) img cv2.imread(p) if img is None: shutil.move(p, os.path.join(bad_dir, name)) print(f坏图已移走: {p})零字节文件也可以顺手查find images -type f -size 0 -delete清理之后再跑一次数量比对确保两边的数字一致。4.5 一份可以复用的数据校验清单把上面的坑收拢成固定流程每次拿新数据集都跑一遍比临时排查高效得多第一步数量比对。find统计 images 和 labels 两边的数量训练、验证分开数。 第二步格式解析。检查每行字段数是否为 5、能否转 float、宽高是否大于 0。 第三步类别 ID 范围。awk {print $1}统计出现的所有首列值确保都在 [0, nc-1]。 第四步可视化抽查。跑 show.py 输出 200 张左右重点看小目标、遮挡和远距离的框。提示这一步检查不是只做一次。每次向数据集追加新图或重新标注后都要重新跑一遍。我见过太多人加了图之后直接开训结果新图用了另一种标注工具导出的格式整个训练从头乱到尾。5. 接进 YOLOv5 训练data.yaml、超参与改进方向数据质检通过之后正式训练前的最后一道工序是把它接进 YOLOv5 的工程目录。这章讲 data.yaml 的写法、训练参数的设定规则以及围绕这份 crane 数据可以做的改进参考。5.1 data.yaml 配置路径、nc 与 names在 YOLOv5 的 datasets 目录下建好crane/结构后data.yaml 长这样# 数据集路径相对于 YOLOv5 工程根目录 path: ../datasets/crane train: images/train val: images/val # 类别数单类 nc: 1 # 类别名顺序与 classes 文件保持一致 names: 0: crane几个容易踩的细节train和val填写的是相对path的相对路径不能写成images/train/带末尾斜杠YOLOv5 拼接路径时多个斜杠不影响但某些图像加载库会报路径错误。nc必须和 names 列表长度相等。单类时 nc1names 只有一个元素。有些格式会写成names: [crane]这种列表写法YOLOv5 也能识别但注意索引一定是 0 开始。如果 names 写成了[crane, tower]而 nc 还是 1训练会直接报维度错误。5.2 训练参数imgsz、batch、epochs 与早停以这份 crane 数据跑 YOLOv5s 为例常用命令是python train.py --data data.yaml --weights yolov5s.pt --img 640 \ --batch 16 --epochs 100 --cache --patience 20参数含义不用多讲但有几个针对这份数据的调整经验imgsz建议先 640 起。crane 目标在画面里通常占比较大640 够用。如果后面部署到远距离抓拍场景模型要识别的是画面里很小的塔吊可以试 1280代价是训练显存翻倍、推理变慢。batch以显存为准。单卡 8G 跑 yolov5s、imgsz640batch 16 比较稳。如果显存不够优先降 batch 而不是降 imgsz否则目标太小边界会糊。patience设 20 是防止验证集 mAP 到平台期后浪费算力。crane 是单类模型通常 60~80 epoch 左右就收敛100 epoch 上限够用了。5.3 改进参考efficient head、损失函数与 TensorRT 部署原版 YOLOv5 的检测头对中等尺寸目标表现可以但如果 crane 数据集里有大量远处小目标可以考虑三个改进方向。第一个是换 efficient head。它用更轻量的卷积组合替换原版 Detect 头的部分结构参数量下降小目标召回率略有提升。做法是把模型的 head 模块替换成 EfficientHead 类保持输出维度不变直接复用原训练脚本。注意替换后要重新跑 warmup不能用原模型的 head 权重做 fine-tune 起点。第二个是损失函数。YOLOv5 默认 CIoU loss 对遮挡目标有一定容忍度但起重机吊臂细长、容易被钢缆遮挡可以考虑把 box loss 换为 SIoU它在角度对齐上有额外惩罚项。改一行配置即可把hyp.scratch.yaml中的box0.05保留去utils/loss.py把 CIoU 相关分支换成 SIoU 的实现。改完单独跑一个 20 epoch 的对比实验用验证集 mAP 决定是否保留。第三个是部署侧的 TensorRT。训练收敛后导出 ONNX 再用trtexec --onnx crane.onnx --fp16转 TensorRT 引擎1080p 25 帧的视频流下单张 640 推理耗时可以压到十毫秒以内。但注意TensorRT 的输入尺寸和 letterbox 逻辑必须和训练时完全一致转换前先固定模型输入 shape否则输出坐标整体偏移。6. 部署前的最后一公里验证模型输出与标注一致性模型训练完mAP 再好看落地部署也绕不开“模型输出和原始标注是否对得上”这个问题。YOLO 预测输出的是归一化坐标验证标签也是归一化坐标两者坐标系定义完全一致所以可以直接用一个小脚本把预测框和真实框叠加在同一张图上人工核对。import cv2 import torch model torch.hub.load(yolov5, custom, pathruns/train/exp/weights/best.pt, force_reloadTrue) model.conf 0.25 model.iou 0.45 img cv2.imread(test.jpg) results model(img) pred results.xyxy[0].cpu().numpy() # [x1, y1, x2, y2, conf, cls] for x1, y1, x2, y2, conf, cls in pred: cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), (0, 0, 255), 2) cv2.putText(img, fcrane {conf:.2f}, (int(x1), int(y1) - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) cv2.imwrite(compare.jpg, img)核对标准不是“框是否都在物体上”而是三点一是远处小目标有没有漏检二是目标部分遮挡时框是否过大或过小三是同一场景多台起重机同时出现时框之间是重叠还是各自独立。单类检测如果画面上出现了连续大块红色误检区域基本可以判定训练集里缺了足够多的负样本场景——这一点在扩充数据时要格外留意。我自己的习惯是每次训练收敛后拿验证集里最难的 50 张图跑一遍这个可视化对比再把预测结果和原标注重叠打印出来。从那以后凡是新数据进项目我都强制先走一遍第 4 章的校验清单再做训练。数据没洗干净后面所有改进都是无效功。希望这份 crane 数据集的操作笔记能帮你少走几步弯路。本文还有配套的精品资源点击获取
返回列表