☰
YOLO船舶数据集详解:从标签格式到模型训练全流程
2026/9/28 17:06:46 网站建设 项目流程

简介:面向YOLO系列目标检测模型训练与验证的船舶类数据集,涵盖充气船、独木舟、船舶等常见水上目标,适用于水域监控、航道管理、海上搜救等场景,可帮助检测学习者和算法工程师快速获取带标注数据,省去采集与标注环节。压缩包总计2000个文件、约88.34MB,主要包含1063个XML标注文件与936个TXT标签文件,并附1个YAML数据集配置:XML为VOC格式框坐标,TXT为YOLO格式的类别、归一化中心点与宽高信息,YAML内置数据集路径、类别名称与训练验证划分,可直接接入YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLO11等主流版本。数据集共有1088张船舶图像,已按训练与验证需求划分好,解压后配合data.yaml即可直接训练、验证和测试,无需额外格式转换。目前已有86人学习下载,适合正在搭建检测流程、对比YOLO系列性能或开展船舶识别项目的开发者参考使用。

1. yolo算法船舶数据集:1088张带标签图像,从文件清单到能训出模型的完整路径

拆数据集拆多了会有一个直觉:判断一份yolo算法目标检测数据集能不能用,先别急着看图片长什么样,直接去看标签文件。这份船舶数据集压缩包解压后,img_0298_84.txt、img_0529_31.txt 这类文件密密麻麻排了一串,很明显是yolo格式的txt标签,再配合根目录下的两个标签文件夹和data.yaml,基本可以断定它是按yolov5/v8/v9那套标准组织好的。它解决的核心问题是:做充气船、独木舟、船舶这三类目标的检测任务时,你不用再从零标注,拿来划分好的训练验证测试集、两种格式标签和配置直接开训,适合正在调yolov5、yolov8甚至yolov10的算法工程师,也适合做毕设需要真实水域船只样本的学生。

2. 数据集底细:两个标签文件夹,1088张图对应的格式逻辑

2.1 文件命名规律:从txt名反推图像匹配机制

打开压缩包先看到的是几百个txt文件,命名格式统一是 img_编号_编号.txt。yolo系列算法训练时的常规做法是图像和标签同名,只是扩展名不同,train.py 里通过 img_path 替换后缀找到对应txt。比如 img_0298_84.jpg 对应 img_0298_84.txt,编号里的 0298 通常是原图序号,84 可能是目标框编号或者切片序号,具体语义不影响训练。这种命名方式的优势是排序稳定,Windows 下按名称排序不会错位。

实际训练时你不需要手动建立对应关系,ultralytics 的 dataloader 会自动扫描 images 目录下的图片,再去 labels 目录下找同名txt。但我一般会在拿到数据集后先写一个快速脚本核对一遍,确认每张图片确实能找到同名标签,不然训练到一半报 FileNotFoundError 会浪费大量时间。

2.2 双格式存储的意义:yolo格式和voc格式分别解决什么问题

摘要里写明这个数据集包含两种标签格式,分别保存在两个文件夹。yolo格式是txt文件,内容形如 class_id x_center y_center width height,坐标是相对图像的归一化比例,范围0到1。voc格式是xml文件,存储的是真实像素坐标。yolo格式是给训练直接吃的,ultralytics和大多数yolo仓库都是读txt;voc格式是给人看的,也想保留标注工具的原始导出结果,方便你用labelImg继续编辑或者转成coco格式。

这个设计有实际价值。很多时候数据集只有一种格式,你想从voc转yolo还得自己写转换脚本,这份直接把两种都给了。需要强调的是,两类文件内容描述的是同一个标注框,只是表示方式不同,xml里的bndbox坐标除以图像宽高就能得到yolo格式的归一化值,这是后续自己解析和校验的数学基础。

2.3 数据划分状态:data.yaml里能看到什么

数据集已经划分好,配置写在data.yaml里。yolov5和yolov8的data.yaml语法基本兼容,通常包含 train、val、test 三个路径和 nc、names 两个关键字段。路径可能是相对路径格式,比如 train: ../images/train,val: ../images/val,这意味着你得把数据集放在特定目录结构下,或者自己改成绝对路径。

很多人拿到数据集直接改路径就开训,但names的顺序是绝对不能动的。names: ['inflatable_boat', 'kayak', 'ship'] 这个排列顺序决定了txt里class_id的含义,0对应充气船,1对应独木舟,2对应船舶。如果训练时手误改了names顺序,模型学到的类别映射就全乱了,推理输出的结果对不上真实物体,这是一类非常隐蔽的错误。

提示:先打开data.yaml确认names顺序和实际图片内容是否一致,再开始训练。顺序错了,后面所有评估指标都失真。

