
简介这份数据集面向起重机目标检测应用场景提供移动式起重机Mobile_crane的完整标注样本。资源包中采用VOC与YOLO两种主流格式包含完整的689张清晰jpg图片、689个xml标注文件和约690个txt标签文件文件总数2000个压缩包整体34.35MB。标签种类仅Mobile_crane一类全体图片共框出764个矩形目标且未做数据增强能保留原始场景的多样性因此可省去自行转换标注的工程步骤。目录按JPEGImages、Annotations、labels三个文件夹分区图片、标注、标签一一对应便于直接接入YOLO、Faster R-CNN等常见训练框架也适合做训练集/验证集划分与格式对比实验。目前已有170人浏览学习对于正在搭建工程车辆检测模型或学习目标检测标注规范的开发者而言这套双格式数据集能帮助快速验证算法效果减少数据准备阶段的重复工作。1. 起重机数据集只有 689 张为什么这个规模反而够用做吊装作业安全检测的同行大概率都经历过类似的尴尬网上能找到的起重机数据集要么是国外港口码头的远机位俯拍要么是塔吊标准节的特写和你现场要识别的汽车吊、履带吊完全对不上。真正到项目验收时甲方要的是吊钩附近有人就报警而不是让你在演示文稿里放几张 PASCAL VOC 的经典飞机照片。这份起重机数据集 yolovoc 格式压缩包689 张图像其实是典型的小样本工业数据集形态。它解决的问题不是训练出一个通用大模型而是让检测模型在你的特定机位、特定背景、特定光照下不翻车。YOLO 和 VOC 两种格式都打包进去意味着你不必为了换训练框架而重新标注一遍省下的时间足够你多调两轮锚框参数。注意689 张不是让你直接拿去训练就完事。这个规模的数据正确用法是配合数据增强、迁移学习和严格的验证集划分。如果你把它当成 ImageNet 级别的数据量上来就跑上百个 epoch过拟合几乎是必然的。下面各章我会按格式转换—数据划分—训练配置—踩坑排查—交付验证的顺序把这 689 张的潜力完整榨出来。2. 解析 YOLO 和 VOC 双格式先搞清楚两套坐标系再动手2.1 两套格式的底层差异归一化框和绝对坐标的换算陷阱YOLO 格式的标签文件是纯文本每一行对应一个目标结构是class_id x_center y_center width height其中四个数值全部除以图像宽高做了归一化。VOC 格式则是一整个 XML 文件记录的是未归一化的绝对像素坐标xmin,ymin,xmax,ymax。这两套体系最大的坑在于坐标原点。YOLO 的x_center、y_center是相对于图像左上角计算而 VOC 的xmin、ymin同样基于左上角但一个是中心点加宽高一个是边框对角点。如果你写脚本转换时把xmin直接当成x_center训练出来的模型预测框会全部偏移到图像右下方并且 loss 下降得异常顺利这是最阴险的翻车方式。看一个具体的转换脚本从 VOC 转 YOLO 是工业界最常走的路径因为标注工具默认导出 VOC训练框架默认吃 YOLOimport os import xml.etree.ElementTree as ET def voc_to_yolo(xml_path, out_dir, class_list): tree ET.parse(xml_path) root tree.getroot() img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): cls_name obj.find(name).text if cls_name not in class_list: continue cls_id class_list.index(cls_name) box obj.find(bndbox) xmin float(box.find(xmin).text) ymin float(box.find(ymin).text) xmax float(box.find(xmax).text) ymax float(box.find(ymax).text) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h width (xmax - xmin) / img_w height (ymax - ymin) / img_h lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}) txt_name os.path.splitext(os.path.basename(xml_path))[0] .txt with open(os.path.join(out_dir, txt_name), w) as f: f.write(\n.join(lines)) if __name__ __main__: class_list [crane, hook, worker] # 按数据集中实际类别写 xml_dir VOC/Annotations out_dir YOLO/labels os.makedirs(out_dir, exist_okTrue) for xml_file in os.listdir(xml_dir): if xml_file.endswith(.xml): voc_to_yolo(os.path.join(xml_dir, xml_file), out_dir, class_list)这段脚本的核心是三步读图像宽高、解析目标框、做归一化。注意我在除法时同时用了 2.0 和图像宽高Python 的整数除法在 Py2 时代会直接截断虽然 Py3 没有这个问题但保留浮点除法的写法是习惯避免将来移植脚本时踩坑。2.2 labels 和 images 目录必须严格配套文件名不一致是第一个坑很多新手解压数据集后直接把图片和标签全部平铺在一个文件夹里然后开始训练。YOLO 的训练逻辑是根据 images 里的图片路径去同名.txt文件里找标签如果找不到标签文件默认当作背景图参与训练。这份数据集的 689 张图片里一旦有几十张文件名为crane_001(1).jpg这类带括号的副本文件标签文件名对不上模型就会把这些图当作负样本导致检测头被污染。我拿到压缩包后的第一个动作永远是跑一个名字匹配校验import os img_dir images label_dir labels img_files [f for f in os.listdir(img_dir) if f.endswith((.jpg, .jpeg, .png))] label_files [f for f in os.listdir(label_dir) if f.endswith(.txt)] for img in img_files: stem os.path.splitext(img)[0] if stem .txt not in label_files: print(f缺少标签: {img})跑完这一步如果报错数量超过总图片数的 1%先别急着训练回到标注软件里补标签或者干脆删掉这些没有标签的图像。用残缺数据训练出来的模型在吊车吊臂边缘会产生大量误检框且置信度分布非常诡异。2.3 类别映射表必须固定临时改 id 等于毁掉训练记录另一个常见的低级错误是类别顺序不统一。VOC 的 XML 里name是字符串YOLO 的 txt 里是整数。你第一次用{crane: 0, hook: 1, worker: 2}转出了一批标签训练到一半觉得worker应该排前面又改成{worker: 0, crane: 1, hook: 2}然后接着训练——模型的前向输出保持不变但 loss 计算时 ground truth 的类别索引全乱了表现是验证集 mAP 突然崩到 0.1 以下。正确做法是生成一份classes.txt并锁死顺序同时把它提交到 git 仓库。后续不管谁接手这份数据集第一件事就是读这个文件。3. 689 张的训练集划分与数据增强策略小样本的核心在于防过拟合3.1 按场景划分而不是随机划分吊臂朝向和光照分布决定验证集的代表性689 张图像看起来不多但如果随机划分很可能把同一台吊车、同一角度的连续帧全部塞进训练集而验证集全是另一台吊车的远机位照片。这样训练曲线会非常漂亮验证集 mAP 却一塌糊涂典型的训练集和验证集分布不一致陷阱。我一般会先把 689 张图按照文件名前缀或拍摄批次粗分组例如crane_day_01.jpg到crane_day_60.jpg视为同一批次然后整批划分。训练集、验证集、测试集的比例按 7:2:1 分配但要注意测试集必须包含至少一种训练集里没出现过的机位角度否则测试分数没有参考意义。划分脚本用scikit-learn的train_test_split配合分组参数实现或者更简单按文件名的数字序号取模import os import shutil img_dir images train_dir train/images val_dir val/images test_dir test/images os.makedirs(train_dir, exist_okTrue) os.makedirs(val_dir, exist_okTrue) os.makedirs(test_dir, exist_okTrue) for f in sorted(os.listdir(img_dir)): if not f.endswith(.jpg): continue stem os.path.splitext(f)[0] num int(stem.split(_)[-1]) # 假设文件名是按序号递增的 label stem .txt if num % 10 0: shutil.copy(os.path.join(img_dir, f), test_dir) shutil.copy(os.path.join(labels, label), test/labels) elif num % 10 in (1, 2): shutil.copy(os.path.join(img_dir, f), val_dir) shutil.copy(os.path.join(labels, label), val/labels) else: shutil.copy(os.path.join(img_dir, f), train_dir) shutil.copy(os.path.join(labels, label), train/labels)按序号取模是偷懒做法但在文件已经按拍摄时间排序的前提下这能模拟时间维度的分布。如果文件名没有任何有序特征回到按批次手动分组的方案别偷懒。3.2 数据增强参数怎么设Mosaic、随机透视和 HSV 扰动对 689 张小样本数据集YOLOv8 的默认增强参数通常过猛尤其是 scale 开到 0.5 时吊钩在图像里本来就只占几十个像素被随机缩放后可能直接缩小到 3 个像素标注框比一个锚点还小。我通常会把增强参数收敛到以下范围mosaic: 0.5因为 Mosaic 会大幅改变目标尺度分布小目标被拼接后更加难以学习。scale: 0.3限制缩放幅度保证吊钩的最小尺寸不低于训练输入尺寸的 2%。hsv_h: 0.015工业现场的黄色吊臂对色相偏移极其敏感偏移过大导致颜色失真模型容易学到错误的颜色关联。flipud: 0.0起重机吊臂场景中上下翻转会导致物理语义错误吊钩在主钩和小钩之间的空间关系完全颠倒。degrees: 10只允许小角度旋转因为吊臂通常垂直或接近垂直旋转超过 45 度会产生现实不存在的姿态。这些参数背后的逻辑是数据增强的目的不是把数据集扩大而是让模型对真实场景中可能出现的亮度变化、遮挡、视角偏移更鲁棒。过度增强会制造大量非真实样本模型在验证集上表现尚可一到现场就露馅。3.3 迁移学习是 689 张的正确打开方式加载预训练权重而非从零训练小样本数据集加随机初始化权重几乎等于赌博。YOLOv8 官方提供的yolov8n.pt在 COCO 上预训练过虽然 COCO 没有起重机类别但底层特征提取器已经学会了边缘、纹理、颜色块等通用视觉特征。直接加载预训练权重做微调收敛速度会快得多。在这个项目上我通常直接用 YOLOv8 的 Python API 开始训练from ultralytics import YOLO model YOLO(yolov8n.pt) results model.train( datacrane.yaml, epochs80, imgsz640, batch16, lr00.01, lrf0.01, mosaic0.5, scale0.3, hsv_h0.015, flipud0.0, degrees10, patience15, projectruns/train, namecrane_exp1, pretrainedTrue, )关键参数解释lr00.01是初始学习率预训练权重微调时0.01是安全起点低于这个值收敛太慢高于这个值会瞬间破坏预训练特征。patience15表示连续 15 个 epoch 验证集指标不提升就提前终止对 689 张小数据集来说这个值能有效避免过拟合后的无效训练。4. 从 689 张到可部署模型训练命令、评估指标与导出格式4.1 写对 crane.yaml路径、类别数和验证集的对应关系训练前必须写好数据集描述文件否则 UltraLytics 框架根本找不到标签。一个最基本的crane.yaml长这样path: /your/abs/path/crane_dataset # 数据集根目录 train: train/images # 训练图像相对路径 val: val/images # 验证图像相对路径 test: test/images # 测试图像相对路径可选 nc: 3 # 类别数量和 classes.txt 保持一致 names: 0: crane 1: hook 2: worker注意path最好写成绝对路径。相对路径在单机实验时可能没问题但一旦换机器或者用 Docker 跑路径错了会直接报AssertionError: Label Not Found。这个报错信息其实很有迷惑性它不一定说明标签文件缺失更可能是路径前缀拼接错误。4.2 训练过程怎么看loss 曲线和验证集指标的判读方法训练跑起来之后不要只盯着终端输出。YOLO 每轮会打印box_loss,cls_loss,dfl_loss以及precision,recall,mAP50,mAP50-95。对 689 张的小数据集正常现象是 mAP50 在前 10 个 epoch 内冲到 0.8 以上然后缓慢爬升。如果你的 mAP50 在 20 个 epoch 后还在 0.5 以下问题大概率出在标签质量而不是模型容量。更值得关注的是mAP50-95。这个指标对边界框的 IoU 敏感吊钩这种小目标天然吃亏。如果 mAP50 已经到 0.9而 mAP50-95 只有 0.4说明预测框偏移量大需要检查锚框尺寸是否匹配目标分布。YOLOv8 会自动学习锚框但如果数据集里吊钩的标注框普遍小于 10x10 像素你需要在crane.yaml中配置anchors参数或者干脆调大imgsz到 960让小目标在特征图中的响应区域变大。4.3 导出为 ONNX 和 TensorRT部署端的格式转换和精度损失控制训练结束后导出部署格式是必经之路。我用 ONNX 导出做跨平台验证用 TensorRT 做 GPU 部署的加速版本model YOLO(runs/train/crane_exp1/weights/best.pt) model.export(formatonnx, dynamicFalse, imgsz640) model.export(formatengine, imgsz640, halfTrue)halfTrue是 TensorRT 的 FP16 推理精度损失通常在一个百分点以内但推理速度能提升 40% 以上。如果现场有老旧的 GPU建议先跑 FP32 版本确认精度符合要求再决定是否切 FP16。FP16 有个典型坑网络中间层的激活值溢出导致吊臂尽头的吊钩偶尔检测不到这类问题在验证集上极难复现因为验证集用的是离线图片而现场视频流里光线突变后FP16 激活值范围会瞬间增大。5. 避坑指南689 张数据集最常见的五个翻车现场5.1 训练集 loss 下降但验证集 mAP 纹丝不动现象训练集 loss 从 2.5 降到 0.5验证集 mAP50 却一直停留在 0.3 左右。原因模型严重过拟合。689 张图对网络来说太少了网络把所有训练图的背景纹理都背了下来遇到验证集的新背景就失去泛化能力。解决先检查数据划分是否按批次分组确认验证集确实属于不同场景。然后降低学习率把lr0降到 0.001同时调大weight_decay到 0.0005强制权重稀疏化。最后增加增强强度把mosaic调到 0.8让模型无法依赖完整的背景上下文。5.2 训练时 loss 直接变成 NaN现象训练第 3 个 epoch 后box_loss突然变成nan后续所有指标都跟着异常。原因最常见的是学习率过大导致梯度爆炸或者标签中出现类别 id 越界。689 张数据集里如果有一张图的标注框坐标超出了图像边界归一化后的宽高会是负数反向传播时计算梯度直接溢出。解决先用脚本扫描所有标签文件检查width和height是否大于 0、x_center和y_center是否在 [0,1] 区间。然后降低lr0到 0.001并开启warmup_epochs5让学习率在前几个 epoch 慢速爬升。5.3 吊钩检测框比实际大一圈现象模型能准确找到吊钩但框的面积是实际面积的 3 到 4 倍且框的 IoU 评分很低。原因689 张图里吊钩样本太少导致模型学到的是吊钩周围的区域而不是吊钩本体。通常吊钩周围是吊臂和钢丝绳这些背景特征的梯度主导了检测头的学习。解决用图像裁剪脚本把吊钩区域裁成独立的局部图复制 3 份并粘贴到不同背景上人工扩充吊钩类别样本。我一般会把吊钩单独提出来做 50 到 100 张合成图效果比盲目调模型参数好得多。5.4 验证集输出大量误检框每一帧都有十几个 false positive现象部署测试时图像右上角的塔臂阴影被识别为 crane地面反光被识别为 hook。原因类别样本不平衡。689 张里 crane 类可能有 400 张正样本hook 类只有 150 张worker 类只有 139 张模型对少数类的判别边界完全失效。解决不是简单地复制少数类图片而是计算每个类别的样本数然后针对少数类做定向增强。YOLOv8 的augment参数支持按类别执行不同增强策略但更简单的做法是把 worker 类图片做水平翻转和亮度抖动再合并进训练集。注意翻转后标签里的x_center要变成1 - x_center。5.5 压缩包解压后图片和标签对不上现象解压后 images 目录有 689 张图labels 目录只有 610 个 txt 文件还有 79 张图没有标签。原因数据集作者导出时漏掉了部分标注或者有些 xml 文件损坏导致转换脚本跳过。解决直接删掉没有标签的图片。不要试图自己补标注除非你对吊装场景非常熟悉否则补出来的标注质量参差反而污染模型。删完后总图像数会少于 689但换来的是标签一致性的确定性。6. 验证模型的最后一公里用视频帧实测并输出带置信度的检测结果6.1 用现场照片做 sanity check置信度阈值到底调多少训练和离线评估都通过后真正的考验是把模型接到现场视频流里。这时候我建议先截取 50 帧现场图像逐张检查预测框的置信度分布。吊钩在逆光场景下的置信度可能只有 0.35而晴天顺光场景是 0.8跨度非常大。置信度阈值设低了误检框满天飞设高了漏检吊钩。一个实用的调参方法是先跑一遍所有测试帧记录模型输出置信度最高的十个误检框和最低的十个正确框然后取中间值作为阈值。这个中间值往往在 0.4 到 0.6 之间具体取决于你现场的遮挡程度。代码层面的调参很简单from ultralytics import YOLO model YOLO(runs/train/crane_exp1/weights/best.pt) results model.predict( sourcescene_frames/, conf0.45, iou0.5, saveTrue, save_confTrue, )save_confTrue会在输出的图像上把置信度数值写上去方便快速核对每个框的分数。iou0.5是 NMS 的 IoU 阈值如果检测框在吊钩两侧出现双重响应把iou降到 0.45合并掉重叠框。6.2 时序平滑和后处理单帧检测的抖动可以用工程手段压下去单帧模型部署到视频流最常见的观感问题是框跳来跳去吊钩在连续帧之间位置抖动幅度很大。模型本身没有记忆能力但工程上可以用一个滑动窗口滤波器对历史预测框做加权平均。from collections import deque class SmoothBox: def __init__(self, window_size5): self.q deque(maxlenwindow_size) def update(self, box): if box is not None: self.q.append(box) if len(self.q) 2: return box # 对 x, y, w, h 各自做平均 avg [sum(v[i] for v in self.q) / len(self.q) for i in range(4)] return avg这个滤波器只对中心点做平均宽度和高度也做平均但要注意吊钩在快速起吊时历史框会拖累当前帧的响应此时应该根据当前帧检测框的置信度动态调整窗口大小——置信度高的时候直接信任当前帧置信度低的时候才去看历史帧。6.3 现场跑三个月后的反思689 张数据集真正值钱的地方在哪用过一段这个数据集后体会最深的是小样本数据集真正决定成败的不是图片数量而是数据划分策略和增强参数。689 张图能训练出一个可用的起重机检测模型靠的是每张图都足够有代表性。如果当初把增强开到最大盲目追求数据量翻倍最终会得到一个对真实画面完全不鲁棒的模型。反过来如果完全不增强纯靠 689 张图过拟合验证集上的 mAP 再好看到现场也是白搭。以后的流程里我会在拿到任何工业小样本数据集后先用一晚上时间把标签质量、类别分布、场景分布盘清楚再决定训练参数。数据洗得干净比模型调参更值钱。希望这套从格式转换到部署调参的路径能帮你在自己的起重机检测项目上少走几趟弯路。本文还有配套的精品资源点击获取