ARTICLE DETAIL

资讯详情

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

起重机目标检测数据集YOLO/VOC格式详解与YOLOv8训练实践

起重机目标检测数据集YOLO/VOC格式详解与YOLOv8训练实践 简介面向目标检测与计算机视觉学习者的起重机Mobile_crane数据集基于真实场景图片整理以VOC与YOLO两种主流格式提供标注适合用于训练工程机械检测模型也适合作为YOLO系列、Faster R-CNN等算法入门练习的数据支撑。压缩包共2000个文件包含620张jpg原图、689个xml标注文件与689个txt标签文件整体约34.35MB三个文件夹JPEGImages、Annotations、labels分别存放图片、XML与TXT文件文件一一对应方便脚本直接读取与格式互转且压缩包内无多余层级解压即可使用。图片清晰且未做增强标签为矩形框共计764个目标框标签类别仅Mobile_crane一种任务聚焦可在较小数据规模下快速验证模型效果也可进一步做数据增强、划分训练验证集。目前已有170人学习下载借助统一命名与分类存储的目录结构能直观理解VOC与YOLO格式的对应关系省去自行标注整理的时间无论是课程实验还是工程预研都能提供清晰的标注参考。1. 从一张起重机照片到一个可训练的 YOLO 数据集这份 689 张的双格式压缩包值不值得用工地安全巡检里“识别起重机械”是个高频需求很多人卡在第一步找不到带标注的数据集。这份“起重机数据集yolovoc格式-689张.zip”从名字就能看出两件事——图片数量不多不少 689 张而且同时给了 YOLO 和 VOC 两种标注格式。VOC 格式方便你在 LabelImg 里二次编辑、查错YOLO 格式省去转换步骤解压后可以直接丢进 YOLOv5/YOLOv8 的训练命令里。它主要解决的是“有哪些起重机、标注框贴不贴目标、类别怎么定义”这类做检测的前置问题对刚接触目标检测的新手它是跑通完整训练流程的好样本对做工业视觉的熟手它适合当迁移学习的起点再补自己现场的塔吊、汽车吊数据。2. 拆开压缩包看门道YOLO 与 VOC 格式互转的目录结构和标签约定拿到手上这份 689 张的双格式数据集先别急着解压后往训练命令里塞。YOLO 和 VOC 的目录组织方式完全不同标注信息的承载载体也不一样第一步是把两边的关系理清楚否则后面训练出的模型大概率会翻车。2.1 VOC 那一侧JPEGImages、Annotations 与 XML 里容易被忽视的字段压缩包如果有 VOC 格式一侧目录通常是按 PASCAL VOC 的惯例组织的JPEGImages放图片Annotations放 XML 标注文件ImageSets/Main里则放着划分好的train.txt和val.txt。689 张图对应 689 个 XML文件名一一对应这是 VOC 格式的基本前提。XML 里真正有用的节点只有三个filename、size和object。size里记录着图片原始宽高转换到 YOLO 格式时必须用它做归一化分母object节点里name是类别字符串bndbox是四个像素坐标。容易被新手忽略的是difficult字段这个值如果为 1表示该目标很难辨认不少训练框架默认会把它排除在训练样本之外。如果你在后续评估时发现某些目标老是漏检先回去看看 XML 里是不是有一批difficult标记的框。还有一个坑在bndbox坐标本身。VOC 标注的坐标有一些是浮点数有些标注工具会写出超出图片边界的值比如xmax大于图片宽度。这种脏数据在 VOC 里不影响显示但一旦转成 YOLO 归一化坐标就可能出现大于 1 或小于 0 的数值训练时轻则 loss 抖动重则直接 NaN。处理逻辑里对坐标做裁剪不丢人这是我在实际数据里碰到过多次的情况。2.2 YOLO 那一侧图片与 txt 的对应关系、类别文件 classes.txt 怎么来的YOLO 格式的标注不是 XML而是每张图片对应一个同名 txt 文件放在labels目录下。txt 里每行代表一个目标框五个值依次是class_id cx cy w h。前两个是中心点的归一化坐标后两个是框的宽高归一化坐标全部除以图片原始宽高所以取值范围通常在 0 到 1 之间。这套格式最大的优点是紧凑没有 XML 那些冗余标签读取效率高缺点是可读性差肉眼没法直接从数字判断框的位置。正因为这样训练前做可视化验证才特别重要。类别映射是 YOLO 格式最容易出错的地方。VOC 的 XML 里写的是crane、hook这类可读字符串YOLO 的 txt 里只有数字 ID。这个 ID 对应哪个类别由data.yaml里的names列表顺序决定而这个顺序又必须和压缩包里classes.txt的顺序一致。如果转换脚本里class_dict的映射写错或者data.yaml里的顺序和标注时不一致模型训练出来就会把 A 类目标当成 B 类而且 loss 曲线还看起来很正常——这是最阴的一类错误不检查推理结果根本发现不了。2.3 用一段脚本做 YOLO 与 VOC 格式互转最小 Python 实现如果你的压缩包里 VOC 和 YOLO 两侧都有这个脚本可能用不上但更多时候你拿到的是只有一侧标注的数据另一侧需要自己生成。我这里给出 VOC 转 YOLO 的通用实现67 行以内搞定批量处理。import os import xml.etree.ElementTree as ET def voc_xml_to_yolo_txt(xml_file, class_dict, out_txt): 把单个VOC XML转换为YOLO格式的txt Args: xml_file: 输入的XML标注路径 class_dict: 类别名到数字ID的映射例如 {crane:0, hook:1} out_txt: 输出的txt路径 tree ET.parse(xml_file) root tree.getroot() # 必须用XML里记录的原始图片尺寸做归一化分母 img_w int(root.find(size/width).text) img_h int(root.find(size/height).text) lines [] for obj in root.iter(object): # 有些XML里包含difficult样本按需跳过 difficult obj.find(difficult) if difficult is not None and int(difficult.text) 1: continue name obj.find(name).text if name not in class_dict: # 遇到映射表之外的类别跳过并打印方便回头补映射 print(f跳过未知类别: {name} in {xml_file}) continue cls_id class_dict[name] bbox obj.find(bndbox) xmin float(bbox.find(xmin).text) ymin float(bbox.find(ymin).text) xmax float(bbox.find(xmax).text) ymax float(bbox.find(ymax).text) # 转归一化中心点坐标和宽高 cx (xmin xmax) / 2.0 / img_w cy (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 裁剪越界值防止训练时loss异常 cx min(max(cx, 0.0), 1.0) cy min(max(cy, 0.0), 1.0) w min(max(w, 0.0), 1.0) h min(max(h, 0.0), 1.0) lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_txt, w, encodingutf-8) as f: f.write(\n.join(lines)) # 批处理目录下所有XML def batch_convert(xml_dir, labels_dir, class_dict): os.makedirs(labels_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue xml_path os.path.join(xml_dir, xml_name) # 输出的txt必须与图片同名 out_txt os.path.join(labels_dir, xml_name[:-4] .txt) voc_xml_to_yolo_txt(xml_path, class_dict, out_txt) print(f转换完成共处理 {len(os.listdir(xml_dir))} 个XML文件)这段脚本的逻辑说明先解析 XML 拿到原始图片宽高再遍历所有object节点把name换成数字 IDbndbox的四个坐标换算成中心点加宽高最后统一归一化。注意两个关键参数class_dict的映射顺序一旦固定后续训练配置必须保持一致越界裁剪一定要做因为吊臂这种细长目标经常出现标注框伸出画面边界的情况不裁剪会让后续训练出现难以排查的诡异 loss。YOLO 转 VOC 是逆运算把cx cy w h还原成xmin ymin xmax ymax再按类别 ID 反查类名拼成 XML 结构。实际工作中用得少因为主流标注工具都支持直接导出 VOC而 YOLO 格式是训练框架直接消费的所以我一般只在需要人工复查标注时做一次 YOLO 转 VOC然后把 XML 拖进 LabelImg 检查。3. 先做数据体检再训练689 张图够不够、类别分布怎么看689 张图属于典型的中小规模数据集。直接开训不是不行但要在训练之前搞清楚三件事图片尺寸是否一致、类别分布有没有偏斜、标注框有没有空文件和脏数据。这三件事决定了训练策略怎么定也决定了模型上限在哪。我处理数据集的时候体检脚本和可视化是固定动作几乎没有跳过的时候。3.1 数据集体检脚本统计图片尺寸、通道数、标注框数量第一个脚本是统计数据集的画像图片有多少张、分辨率集中在什么区间、每类目标有多少个框、有没有空标注 txt。这些数据直接决定imgsz参数怎么设、需不需要做类别过采样。import os from collections import Counter from PIL import Image def inspect_dataset(labels_dir, images_dir): 统计YOLO格式数据集的图片尺寸分布、标注框数量、类别分布 Args: labels_dir: txt标注目录 images_dir: 图片目录 img_exts {.jpg, .jpeg, .png, .bmp} total_boxes 0 class_counter Counter() size_counter Counter() empty_txt [] mismatch_txt [] for txt_name in os.listdir(labels_dir): if not txt_name.endswith(.txt): continue # 根据标注文件名找对应图片 base_name os.path.splitext(txt_name)[0] img_path None for ext in img_exts: candidate os.path.join(images_dir, base_name ext) if os.path.exists(candidate): img_path candidate break if img_path is None: mismatch_txt.append(txt_name) continue with Image.open(img_path) as im: size_counter[im.size] 1 txt_path os.path.join(labels_dir, txt_name) with open(txt_path, r, encodingutf-8) as f: lines [line.strip() for line in f if line.strip()] if not lines: empty_txt.append(txt_name) continue for line in lines: parts line.split() if len(parts) ! 5: print(f格式异常行: {txt_name} - {line}) continue class_counter[int(parts[0])] 1 total_boxes 1 print(f标注txt文件数: {len(os.listdir(labels_dir))}) print(f有效标注框总数: {total_boxes}) print(f类别ID分布: {dict(class_counter)}) print(f图片尺寸分布: {dict(size_counter)}) print(f空标注文件: {empty_txt[:10]}) print(f找不到对应图片的txt: {mismatch_txt[:10]})这段脚本里有个参数细节值得注意img_exts集合里同时考虑了.jpg、.png这几种常见扩展名因为双格式数据集里偶尔会出现图片扩展名不统一的情况。脚本会列出三类问题清单空标注文件有图片但 txt 里没有框、格式异常行、找不到图片的 txt。后两种情况通常意味着文件缺失或者手工编辑标注时把格式写坏了需要在训练前处理掉。类别分布和图片尺寸分布同样重要。如果 689 张图里 80% 是塔吊、20% 是汽车吊模型天然会对塔吊过拟合如果图片尺寸从 800x600 到 1920x1080 都有统一缩放到 640 时小分辨率图片的标注框会被明显拉伸。拿到这些统计数字才能决定下一步怎么划分数据集。3.2 可视化标注框不要直接开训先画出来看边界框对齐情况统计脚本只能发现数值层面的异常标注框是否贴目标这类语义问题必须通过画框才能确认。我自己在多个数据集上踩过同一条坑标注框数值都合法、类别分布也合理但框本身和目标的边缘差了一大截——多半是标注工具自动化生成时用了过大的外接矩形或者是转换脚本归一化分母取错。import cv2 import os def draw_yolo_boxes(image_dir, label_dir, output_dir, class_names, sample_n20): 可视化YOLO标注框人工抽查标注质量 Args: image_dir: 图片目录 label_dir: txt标注目录 output_dir: 可视化结果输出目录 class_names: 类别名列表顺序与class_id一致 sample_n: 抽查的样本数 os.makedirs(output_dir, exist_okTrue) txt_list os.listdir(label_dir)[:sample_n] for txt_name in txt_list: base_name os.path.splitext(txt_name)[0] img_path os.path.join(image_dir, base_name .jpg) img cv2.imread(img_path) if img is None: # 找不到.jpg时尝试.png避免误报 img_path os.path.join(image_dir, base_name .png) img cv2.imread(img_path) if img is None: continue h, w img.shape[:2] txt_path os.path.join(label_dir, txt_name) with open(txt_path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_id, cx, cy, bw, bh map(float, parts) # 归一化坐标还原为像素坐标 x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) # 不同类别用不同颜色方便区分标签错位 color (0, 255, 0) if int(cls_id) 0 else (0, 0, 255) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) label class_names[int(cls_id)] cv2.putText(img, label, (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 1) out_path os.path.join(output_dir, txt_name[:-4] _vis.jpg) cv2.imwrite(out_path, img) print(f已生成: {out_path})逻辑说明遍历前 20 个 txt把归一化坐标还原成像素坐标并画框类别名直接写在框上方。这里设置了两类目标用不同颜色就是为了快速发现类别错位——如果绿色框出现在钩子上而红色框套着吊臂说明class_dict映射顺序有问题。抽查时重点看三点框有没有紧贴目标边缘、有没有把背景框进来、类别颜色和实际物体是否匹配。689 张图抽 20 张够了如果这 20 张里出现超过 2 个明显错框建议扩大到 100 张甚至直接全部可视化再人工扫一遍。这一步是后悔药最便宜的时候——训练一个晚上后发现标签错位再来返工代价完全不是一回事。3.3 按 7:2:1 划分训练集/验证集/测试集并生成 YOLO 需要的 data.yaml体检通过后第二步是划分数据集。689 张图我习惯按 7:2:1 切成训练、验证、测试三份而不是常见的 8:2。原因是训练集只有 480 张左右单类目标可能出现得更少留 10% 的测试集能逼自己认真对待验证集效果而不是把测试集压到只有 50 张的尴尬规模。import os import random def split_dataset(image_dir, label_dir, out_dir, train_ratio0.7, val_ratio0.2): 按比例划分YOLO数据集生成train.txt/val.txt/test.txt Args: image_dir: 图片目录 label_dir: txt标注目录用于过滤无标注的图片 out_dir: 输出txt清单的目录 train_ratio: 训练集比例 val_ratio: 验证集比例 random.seed(42) # 固定随机种子保证划分结果可复现 names [] for fname in os.listdir(image_dir): if not fname.lower().endswith((.jpg, .jpeg, .png)): continue base os.path.splitext(fname)[0] # 只有图片和标注txt都在才算有效样本 if os.path.exists(os.path.join(label_dir, base .txt)): names.append(fname) random.shuffle(names) n len(names) train_end int(n * train_ratio) val_end int(n * (train_ratio val_ratio)) split { train: names[:train_end], val: names[train_end:val_end], test: names[val_end:] } os.makedirs(out_dir, exist_okTrue) for part, part_names in split.items(): list_path os.path.join(out_dir, f{part}.txt) with open(list_path, w, encodingutf-8) as f: for name in part_names: abs_path os.path.join(os.path.abspath(image_dir), name) f.write(abs_path \n) print(f{part}: {len(part_names)} 张已写入 {list_path})划分逻辑里的一个细节是过滤无标注文件先检查同名 txt 是否存在再把图片路径写入清单。这能避免把空标注图片划进训练集。固定随机种子同样不是玄学它保证你在修改配置重新训练时训练集和验证集的构成不变对比实验才有意义。划分完成后在数据集根目录写一个data.yaml这是 YOLOv5/YOLOv8 训练命令直接读取的配置入口。# 数据集的绝对路径根目录换成你解压后的实际路径 path: /home/user/crane_dataset train: train.txt val: val.txt test: test.txt # 类别数量 nc: 2 # 类别名列表索引顺序必须与标注txt里的class_id一致 names: [crane, hook]这里最容易翻车的点是path字段。写成相对路径时训练命令的当前工作目录必须和data.yaml所在目录一致否则框架找不到train.txt。我习惯把path写成绝对路径配合 split 脚本里同样使用绝对路径换机器跑的时候只要改一处就行。names顺序则必须和 2.3 节转换脚本里class_dict的值对应如果class_dict里crane映射成 1 而这里把crane放在 0 号位模型学到的类别就会整个错位。4. 用 YOLOv8 训练自己的起重机检测模型命令、参数与一次收敛的调法数据集准备到这个程度才轮到训练环境。以 YOLOv8 为例完整流程包括环境创建、训练配置、迁移学习策略三个部分。689 张图不大但训练参数设置错误会让损失函数直接跑飞或者验证集指标虚高但实际检测效果一塌糊涂。4.1 从零搭训练环境conda 创建环境、安装 ultralytics训练环境我一般用 conda 单独建一个避免和本来的 Python 环境互相污染。Python 版本选 3.10这套组合在 ultralytics 各版本上都比较稳。conda create -n crane_yolo python3.10 -y conda activate crane_yolo pip install ultralytics装完后先验证一下yolo命令能不能用。直接运行yolo --help如果输出了命令行帮助列表说明环境没问题。GPU 版本的 PyTorch 建议单独装pip install ultralytics会自动带一份 CPU 版 PyTorch但你如果机器有 N 卡却用 CPU 版训练速度会慢到一个离谱的程度。# 先装GPU版PyTorch再装ultralytics顺序不要反 conda activate crane_yolo pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics--index-url指向 PyTorch 官方 CUDA 11.8 的 wheel 源在你的网络环境下如果拉不动可以换 cu121 或者用镜像源但一定要保证 PyTorch 的 CUDA 版本和显卡驱动匹配。装好后在 Python 里执行import torch; print(torch.cuda.is_available())返回True才继续往下走。4.2 训练命令与关键参数imgsz、batch、epochs、patience 的含义环境就绪后一个最基础的训练命令长这样yolo detect train \ data./crane_dataset/data.yaml \ modelyolov8n.yaml \ pretrainedtrue \ epochs150 \ imgsz640 \ batch16 \ lr00.01 \ patience30 \ device0 \ project./runs \ namecrane_v8n逐个说参数因为它们每一条都直接关系训练结果。modelyolov8n.yaml指定的是网络结构配置文件yolov8n是 nano 版本参数量最小适合样本规模不大的场景。如果显存充足想要更高精度可以换成yolov8s.yaml但 689 张图喂给 s 模型很容易过拟合我建议从 n 开始跑通流程再说。pretrainedtrue表示加载 COCO 预训练权重配合data.yaml里的nc2框架会自动替换最后的检测头并重新初始化输出层。这里要不要用预训练权重后面 4.3 节单独讲。imgsz640是训练时输入图片的边长YOLO 会按这个尺寸做等比缩放和填充。起重机吊臂是典型的细长目标如果图片里吊臂横跨整个画面640 分辨率下缩得太小可以试 1024但显存占用会翻倍batch 也要跟着降。batch16由显卡显存决定。RTX 3060 12G 跑yolov8n用 16 没问题8G 显存就降到 8否则训练到一半爆显存中断前面几小时全白费。lr00.01是 SGD 优化器的初始学习率这个值在默认超参数下能正常收敛但如果 loss 曲线出现震荡或 NaN优先把它降到 0.001这个经验比调任何花哨参数都管用。patience30是早停阈值训练集和验证集 loss 连续 30 轮没有改善就自动停止。689 张图的数据量150 轮已经能看到比较稳的收敛趋势没必要盲目的跑到 500 轮。早停不是玄学它实实在在帮你省时间。device0选 GPUproject和name指定输出目录最后的权重会写到runs/detect/crane_v8n/weights/best.pt。训练结束后results.png里能看到 loss 曲线和 mAP 曲线confusion_matrix.png里能看到每个类别的混淆情况这两个文件是判断模型有没有正常学到的第一手资料。4.3 迁移学习用 yolov8n.pt 预训练权重还是从头训练689 张图到底该用哪种姿势训练其实是很多人纠结的问题。我直接说结论用预训练权重别从零开始。COCO 预训练权重覆盖了 80 个日常类别里面没有crane也没有hook但模型在前几层学到的边缘、纹理、形状特征对起重机同样有效。迁移学习的意义在于底层特征不用重新学只需要微调高层检测头和类别输出层。如果从零开始689 张图不但要重新学特征还要在一个相对小的数据量上拟合所有参数过拟合几乎是必然的。modelyolov8n.yaml加pretrainedtrue的做法会自动下载yolov8n.pt预训练权重网络环境不好时下载可能会失败。更可控的做法是手动下载权重文件放到当前目录然后把参数改成modelyolov8n.pt效果等价但少了联网环节。预训练权重一开始是 80 类输出你的data.yaml里nc2框架在加载权重时会自动丢弃最后一层并重新初始化这个过程是自动的不需要手动改结构。有一点要提醒如果你拿到的是另一个项目训练好的best.pt而这个项目类别数和你的不一致直接拿来pretrainedtrue会报形状不匹配的错误。常见做法是先把权重加载到同结构模型上或者干脆只用它的骨干部分做参数初始化但这就涉及修改模型的代码不展开。普通场景下用官方yolov8n.pt就够了。5. 起重机数据集训练避坑指南5 个让模型翻车的高频问题这一节写我见过和踩过的高频问题。每一条都是“现象 → 原因 → 解决”的结构翻车的时候对照着查比重新读一遍文档快得多。5.1 现象loss 先降后炸bbox_loss 一路飙升到 NaN训练前几轮 loss 正常下降到某一步突然变成nan或者验证集bbox_loss剧烈震荡后彻底跑飞。这基本可以确定是学习率过大、标注数据带脏值、或预设锚框尺寸与目标尺度严重不匹配三者之一。先看数据检查转换出来的 txt 里有没有w0或h0的框这种行会让宽高对数计算直接得到无穷大梯度反向传播时自然变成 NaN。再看学习率lr00.01在 689 张图这样的小数据集上可能偏激进降到0.001或0.0005通常能解决。最后检查锚框如果你的图片里吊臂都是超长条而默认锚框偏正方形可以在预测阶段调高anchor_t或者自定义锚框但这是后话前提是先排除前两个原因。5.2 现象mAP0.5 很高但 mAP0.5:0.95 低得离谱验证集上mAP0.5能到 0.85说明粗定位没问题但mAP0.5:0.95只有 0.4说明候选框和目标真实框的重合度不够紧。吊臂这类细长目标尤其明显——宽高比可能达到 1:10即使中心点完全对中框的角度或长度稍微偏一点IoU 就会从 0.9 跌到 0.6 以下。解决方向有两个。一是回去精修标注边把外接矩形尽量贴到物体边缘减少标注本身的误差二是对细长目标适当提高输入分辨率imgsz从 640 提到 1024让网络有更多像素去拟合长宽比。不要指望调锚框比例能一次性解决标注质量才是最根本的影响因子。5.3 现象训练正常推理时把吊钩识别成吊臂训练曲线漂亮可视化标注也正常但跑推理视频时发现吊钩位置被标成crane或者反过来远处吊臂被识别成hook。这大概率不是模型问题而是类别索引错位。检查顺序先看data.yaml里的names顺序再看labels目录 txt 里的class_id范围最后看一眼体检脚本输出的类别分布。如果 txt 里类别 ID 是 0 和 1而names列表是[hook, crane]模型学到的映射就和你的直觉相反。这类错位不会影响损失函数收敛所以曲线很正常只有到推理阶段肉眼才能发现。5.4 现象训练集 loss 低验证集 loss 居高不下这是过拟合的典型信号。689 张图里训练集约 480 张如果检测目标种类多、每类样本量又少模型会把训练图片的背景纹理也当作特征记下来验证集上自然表现差。对策按优先级排第一关掉或减弱mosaic之外的其他数据增强避免增强后的样本离真实分布太远第二把epochs降到 100 附近配合patience30更早地停在泛化较好的点第三换用更小的模型yolov8n不行就试yolov8n配低分辨率减少模型容量。最根本的解法还是补数据但这不在单次训练能解决的范围内。5.5 现象从 VOC 转 YOLO 后框的位置全偏了转换脚本跑完可视化一看所有框要么整体往左上偏要么宽高比完全对不上。这种现象九成是因为归一化分母用错了。常见错误写法是取cv2.imread后的h, w做分母但 OpenCV 读出的h, w和 VOC XML 里size记录的宽高虽然大多数情况一致一旦图片被脚本做过 resize 或压缩这里的尺寸就是错的。正确做法是始终以 XML 里size/width和size/height为唯一标注来源。另一个原因是bndbox坐标本身有越界值例如标注时xmax超过图片实际宽度转换后归一化坐标大于 1画框时自然偏出画面。解决方法在 2.3 节的脚本里已经体现宽高取 XML 的size字段坐标做一次越界裁剪。如果已经转完了不要手工改 txt直接重新生成一遍更省事。6. 把 689 张图的效用榨干增量扩类和混淆矩阵驱动的下一轮迭代模型训练完不是终点尤其数据集只有 689 张离真实工地场景的覆盖度还有明显差距。最后一节讲两个把这个数据集价值放大的思路扩类增量训练以及用混淆矩阵决定下一批数据该补什么。6.1 只有两类不够增量扩类的最省事流程如果部署现场需要辨识的不止起重机本身还有吊钩、吊臂、驾驶室原来的 2 类标注就不够用了。最省事的做法是保留原有标注用 LabelImg 打开 VOC 格式的 XML新类别标注完成后导出再用 2.3 节的转换脚本追加生成新的 YOLO 标注。注意新类别 ID 要在原来class_dict基础上往后排例如原来是{crane: 0, hook: 1}现在新增jib就映射成 2千万不要把已有类别的 ID 打乱——否则所有旧标注的 txt 都要跟着改而且改错一个就废一张图。6.2 混淆矩阵不看总数看互相认错的类别对训练输出目录里的confusion_matrix.png是决定下一轮数据补什么的最佳依据。矩阵的主对角线越亮越好亮点越集中说明分类越干净。真正要警惕的是两类之间非对称的误报比如塔吊被识别成龙门吊的比例明显偏高说明当前训练图里这两种场景的外观相似度没被充分区分。下一批数据优先补这类容易混淆的场景比平均补图提升快得多。顺带提一个容易让人困惑的细节混淆矩阵里每行数量之和可能不等于验证集里该类目标总数这是 NMS 去重和置信度阈值过滤造成的正常现象不必纠结总和是不是 100%。把注意力放在相对比例上就行。6.3 批量推理并输出带框视频给业务方看比看 mAP 更管用给工地安全负责人汇报模型效果时mAP0.5是多少在他眼里没有画面直观。训练结束后用测试集或现场拍的视频做一次批量推理直接把带框结果输出成视频文件这是最省事的交付物。yolo detect predict \ model./runs/detect/crane_v8n/weights/best.pt \ source./test_video.mp4 \ conf0.4 \ saveTruemodel指向训练产出的best.ptsource可以是视频文件也可以是图片目录conf0.4是置信度阈值低于这个值的预测框会被丢弃。现场场景中有大量远距离小目标时conf可以降到 0.25但相应地误报也会增加需要根据业务对漏报和误报的容忍度来调节。saveTrue会把推理结果自动存成一个带框的新视频放到项目汇报材料里就够了。我自己的习惯是把训练命令、转换脚本、体检脚本、可视化脚本全存在同一个项目目录下版本一跑就是一晚上第二天起来先看混淆矩阵再决定要不要补数据。模型翻车时先怀疑标签和配置最后才怀疑网络结构——这个顺序帮我避开了不少无效调参。689 张图的数据量虽然没有想象中那么大但只要标注质量可控、训练流程规范它足够支撑起一个能上真实场景演示的检测模型。希望帮到你。本文还有配套的精品资源点击获取
返回列表