在数据量层面,1088张图做三类检测属于中等偏小的规模。我的经验是:这个量级直接上yolov8s能跑通流程,但mAP可能不太稳定,尤其是小目标船只在远处密集出现时。建议先把图片尺寸设成640x640,batch size根据显存调整,用yolov8s作为baseline,先验证标签和流程没有问题,再考虑上yolov8m或者加数据增强。

3. YOLO txt标签解析:归一化坐标的数学含义与校验脚本

3.1 五个数值的物理意义,别把x_center理解成像素坐标

yolo格式每行五列,class是类别索引,从0开始,后面四个值是比例值。很多人第一次接触会犯一个错误:以为 x_center 是目标中心点的像素横坐标,结果画框全画到左上角去了。实际上它计算的是 original_x_center / image_width,也就是中心点横坐标占整张图宽度的比例。

拿一个实例来说,img_0298_84.txt 中某一行的内容是:

0 0.617651 0.442058 0.283472 0.216485

其中0代表类别是充气船,0.617651表示中心点离左边缘的距离是图像宽度的61.77%,0.442058表示中心点离上边缘距离是图像高度的44.21%,0.283472是框宽度占图像宽度的28.35%,0.216485是框高度占图像高度的21.65%。这四个比例乘上原图尺寸就还原出像素坐标了。

训练时这个格式不需要做任何修改,ultralytics 自己会做尺度缩放。但如果你想用OpenCV画框可视化检查标签质量,就需要把归一化坐标换算成像素坐标,并且注意矩形框右上角的横坐标是 x_center 加 half_width,纵坐标是 y_center 加 half_height,不是直接拿width和height当宽高。

3.2 用Python快速校验标签坐标合法性

拿到数据集后,我的习惯是写一段脚本,遍历所有txt,检查坐标是否越界。因为有时标注工具导出时会把个别框算错,导致 x_center + width/2 大于1,或者坐标出现负数。这种脏数据不会让训练直接报错,但会拉低mAP,而且出错位置随机,很难排查。

import os label_dir = "labels/train" bad_count = 0 for fname in os.listdir(label_dir): if not fname.endswith(".txt"): continue with open(os.path.join(label_dir, fname), "r") as f: for line in f: parts = line.strip().split() if len(parts) != 5: print(f"{fname} 格式异常: {line.strip()}") bad_count += 1 continue cls = parts[0] x, y, w, h = map(float, parts[1:]) # 类别索引必须是整数且在有效范围内 if not cls.isdigit() or int(cls) not in [0, 1, 2]: print(f"{fname} 类别索引异常: {line.strip()}") bad_count += 1 # 坐标必须在0到1之间,宽高不能为负 if not (0 <= x <= 1 and 0 <= y <= 1 and 0 < w <= 1 and 0 < h <= 1): print(f"{fname} 归一化坐标越界: {line.strip()}") bad_count += 1 print(f"扫描完成,异常标签数量: {bad_count}")

这段脚本先按空格拆分标签行,再逐一校验五个字段。类别索引限定在[0,1,2]是因为这个数据集只有三个类别,如果你自己在扩展数据集,改成你的真实类别数即可。归一化坐标理论上必须在0到1之间,但要注意w和h大于1的情况一般不会出现,如果出现说明标注框比整张图还大,原因通常是图像尺寸取错了。

扫描结果如果异常标签数量超过10个,我建议直接从训练集中剔除这些文件,而不是尝试修复,因为修复框坐标需要原图辅助,工作量大还容易改错。少量坏标签剔除后不影响整体训练。

3.3 画框可视化:检查标签对应目标是否准确

坐标校验只是第一步,更直观的检查是把框画到图上。这一步能肉眼确认类别是否标反、框是否贴边,比mAP曲线更早暴露问题。

import cv2 img = cv2.imread("images/train/img_0298_84.jpg") h, w = img.shape[:2] with open("labels/train/img_0298_84.txt") as f: for line in f: cls, x, y, bw, bh = line.split() x, y, bw, bh = map(float, [x, y, bw, bh]) x1 = int((x - bw / 2) * w) y1 = int((y - bh / 2) * h) x2 = int((x + bw / 2) * w) y2 = int((y + bh / 2) * h) color = (0, 255, 0) if cls == "0" else (0, 0, 255) if cls == "1" else (255, 0, 0) cv2.rectangle(img, (x1, y1), (x2, y2), color, 2) cv2.imwrite("check.jpg", img)

画框代码里用类别的颜色区分充气船、独木舟和船舶。看到绿色框是充气船、红色框是独木舟、蓝色框是船舶。抽20-30张图看下来,如果边界框都紧贴船体且类别看起来合理,标签质量就算过关。

