尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

VOC到YOLO格式转换:549张boat图像训练舰船检测模型实战

发布时间:2026/9/14 17:37:28

资讯中心
01
ARTICLE

VOC到YOLO格式转换:549张boat图像训练舰船检测模型实战

VOC到YOLO格式转换:549张boat图像训练舰船检测模型实战
简介YOLO舰船检测数据集是一份从PASCAL VOC 2012中筛选出所有包含“boat”类别的图像构成的专用训练集面向目标检测开发者、算法研究人员以及海事智能视觉落地项目。压缩包解压后共1648个文件内含549张JPEG船舶图像、549个VOC格式XML标注文件、550个YOLO格式TXT标注文件整体大小约56.31MB。TXT标注按YOLO规范记录目标框中心点、宽高与类别IDXML标注则遵循PASCAL VOC标准描述框坐标与标签两种格式可分别供YOLO系列和Faster R-CNN、SSD等框架使用。由于数据集仅保留“boat”一个类别模型训练目标集中适合做舰船检测、海上交通监控等场景的专项优化也可通过翻转、裁剪、色彩扰动等数据增强手段扩大样本量从而提升召回率与定位精度。已有2399人学习/浏览借助该数据可快速搭建从数据解析、模型训练到效果评估的完整流程对照图片与标注理解目标检测原理进而支撑模型调优与部署验证。1. 为什么 549 张 boat 图是最磨人的一类检测任务海洋监控摄像头画面里船往往只有十几个像素浪花、倒影、岸边浮木都是“疑似船”。用 COCO 预训练权重直接推大船能框住但错检率居高不下。单类舰船检测的难点不在识别而在定位和背景抑制。boat_VOCtrainval2012 这个数据集把 PASCAL VOC 2012 里 boat 类别抽出来筛出 549 张带完整 bndbox 标注的 JPEG 图像同时给出 VOC XML 和 YOLO txt 两套标注。对正在练手 YOLO 目标检测流程、想在真实背景下训练自己的船舶检测器的工程师和数据科学同学它是很好的单类检测样板。小样本、单类别、强背景干扰这三个属性放一起比直接拿 COCO 大模型微调更能暴露数据工程问题。要验证自己的数据链路是否扎实先把目录结构摸清把 XML 转成 YOLO 格式并验证坐标再用标准脚本把数据喂进 YOLOv8。2. 从 VOC XML 到 YOLO txt格式转换是训练前的分水岭拿到 boat_VOCtrainval2012.zip解压后就要开始破译标注结构。它的图像文件名沿用 VOC 原始编号例如 2011_000238.jpg 属于 VOC 2012 的 trainval 集2008_005863.jpg 属于更早的 VOC 年份。文件名与图像内容无直接关系后续做数据集划分时按序号采样即可不要根据文件名猜测场景。2.1 解压后先看目录结构和 XML 关键节点常见做法是直接 unzip 解压到本地然后快速观察目录布局。unzip boat_VOCtrainval2012.zip -d boat_dataset find boat_dataset -maxdepth 2 -type d | sort输出一般会看到三个核心目录对应关系如下表目录内容体量JPEGImages船舶原始图片JPEG 有损压缩549 张AnnotationsVOC XML 格式标注与图片一一对应ImageSets/Main按 trainval/test 划分的清单文件1~2 个 txtJPEG 是数字图像存储里常用的有损压缩格式单张缩略图约几十到两百 KB。这里数量只有 549整个压缩包通常不超过 300 MB训练和拷贝成本都很低。标注文件是理解数据集的关键直接看一张 XML 的内部结构head -60 boat_dataset/Annotations/2008_002610.xml一个典型 VOC 对象标注包含 filename、size 和 object 三部分。object 里有 name类别名 boat、pose、truncated、difficult以及 bndbox 的 xmin、ymin、xmax、ymax。训练时真正读取的是 name 和 bndbox 四个整数坐标。truncated 和 difficult 表示目标被截断或难以辨认YOLO 训练一般直接忽略但做竞赛评测时 difficult 对象不参与 AP 计算转换脚本里最好保留解析逻辑方便后续统计。提示JPEGImages 文件名不能有中文和空格。如果解压后发现路径带空格训练时 Dataloader 拼接路径不会对空格转义概率性报 FileNotFoundError。这种坑在数据集来自不同渠道时很常见拿到先统一改名。2.2 YOLO 归一化坐标格式与换算原理VOC XML 记录的是以图像左上角为原点的像素坐标YOLO 则需要归一化的中心点坐标和宽高即摘要里说的「类ID 中心x 中心y 宽 高」。换算公式如下yolo_x_center ((xmin xmax) / 2) / image_widthyolo_y_center ((ymin ymax) / 2) / image_heightyolo_width (xmax - xmin) / image_widthyolo_height (ymax - ymin) / image_height所有输出值都在 0~1 之间。YOLO 内部把 feature map 每个格子对应到整图坐标归一化让不同分辨率图像可以共用一套 anchor 和网络参数。如果直接喂绝对像素值网络会认为 1920×1080 的船和 640×360 的船是两类目标。这里有个容易忽略的细节增强模块Mosaic、随机缩放在送入网络前会做 letterbox原图宽高比改变后标注坐标要跟着 affine 变换走YOLOv8 的自带 dataloader 会处理这一步自己造数据时常见的错误是把 XML 里的 xmin、ymin、xmax、ymax 原样写进 txt导致训练时边界框扭曲。2.3 完整转换脚本解析、归一化、落盘下面给出兼容 boat_VOC 目录结构的转换脚本输出到 labels 子目录import xml.etree.ElementTree as ET import os voc_root boat_dataset labels_dir os.path.join(voc_root, labels) os.makedirs(labels_dir, exist_okTrue) def voc_to_yolo(xml_path, out_path): 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.findall(object): name obj.find(name).text if name ! boat: continue 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) x_center (xmin xmax) / 2.0 / img_w y_center (ymin ymax) / 2.0 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h # 单类别 boat 的 class_id 固定为 0 lines.append(f0 {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines) \n) for xml_name in os.listdir(os.path.join(voc_root, Annotations)): if not xml_name.endswith(.xml): continue stem xml_name[:-4] xml_path os.path.join(voc_root, Annotations, xml_name) out_path os.path.join(labels_dir, stem .txt) voc_to_yolo(xml_path, out_path)代码逻辑先取 size/width 和 size/height 做归一化分母再遍历 object 节点解析 bndbox。class_id 写死为 0因为该数据集只有一个类别。后续如果要合并 COCO 船类子集建议把 name 到 id 的映射抽成字典方便扩展。这段脚本也适用于 KITTI 标注转 YOLO 的思路KITTI 的 bbox 字段顺序同样是 xmin、ymin、xmax、ymax只是 XML 标签名不同把 find 路径改掉即可。运行后抽检 2008_002610 的标注内容坐标值应为 0~1 的小数如果出现一整行 0.0 或 1.0多半是边界框贴边需要回头检查 XML 的 bndbox 是否越界。3. 把 boat 数据组装成 YOLOv5/v8 训练目录并跑通首轮格式转换只完成数据准备的一半接下来要组织目录结构和配置 data.yaml。YOLOv5 和 YOLOv8 都要求 images 和 labels 分目录存放图片名与标签名一致只差后缀。训练脚本会按图片文件名自动找同名 .txt。组织好的标准结构如下boat_yolo/ ├── images/ │ ├── train/ # 例如 439 张 │ └── val/ # 例如 110 张 ├── labels/ │ ├── train/ │ └── val/ └── boat.yaml3.1 目录重组与 train/val 划分的比例决策用 shell 命令把上一章生成的 labels 和 JPEGImages 归位cd boat_dataset mkdir -p ../boat_yolo/images/train ../boat_yolo/images/val ../boat_yolo/labels/train ../boat_yolo/labels/val mv JPEGImages/*.jpg ../boat_yolo/images/train/ 2/dev/null549 张图规模小val 留 100~120 张比较稳否则验证集太少mAP 波动会非常大。比例一般取 train:val 8:2 或 7:3。这里的 trainval 原始划分与 VOC 官方 train/val 交集问题要特别注意VOC 的 trainval 是 train 和 val 的总和boat_VOCtrainval2012 本身是一个整体如果直接从 ImageSets/Main 里拿现成清单可能把验证图和训练图混在一起造成数据泄漏训练时 mAP 虚高。更严谨的做法是用固定随机种子做一次划分并保存清单import os import random random.seed(0) imgs sorted(os.listdir(boat_dataset/JPEGImages)) random.shuffle(imgs) val_count int(len(imgs) * 0.2) val_set set(imgs[:val_count]) for img in imgs: stem img[:-4] src_img fboat_dataset/JPEGImages/{img} src_lbl fboat_dataset/labels/{stem}.txt if img in val_set: os.makedirs(boat_yolo/images/val, exist_okTrue) os.makedirs(boat_yolo/labels/val, exist_okTrue) os.replace(src_img, fboat_yolo/images/val/{img}) os.replace(src_lbl, fboat_yolo/labels/val/{stem}.txt) else: os.makedirs(boat_yolo/images/train, exist_okTrue) os.makedirs(boat_yolo/labels/train, exist_okTrue) os.replace(src_img, fboat_yolo/images/train/{img}) os.replace(src_lbl, fboat_yolo/labels/train/{stem}.txt)固定 random.seed(0) 后打乱可以保证每次执行得到相同划分。val_count 按 20% 计算因为原图存在漏标注保留更多验证样本能暴露标注质量问题。如果后续换用 COCO 船类子集扩充建议把划分脚本的参数化不要手动复制图片。3.2 data.yaml 配置与 AMD 显卡运行注意事项boat.yaml 最少只需要三行路径加一个类别列表path: ../boat_yolo train: images/train val: images/val names: 0: boatpath 建议写相对路径或者写绝对路径时用正斜杠。YOLOv8 对 Windows 绝对路径中的反斜杠处理不如相对路径稳这个坑常见于从 Windows 拷贝到 Linux 服务器训练的场景。names 的 key 必须从 0 开始单类别数据集才不会报 num_class 不匹配错误。如果是在 AMD 显卡上跑 YOLO需要用 ROCm 编译的 torch 环境否则 import torch 后 device 检测不到 cuda训练退回 CPU。安装 ultralytics 前先确认 torch 能打印出 ROCm enabled再决定是否拉预编译包。目前 ultralytics 官方 wheels 对 ROCm 支持仍然停留在社区构建版本直接 pip install ultralytics 后强制 device0 会看到 RuntimeError: Attempting to deserialize object on a CUDA device这是环境问题不是代码问题。3.3 用 YOLOv8 跑通第一轮训练安装依赖后直接启动训练pip install -U ultralytics yolo detect train databoat.yaml modelyolov8n.pt epochs100 imgsz640 batch16 device0关键参数建议如下参数建议值说明modelyolov8n.pt / yolov8s.pt数据量小从 n 起步先跑通再做 s/l 对比imgsz640 或 1280船目标小则 1280 能提升小目标召回显存约翻 3 倍batch16 或 32显存不够时减半549 张图 batch 32 对精度提升有限epochs100~300单类目标 100 轮足够判断过拟合方向device0 或 cpu无 GPU 时加 devicecpu训练时间会拉长 10 倍以上第一轮训练时建议加plotsTrue保存 confusion matrix 和预测样例。训练结束后运行验证命令yolo detect val databoat.yaml modelruns/detect/train/weights/best.pt只跑通训练不跑验证就看不到 mAP 的完整视图模型好坏无从判断。如果验证时报 label 不匹配错误检查 labels 目录下是否有空 txt 文件空标注文件会导致 dataloader 解析失败。4. YOLO 损失函数与单类舰船 mAP 的判读方法训练跑起来以后问题从“怎么传数据”变成“怎么判断模型在变好”。YOLOv5 和 YOLOv8 的损失组合不一样直接决定日志里该看哪些字段。4.1 不同版本损失函数组成与单类场景差异YOLOv5 损失由三部分组成box_loss 用 CIoUobj_loss 用 BCEWithLogitscls_loss 也用 BCEWithLogits。YOLOv8 是 anchor-free 结构box 分支用 Distribution Focal Loss 加 CIoU类别分支仍用 BCE。单类 boat 数据集的差异集中在 cls_loss 上只有一个类别BCE 只输出一个 logit负样本数量远大于正样本obj_loss 比 cls_loss 更容易主导总 loss。训练日志字段的含义要分清epoch 50 box_loss: 0.62 cls_loss: 0.03 dfl_loss: 1.02 val box_loss: 0.71 val cls_loss: 0.05 mAP50: 0.83 mAP50-95: 0.41box_loss 下降说明边界框回归在收敛cls_loss 是类别置信度损失单类下数值通常较小如果它突然增大先怀疑数据集中混入了非 boat 背景图dfl_loss 是 YOLOv8 独有的分布损失负责预测框质量分布平稳波动不需要过度干预。如果第 30 轮 mAP50 就超过 0.9不要高兴太早多数是 val 划分太小导致的偶然偏高重点看 val loss 是否同步下降。4.2 mAP 计算口径与 results.csv 诊断mAP50 和 mAP50-95 是练手小模型最常看的两个指标。mAP50 是 IoU 阈值为 0.5 时的平均精度衡量“框是否大致框住”mAP50-95 是 0.5 到 0.95 区间按步长 0.05 计算的平均值对定位精度要求更高数值通常只有 mAP50 的一半。单类数据集只有 boat 一个类别mAP 就等于这个类别的 AP。如果 mAP50 明显高于 0.8但 mAP50-95 低于 0.3说明模型漏检少但框偏了优先调 anchor 或提高 imgsz。用 results.csv 画出曲线做定位诊断import pandas as pd import matplotlib.pyplot as plt df pd.read_csv(runs/detect/train/results.csv) df.columns [c.strip() for c in df.columns] plt.figure(figsize(10, 4)) plt.plot(df[epoch], df[train/box_loss], labeltrain box) plt.plot(df[epoch], df[val/box_loss], labelval box) plt.legend() plt.savefig(loss_curve.png)代码不引入额外依赖pandas 和 matplotlib 在训练环境里一般都有。看到 train/box_loss 继续下降而 val/box_loss 开始上扬就是典型的过拟合信号。549 张小样本大概在 60~100 轮之间出现此时应停止训练改用早停机制或者切换更强的数据增强而不是继续加 epochs。5. 小样本舰船检测的增强补偿与锚框调整5.1 按海上场景裁剪增强参数549 张图决定模型必然依赖增强正则化。YOLOv8 默认增强参数对通用目标有效但海上场景需要专门调整。参考配置如下hsv_h: 0.015 degrees: 10.0 translate: 0.1 scale: 0.4 fliplr: 0.5 flipud: 0.0 mosaic: 1.0 mixup: 0.0 copy_paste: 0.0海天线会让上下翻转产生大量不合理样本flipud 直接关掉fliplr 保留是因为左右航行方向不影响语义。scale 设 0.4 的代价是目标缩小后更难学如果原图里船普遍小于 32×32 像素把 scale 降到 0.2 更稳。mosaic 保留为 1.0它把四张图拼一起训练能提升模型对被遮挡船只的鲁棒性。mixup 和 copy_paste 在单类小样本下容易出现重复标签叠加建议先从 0 起步观察 mAP50-95 是否停在 0.5 以下再决定是否打开。5.2 用预测脚本检查锚框尺寸适配度训练完成后检查锚框是否适配本数据集中小目标from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) results model.predict( sourceboat_yolo/images/val/2008_002610.jpg, conf0.25, iou0.45, ) boxes results[0].boxes.xywh.cpu().numpy() for b in boxes: print(fw{b[2]:.0f} h{b[3]:.0f})conf 和 iou 阈值控制输出框数量。对比同一图像 labels 目录下的 txt如果输出框普遍比标签小一半以上说明默认锚框是按 COCO 目标比例初始化的对舰船这种长宽比变化大的目标适配不足。此时可运行yolo detect tune databoat.yaml modelruns/detect/train/weights/best.pt重新搜索锚框超参或者直接把 imgsz 提到 1280让网络在小目标的 feature map 上保留更多空间特征。锚框调整对 mAP50-95 的影响通常比增加训练轮数更直接这也是单类小样本数据集上最值得先做的优化方向。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。