4. VOC格式:xml里藏着原始像素坐标,转txt时的四个边界坑

4.1 读取xml标签结构,理解bbox坐标存储方式

voc格式的xml是Pascal VOC标准,object节点下会有bndbox节点,里面存 xmin、ymin、xmax、ymax 四个像素值。下面是这个数据集里典型的xml内容结构:

<annotation> <folder>images</folder> <filename>img_0529_31.jpg</filename> <size> <width>1280</width> <height>720</height> <depth>3</depth> </size> <object> <name>kayak</name> <bndbox> <xmin>412</xmin> <ymin>233</ymin> <xmax>587</xmax> <ymax>401</ymax> </bndbox> </object> </annotation>

转换逻辑是:x_center = (xmin + xmax) / 2 / width,y_center = (ymin + ymax) / 2 / height,框宽 = (xmax - xmin) / width,框高 = (ymax - ymin) / height。四个边界坑第一个就出现在这里——有些xml的坐标不是整数而是浮点,直接用int会丢失精度,导致中心点偏移一两个像素,目标较小的船可能因此错位明显。

第二个坑是xml里的filename有时是绝对路径,有时是文件名不带目录,写转换脚本时不要拼路径,直接用os.path.basename。

第三个坑是namespace。有些标注工具会在xml根节点加自定义命名空间,ElementTree解析时tag会变成带前缀的名字,比如 object 变成 ns0:object,按常规写法取不到值。我一般先解析一段打印根节点tag,确认是否有前缀再写取值逻辑。

第四个坑是坐标越限。像素坐标经过归一化后,理论上应该在0到1之间,但极端情况可能会出现 xmax 超出图像宽度,归一化后大于1。转换脚本里要加clamp逻辑,超出部分裁剪到合法区间,避免训练时出现NaN。

4.2 写一个可靠的xml转txt脚本

虽然这份数据集自带两种格式,不需要手动转换,但你在实际项目中可能拿到只有voc格式的数据,这个转换脚本可以复用。

import xml.etree.ElementTree as ET import os class_names = ["inflatable_boat", "kayak", "ship"] # 必须与data.yaml的names一致 def convert_xml_to_yolo(xml_path, output_path): tree = ET.parse(xml_path) root = tree.getroot() size = root.find("size") img_w = int(size.find("width").text) img_h = int(size.find("height").text) lines = [] for obj in root.iter("object"): name = obj.find("name").text if name not in class_names: continue cls_id = class_names.index(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) # 防止越界像素坐标,先裁剪再归一化 xmin = max(0, xmin) ymin = max(0, ymin) xmax = min(img_w, xmax) ymax = min(img_h, ymax) x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") with open(output_path, "w") as f: f.write("\n".join(lines)) # 批量转换示例 xml_dir = "voc_labels" out_dir = "yolo_labels" os.makedirs(out_dir, exist_ok=True) for fname in os.listdir(xml_dir): if fname.endswith(".xml"): convert_xml_to_yolo( os.path.join(xml_dir, fname), os.path.join(out_dir, fname.replace(".xml", ".txt")) )

代码里我用root.iter("object")而不是root.findall("object"),是为了兼容object节点嵌套层级有差异的标注文件。float转换比int更稳,避免精度损失。输出保留六位小数,对yolo训练来说精度足够。

4.3 校验转换结果与原有txt是否一致

这个数据集同时提供了两种格式,正好可以用来验证转换脚本正确性。随机挑几个文件,按第3章的校验脚本重新解析txt,对比自己转换输出的txt和原有txt的数值,差值在0.001以内视为正常。

有个容易忽略的细节:原有xml里的name标签顺序可能和data.yaml的names顺序不一致。比如xml里写的是"ship"但在names里排第2,转换脚本里用class_names.index(name)就是按names顺序映射的。如果names顺序改动过,索引就会错位,这是转换后模型训练效果莫名其妙变差的常见原因。

5. 避坑记录:训练这类船舶数据集最容易翻车的五个地方

5.1 现象:训练正常跑完,loss曲线下降但验证集mAP只有0.1

原因:标签文件全部存在,但image和label的目录没配对。很多人训练时设置了val路径,但data.yaml里的test路径指向不存在目录,ultralytics不会报错,只是在验证阶段只加载了部分数据。

解决:训练前先打印训练集和验证集的数量,以及每个数据集目录下实际有多少图片多少标签,确保数字和划分文件一致。我通常写一个三行脚本统计文件数量,比看日志快得多。

5.2 现象:img_0529_31.txt里的第一个数字是5,但数据集只有三类目标

原因:标签文件里混入了背景类或者标注错误,把多个类别索引写成了不相干的值。比较常见的是标注时复制粘贴出了错,或者数据集合并不干净。

解决:用第3章的校验脚本扫一遍,发现异常类别索引,统计数量。如果不超过10个,直接删除对应txt文件或修复索引值;如果很多,说明这个数据集标签有问题,优先找作者确认是否漏传文件,而不是带病训练。

5.3 现象:yolov8训练时报错Assertion 'Labels shape error'

原因:某个图像的标签txt里存在空行或者行内字段数不对。比如文件末尾多了一个换行,解析时出现一个空列表。

解决:读txt时strip掉末尾换行符,空行直接跳过不写入训练。用第3章脚本里的 len(parts) != 5 判断就能提前排除这类问题,不要等训练报错再回头查数据。

5.4 现象:训练收敛很快,但在小船上几乎检测不到目标

原因:这个数据集1088张图中,很多船是小目标,占图像面积不到5%,anchor尺寸在640x640分辨率下覆盖不到。

解决:先分析标签里框的宽高分布,如果小框比例高,把yolov8的anchor设置通过anchors参数调整,或者直接在训练时加大输入图片尺寸到960x960,给网络更多像素识别细节。显存不够的话,用mosic增强和9:16宽高比来近似补偿。

5.5 现象:拆分数据集后训练集mAP很高,但测试集mAP骤降

原因:数据集在划分时没有做随机分层抽样,同一个场景的连续帧全部进了训练集,测试集里全是与训练场景差异大的图片,造成过拟合错觉。

解决:拿到数据先跑一次训练,保存best.pt,然后用val模式分别测train和val两个集合的mAP。如果差值超过15个百分点,说明数据分布有偏,思路是重新洗牌划分,或者补充测试集图片。这个数据集标注里说已划分好,如果遇到这个问题,优先怀疑划分逻辑,而不是模型结构。

6. 训练与进阶验证:修改data.yaml到实际调参的完整命令

6.1 修改data.yaml的关键改动点

打开data.yaml后,第一件事是确认路径存在,第二件事是确认names顺序。假如这份数据集的data.yaml写成这样:

train: ../images/train val: ../images/val test: ../images/test nc: 3 names: ['inflatable_boat', 'kayak', 'ship']

如果你把数据集解压在D盘yolo_ship文件夹下,而相对路径写法是 ../images/train,程序会尝试在上一级目录找images,必然失败。最稳的方式是改成绝对路径:

train: D:/yolo_ship/images/train val: D:/yolo_ship/images/val test: D:/yolo_ship/images/test

yolov5和yolov8都接受正斜杠的Windows绝对路径,不需要转义。改完后用一行命令验证配置能不能加载:

python -c "from ultralytics import YOLO; model = YOLO('yolov8s.yaml'); print(model.model)")

这条命令会打印模型结构,如果data.yaml路径有问题,这里不会直接报错,但至少能看到模型是否成功实例化。真正的路径验证在下一步训练命令里会触发。

6.2 yolov8训练命令的参数解释

基础训练命令如下:

yolo train model=yolov8s.pt data=D:/yolo_ship/data.yaml epochs=100 batch=16 imgsz=640 device=0

epochs设100是船舶检测的常见起步值,数据集1088张且目标较小,100轮能看出loss收敛趋势。batch=16是兼顾显存和梯度稳定性的常见选择,如果你显卡只有8GB显存,降到8。device=0表示用第一块GPU,只有CPU就写device=cpu,但训练速度会非常慢,不建议。

训练过程中重点看的指标不是loss绝对值,而是val/box_loss与val/cls_loss是否同时下降,以及mAP50和mAP50-95的变化趋势。yolov8的日志里会直接打印这些,不需要额外装工具。

6.3 验证与进阶技巧:用验证集定位误检

训练结束后,用best.pt做验证:

yolo predict model=best.pt source=D:/yolo_ship/images/val save=True

然后打开输出的预测图,重点看两类错误:一是把水面波纹误检成充气船,这类误检通常是置信度阈值太低,可以调高conf到0.35;二是相邻两艘船被一个框框住,这需要做NMS后处理或调整anchor。这种视觉检查比单纯看mAP更能判断模型落地的可靠性。

如果验证结果显示充气船类别容易被漏检,我后续进阶做法是用第3章的校验脚本统计该类别框的平均面积,如果远小于其他类别,就在训练命令里加上 scale=0.5,1.5 增强,或者单独用该类别图片做一次fine-tune。从那以后我每次拿到新数据集,都强制走一遍「统计标签→坐标校验→画框抽查→训练→预测图抽查」这条流程,虽然多花一个小时,但能省下后面反复调参的几天。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询