简介:面向yolo系列算法的多类别船舶检测数据集,内容覆盖航空母舰、潜水艇、游船、集装箱船、散货船、帆船等常见船型,适用于港口船舶识别、航道流量统计、海上目标监控以及智慧海洋相关项目,可直接用于目标检测模型的训练、验证与测试。数据集已预先完成训练集、验证集、测试集划分,并附带data.yaml配置文件,兼容yolov5、yolov7、yolov8、yolov9、yolov10及yolo11等主流版本,可大幅降低建模前的数据准备成本;同时图片与标注文件按目录组织清晰,便于直接调用。压缩包共2000个文件,主要文件类型为XML标注文件,整体大小约231.66MB;除VOC格式xml外,还提供YOLO格式txt标签,两类标签分别存储于不同文件夹。YOLO标签的每一行对应一个检测目标,依次记录类别索引、归一化后的中心点x坐标、中心点y坐标、宽度和高度;VOC格式XML则以对象为单位保存边界框信息,便于在标注工具中可视化复核。目前已有157人学习下载,适合用作目标检测练手、算法对比、论文实验或毕业设计的基准数据集。
1. 多类别船舶检测,yolo算法为什么是默认起点
做多类别船舶检测,yolo算法几乎是所有从业者默认的起点。这份 12122 张图像的带标签数据集,覆盖航空母舰、潜水艇、游船、集装箱船、猛拉(tugboat,拖轮)、散货船、帆船七类常见船舶,压缩包解压后就能直接喂给 YOLO 系列训练。它的价值不在“又多了一个数据集”,而在把过去需要自己爬图、清洗、标注、统一格式的几个月压缩成一个周末的活。
我最早接触船舶检测是在港口监控项目上,当时自己标了两千张图就迫不及待去训练,结果模型在白天远洋场景还凑合,一到近岸就崩溃。后来拿到这种多类别、带标签、分好训练验证集的数据集,才意识到问题不在算法,在数据质量与类别平衡。这篇文章不打算给你讲花哨的注意力机制,而是站在“拿到 zip 之后怎么一步步落地”的视角,从 YOLO 标签格式、目录组织,到训练参数、避坑排查,再到最后的模型验证,全部按能复现的步骤来。
如果你正准备做船舶识别、海上目标检测,或者只是需要一个多类别目标检测的练手数据集,这份 12122 张图的压缩包就是你的主场。新手可以照着命令跑通全流程,熟手可以重点关注第四章的参数调节和第五章的翻车现场,那几条都是我实际踩过之后才留下的经验。
2. 解压后的数据组织:先把 YOLO 标签格式和类别 id 理顺
2.1 一份合格的数据集包里应该有什么
你拿到的 zip 解压后,常见做法是包含images/和labels/两个兄弟目录,或者按train/、valid/、test/三个子集分别组织。不管哪种,最终要么有划分好的目录,要么有一份train.txt、valid.txt记录图片相对路径。标签文件是 YOLO 格式的纯文本,每行代表一个目标,五个字段依次是:类别 id、归一化中心点 x、归一化中心点 y、归一化宽度 w、归一化高度 h。
关于标题里的“猛拉”,这个类别名就是 tugboat 的直译,在数据集的 names 文件里通常写作tugboat,对应中文“拖轮/拖船”。拿到数据集第一件事不是训练,而是打开 labels 目录随机看几个 txt,确认类别 id 和 names 的对应关系。很多数据集在导出时把类别顺序打乱过,比如集装箱船是 id=3,但你的 names 列表里第 3 个位置写的却是游船,这种错位等到推理时才会暴露,代价是把全部训练时间浪费在了错误映射上。
我建议把类别清单自己整理成一个 Python 列表,顺序严格按数据集自带的data.yaml或names.txt来。这样后续写统计脚本、训练配置、推理可视化,用的都是同一份映射,能避掉一半以上的低级错误。
2.2 用 Python 脚本检查标签合法性,别急着开训
打开一个标签文件,不是只有非空就能训练。常见的问题有三种:坐标出现负数或大于 1(说明混入了像素坐标)、宽高为 0(框退化成线)、类别 id 超出类别总数(说明映射错位)。这些问题在训练时不一定报错,但会让 loss 异常或 mAP 虚高。我第一次跑这个数据集时,就有 17 个框的 x_center 写成了像素值,结果前一百轮 loss 死活降不下去。
用一个脚本把所有 labels 扫一遍,既能看到每类有多少目标,也能定位异常行。下面这个脚本只依赖标准库和 pathlib,直接放在数据根目录运行:
from pathlib import Path label_dir = Path("labels") class_names = [ "aircraft_carrier", "submarine", "cruise_ship", "container_ship", "tugboat", "bulk_carrier", "sailing_ship" ] counts = {name: 0 for name in class_names} bad_lines = [] # 收集格式或范围异常的行 for txt in label_dir.glob("*.txt"): with open(txt, "r", encoding="utf-8") as f: for line in f: parts = line.strip().split() if len(parts) != 5: bad_lines.append(f"{txt.name}: 字段数 {len(parts)}: {line.strip()}") continue cls_id = int(parts[0]) x, y, w, h = map(float, parts[1:]) if not (0 <= cls_id < len(class_names)): bad_lines.append(f"{txt.name}: 类别 id {cls_id} 越界") if not (0 <= x <= 1 and 0 <= y <= 1 and 0 < w <= 1 and 0 < h <= 1): bad_lines.append(f"{txt.name}: 坐标/尺寸异常 {parts}") try: counts[class_names[cls_id]] += 1 except IndexError: pass # 越界已经记录了 print("类别目标数量分布:") for name, num in counts.items(): print(f" {name:20s} {num}") print(f"\n异常行数: {len(bad_lines)}") for item in bad_lines[:20]: print(" ", item)这段代码的核心逻辑是:按行解析,先校验字段数、类别 id、检测框坐标范围,再累加每类目标数。counts字典最终会告诉你这 12122 张图里哪一类占了半壁江山,哪一类少得可怜。这一步不耗时,却能让你对训练前的数据心里有数。
参数说明:bad_lines只记录前 20 条,因为如果文件全面出问题,刷屏反而掩盖了第一处错误;encoding="utf-8"是为了兼容 Windows 下带 BOM 的 txt,避免第一行解析出\ufeff导致 int() 报错;如果int(parts[0])偶尔遇到无法转 int 的行,可以直接在脚本里改成 try/except,并把该行视为异常。
2.3 类别不平衡:先看分布,再定训练策略
拿上面脚本跑完,典型的船舶数据集分布往往是游船、帆船非常多,航空母舰和潜水艇非常少。这符合真实世界——民用船只数量远大于军用舰艇。不平衡直接导致训练后期模型对小类别不敏感,甚至完全忽略。你在第三章训练时如果发现 mAP 在航母类别上掉到 0.2,回头看看统计结果就知道原因。
针对不平衡,常见做法是调整cls_loss权重,或对少数类别做过采样。但在此之前,先确认一个更基本的数字:每张图的平均目标数。如果很多图只有一个框,那么小目标检测本来就难。船舶检测里,航空母舰通常是画面中的大目标,而帆船可能只有几十个像素宽。类别不平衡叠加尺度差异,是这份数据集最需要认真对待的两个点。
统计完分布,建议顺手画一个 bbox 尺寸分布图,用 matplotlib 把归一化宽度和高度做成散点。这个散点图能直观告诉你:是在 640×640 下检测 20 像素的小帆船,还是 200 像素的航母。决定 imgsz 应该取 640 还是 1280,就靠它。
3. 用 YOLOv8 训练船舶检测:最小可跑通的数据准备与命令
3.1 按 YOLOv8 的目录约定重新组织文件
YOLOv8 训练时通过data.yaml指定训练、验证集路径和类别名。常见的目录约定是:
ship_dataset/ images/ train/ val/ test/ labels/ train/ val/ test/但很多 zip 解压出来是扁平的images/和labels/,需要你自己划分。这时候先用 Python 做一次按比例划分,同时保证同名图片和标签放入同一个子集。下面这段脚本按 8:1:1 划分,但它有一个更重要的功能:确保每张图都有对应的 txt 标签,没有标签的图直接剔除,避免训练时踩到空标签的坑。
import random from pathlib import Path import shutil images_dir = Path("images") labels_dir = Path("labels") output_root = Path("ship_split") for split in ["train", "val", "test"]: (output_root / "images" / split).mkdir(parents=True, exist_ok=True) (output_root / "labels" / split).mkdir(parents=True, exist_ok=True) image_files = list(images_dir.glob("*.jpg")) + list(images_dir.glob("*.png")) random.shuffle(image_files) train_ratio, val_ratio = 0.8, 0.1 train_count = int(len(image_files) * train_ratio) val_count = int(len(image_files) * val_ratio) valid_files = [] for img in image_files: label = labels_dir / (img.stem + ".txt") if label.exists(): valid_files.append(img) else: print(f"跳过无标签: {img.name}") splits = {} splits["train"] = valid_files[:train_count] splits["val"] = valid_files[train_count:train_count + val_count] splits["test"] = valid_files[train_count + val_count:] for split, files in splits.items(): for img in files: label = labels_dir / (img.stem + ".txt") shutil.copy(img, output_root / "images" / split / img.name) shutil.copy(label, output_root / "labels" / split / label.name) print(f"{split}: {len(files)} 张")这段脚本的边界条件值得说明:先算train_count和val_count,用切片而非循环内判断,保证三个集合没有重叠;用random.shuffle打乱顺序,但只打乱了图片名单,标签路径始终跟随图片 stem,这是 YOLO 标签命名的基本约定;复制而不是移动,是为了保留原始 zip 解压的备份,训练过程中如果发现标签错误,还能从原始目录修复后重新划分。
参数说明:train_ratio=0.8, val_ratio=0.1是目标检测最常见的比例,测试集 10% 足够验最终效果;如果数据量大到几千张,可以把 0.8 提到 0.85;如果你的数据里同一艘船的连续帧非常多,随机划分会导致验证集和训练集出现高度相似的图像,这时候要按场景或者按视频片段分组划分,后面第五章会专门讲。
3.2 写 data.yaml:names 顺序必须和标签 id 对死
YOLOv8 使用一个 YAML 文件描述数据路径和类别。这个文件不要手动把图片路径写全绝对路径,因为换机器就要改。用相对路径或者占位符都行,但最省事的写法是把path写成数据集根目录,它的子路径直接写images/train和labels/train。
path: ./ship_split train: images/train val: images/val test: images/test names: 0: aircraft_carrier 1: submarine 2: cruise_ship 3: container_ship 4: tugboat 5: bulk_carrier 6: sailing_ship这里类别 id 的顺序不是随意的。你前面在 2.2 节脚本里用的class_names列表顺序,和这个 yaml 里的顺序必须完全一致。否则训练脚本读标签时,第 0 类原先是 aircraft_carrier,你的 yaml 却把第 0 类写成 submarine,训练过程不会报错,但模型输出的类别全部错位。
判断顺序对不对,有一个简单方法:从测试集随机挑三张包含多类船舶的图,运行 YOLOv8 的可视化脚本,把标签画出来。如果一艘航空母舰的框里写的是submarine 0.95,那么你的 yaml 顺序和标签 id 对不上,不用等训练完,当场就能发现。
3.3 用一条命令启动训练:基础参数怎么设
确认 data.yaml 无误后,训练命令是极简的。如果你只是想先跑通,用最小的预训练模型和合理的 epoch 数:
yolo detect train \ data=ship_split/data.yaml \ model=yolov8n.pt \ epochs=100 \ imgsz=640 \ batch=16 \ device=0 \ patience=20 \ name=ship_first_run这条命令背后的逻辑:model=yolov8n.pt是下载 COCO 预训练权重,船舶检测作为下游任务,预训练权重里的通用特征(边缘、纹理)对船舶依然有效,能显著加快收敛;imgsz=640是速度和精度的折中,如果你的 bbox 尺寸分布显示有大量小目标(宽度小于 0.05 倍图宽),可以改成 896 或 1280,但显存占用会成倍增长;batch=16在 8GB 显存下比较稳,如果你有 24GB 显存可以开到 32 甚至 64,batch 越大,类别不平衡问题越容易被平均掉,但也别指望它根治。
patience=20是早停轮数,意思是验证集 mAP 连续 20 轮没有上升就自动停止。我一般把 patience 设成 10 到 20,防止模型反复震荡浪费时间。第一次训练可以不开早停,手动看曲线,但这里的 20 是一个保险值,跑完观察 loss 曲线之后再做调整。
训练结束后,runs/detect/ship_first_run/目录下会出现weights/best.pt和weights/last.pt。best.pt是验证集 mAP 最高的权重,直接拿它去推理;last.pt是最后一轮的权重,两者可能相差很大。在训练过程中,如果发现 last 的置信度普遍偏低,但 best 的 mAP 很高,说明模型后期过拟合了,需要加大增强强度或提前停止。
4. 七类船舶训练的参数调节:类别不平衡与小目标的两个硬骨头
4.1 imgsz 选择:小目标占比高时不能无脑用 640
船舶检测和行人检测有个相似之处:目标尺度分布极广。航空母舰常占据画面四分之一,帆船可能在远处海面上只有 30×30 像素。YOLO 的训练图像尺寸决定了特征图分辨率,YOLOv8 在 640 下最后一次特征图 stride 是 32,也就是说 30 像素的帆船在特征图上只占不足一个点,检测不到才是正常的。
这时候有两种选择:一是把imgsz提高到 896 或 1280,二是保留 640 但依赖数据增强中的 mosaic 和 copy-paste。提高 imgsz 最直接,但也最耗显存。在 8GB 卡上,1280 的 batch 只能开到 4,训练速度慢三倍。我的经验是先跑一个 640、batch 16 的基线,再看验证集里小目标类别的 PR 曲线。如果帆船的召回率低于 0.3,就值得专门跑一个 imgsz=896 的对比组,哪怕只训五十轮。
imgsz=896不是随意拍的,它正好是 640 的 1.4 倍,对应 YOLO 特征图尺寸从 20×20 变成 28×28,小目标在特征图上的像素面积扩大了约 1.96 倍。代价是每个 batch 的显存需求大约是 640 的两倍。如果显卡有限,另一个替代是开启augment=True下的scale=0.5,在训练时随机缩放输入图像,相当于让模型看到不同尺度的目标,但效果不如直接提高 imgsz 来得稳。
4.2 cls_loss 与类别权重:少数类别不是靠加样本就能救
类别不平衡严重时,最简单的做法是在损失函数上给少数类更高的权重。YOLOv8 的训练参数里有cls控制分类损失权重,默认是 0.5。如果你发现航母或潜水艇这类样本很少的类别 AP 极低,可以把cls提高到 1.0 或 2.0,让模型对分类错误更敏感,但代价是定位精度可能轻微下降。
另一种更精确的做法是给不同类别配不同的损失权重,YOLOv8 原生不直接支持按类权重,但你可以通过过采样少数类来近似实现。在 2.3 节的类别统计输出里,假如帆船有 5000 个目标,航空母舰只有 300 个,那么我一般会把包含航母的训练图片复制 5 到 8 份,重新划分后再训练。注意是复制图片并同步复制标签,不是把同一个文件名写两遍。这个方法很土,但非常有效,尤其是在数据增强 mosaic 开启时,复制后的图片会被裁剪组合成新图,相当于给了模型更多机会看到航母的上下文。
如果你不想动数据,也可以在data.yaml里加一个loss_weights字段,但 YOLOv8 的命令行解析不认这个,需要改配置文件或用其他框架。这里不推荐,因为改源码容易埋坑。先试过采样,再试调整cls,两个方向都跑一轮对比,再下结论。
4.3 数据增强参数:近岸场景的“解药”是 mosaic 和 mixup
船舶检测的常见场景有两种:开阔海面的独立目标,和港口、近岸的密集与遮挡目标。近岸场景下,船与船、船与码头建筑物相互遮挡,模型如果只在纯净背景下训练,到了实际监控画面必然会翻车。这份数据集的标注如果包含近岸港口图,那么增强策略会直接影响泛化能力。
YOLOv8 的默认增强已经包含 mosaic(概率 1.0)、mixup(概率 0.0)、flip(水平翻转 0.5)和 hsv 颜色扰动。对船舶来说,水平翻转完全合理,因为船的左右结构不影响类别判断;垂直翻转建议关掉,因为没有一艘船是倒着航行的,强行学习反而会干扰方向特征。Hsv 增强对海面光照变化有帮助,默认的hsv_h=0.015, hsv_s=0.7, hsv_v=0.4已经足够,不需要再加大,否则会让海面颜色失真,模型学会的可能是颜色而不是形状。
mosaic 是 YOLO 系算法的看家本领,四张图拼接成一个输入,等于让模型在更丰富的背景中学习检测,对小目标特别有效。但有一个坑:当验证集图像尺寸和训练集不一致时,mosaic 生成的拼接框可能超出原图边界,导致标签坐标被裁剪,训练 loss 出现骤降又骤升的锯齿。如果你发现训练曲线在第 20 轮附近震荡严重,可以尝试把mosaic从 1.0 降到 0.5 或完全关闭,让模型专注学习真实图像分布。我一般会在近岸场景开启 mosaic,但会把mosaic概率设成 0.8,留一些不拼接的 epoch,让模型适应完整布局。
4.4 batch、学习率和优化器:别让狮子大开口的 batch 撑爆显存
batch 大小对训练稳定性的影响常被低估。batch 太小(小于 8),BN 层的统计量不稳定,模型收敛慢;batch 太大(大于 64),BN 反而容易饱和,且需要同步提高学习率。在船舶检测这类目标数不均匀的数据集上,我建议 batch 至少 16。如果你的显卡只有 6GB,可以把imgsz降到 512,继续保持 batch 16,这样 BN 层才能统计出有意义的均值和方差。
学习率方面,YOLOv8 默认lr0=0.01搭配cos_lr调度,对预训练权重是合适的。你唯一需要调的是batch提高后,要不要同步把lr0调大。如果 batch 从 16 提到 32,可以按线性缩放原则把lr0从 0.01 提到 0.02,但注意不要超过 0.02,否则前几轮 loss 可能爆掉。最直接的监控指标是训练日志里的P(精确率)和R(召回率),如果训练刚开始 P 快速上升但 R 一直低迷,说明学习率偏高,模型只挑置信度最高的框输出,需要降低lr0到 0.005 左右。
另一个容易忽略的参数是weight_decay,默认 0.0005 对 COCO 合适,但在船舶这种背景相对单一的数据集上,我习惯把weight_decay降到 0.0001,防止模型过度正则化,丢失对船体形状的敏感度。这个参数的影响要到训练中后期才体现,如果你发现 val 的 loss 在最后 30 轮不停波动,而训练 loss 还在下降,可以查一下 weight_decay 是不是太大,或者 just 降低 patience 早点收住。
4.5 类别 id 与名字对齐后,做一次“冒烟推理”再训完整
在启动长训练之前,我强烈建议用最短的流程验证一遍整个链路。不要直接跑 100 轮,而是先用 10 轮、imgsz=256、batch 2,把训练脚本跑通一次,确保 data.yaml 能被读、标签能被加载、loss 能够下降。这一步能节省大量排查时间,尤其是当你有多个数据集交替训练时。
冒烟训练完成后,用生成的best.pt对验证集里任意几张图做推理:
yolo predict model=runs/detect/ship_smoke/weights/best.pt source=ship_split/images/val device=0推理出的图像会保存到runs/detect/predict/,打开看看类别标签和置信度。如果一张帆船的框上写的是sailing_ship 0.85,说明标签映射没问题;如果出现submarine 0.9,回去检查 yaml。这一步虽然只花几分钟,但它能救回你后面几十小时的等待。我见过有人跑完一百轮才发现名字顺序错了,那种“白训一场”的血泪经验,拿十分钟就能避免。
5. 避坑:船舶检测数据集训练中的四个典型翻车现场
5.1 标签坐标越界导致 loss 为 NaN
现象:训练在第 12 轮左右 loss 突然变成 NaN,之后一直 NaN,训练进程虽然继续跑,但验证集 mAP 全部归零。
原因:标签 txt 里混入了绝对值坐标。比如某一张图的尺寸是 1920×1080,标注工具导出的 bbox 是100 200 300 400,没有归一化到[0,1]。这在训练早期的 mosaic 增强中可能不会立刻暴露,但当输入缩放比例触发某个框的采样区域超出边界时,损失函数计算出现无穷值。
解决:回到 2.2 节的检查脚本,把异常行过滤出来。如果确认是绝对坐标,就把这些标签重新归一化。常见做法是读取对应图片的宽高,将坐标除以宽高再写回。注意 YOLO 标签的 x_center、y_center 是相对图片宽高归一化的,不是相对框的尺寸,转换公式为x_norm = x_abs / img_width,w_norm = w_abs / img_width,高度同理。转换完成后再次运行脚本,直到bad_lines为空再训练。
5.2 随机划分导致“数据泄漏”,验证集虚高
现象:训练时 mAP 一路涨到 0.92,但把模型部署到现场视频上,检测率惨不忍睹,尤其是船舶跟踪场景,模型频繁漏检。
原因:数据划分时用了随机打乱。这份数据集里的图像很可能来自不同的视频片段,同一艘船在连续帧中外观几乎不变。如果第 100 帧进了训练集,第 101 帧进了验证集,模型在验证时等于“开卷考试”,mAP 虚高,但真实场景里船的角度、光线、姿态都和训练帧不同,立刻现出原形。
解决:按视频片段或按拍摄时间划分。如果你不知道哪些图来自同一视频,可以用文件名前缀判断,船舶数据集的图片名通常包含video1_0001.jpg、video1_0002.jpg这类编号。划分时先提取前缀,把同一前缀的图片全部放入同一集合,再按 8:1:1 切分。如果文件名没有规律,就按场景手动分组,或者干脆放弃随机划分,改成按地理位置或拍摄条件分层采样。
5.3 类别 id 错位但训练不报错,推理时全部张冠李戴
现象:训练过程一切正常,loss 平滑下降,验证集的整体 mAP 也很高。但是当你把模型用于实际识别时,发现航空母舰的框上写的都是 cruise_ship,而且置信度还挺高。
原因:数据集的标签是别人按他们的 names 顺序生成的,你用了自己的 yaml 时顺序不同。YOLO 训练读取标签每行第一个整数作为类别索引,它不管这个数字叫什么名字。如果你的 yaml 把 0 设为 submarine,而标签里 0 原本代表 aircraft_carrier,模型学到的就是错误的映射。
解决:不要偷懒,先在 2.2 节的脚本中统计每个类别 id 的目标数量,再和数据集自带的 readme 或 data.yaml 核对。如果没有原始说明,就随机抽 20 个标注框,打开对应图片人工确认。确认无误后,把class_names列表固定下来,写在训练前的 config 文件中,并写一个注释说明“此顺序对应标签 txt 中的 id”。我自己的习惯是在 data.yaml 的同级目录放一份id_map.txt,内容是每行的id: class_name,训练脚本启动时自动检查这个文件和标签中出现的最大 id 是否一致。
5.4 解压 zip 时中文文件名与路径过长导致文件丢失
现象:用 Windows 自带解压工具解压这份 zip 后,发现 labels 目录里少了几个 txt,但在压缩软件预览里却能看到这些文件。
原因:zip 包在创建时如果包含非 ASCII 文件名,或者目录层级过深,Windows 自带解压器的编码识别可能出错,把中文文件名解压成乱码,甚至因为路径超过 260 字符而自动跳过。船拍数据集的图片命名往往很长,比如2024_08_12_sanya_port_aircraft_carrier_0001.jpg,加上多层目录很容易触发路径限制。
解决:改用 Python 的zipfile模块解压,它能统一处理 UTF-8 文件名,且不受系统路径长度限制。解压时还需要处理非法字符,比如把:替换成_,避免在 Windows 文件系统中创建失败。解压完成后,再运行一遍 2.2 节的脚本,统计图片数和标签数,如果二者数量不一致,就说明有文件在解压或传递过程中丢失,需要从源头重新复制。
6. 验证模型的最后一公里:混淆矩阵、PR 曲线和 F1 阈值怎么用
训练结束并不代表模型能用。YOLOv8 在训练完成后会自动在验证集上计算 mAP50 和 mAP50-95,但这两个指标是整体平均,掩盖了类别之间的巨大差异。我会做两件额外的事:输出每个类别的混淆矩阵,以及画出 P-R 曲线来确定最终置信度阈值。
混淆矩阵可以用yolo val命令自动生成:
yolo detect val \ model=runs/detect/ship_first_run/weights/best.pt \ data=ship_split/data.yaml \ conf=0.001 \ plots=True把conf设成 0.001 是为了让模型尽可能多地预测框,这样混淆矩阵能真实反映它的召回能力。生成的confusion_matrix.png会在验证输出目录里。重点关注两类错误:一是航空母舰和游船互相混淆,这是因为二者都有庞大的白色上层建筑,单纯靠颜色无法区分,需要在训练时引入更多船体结构细节;二是“猛拉”(拖轮)和散货船混淆,因为拖轮常常出现在散货船旁边,遮挡严重。看到这些混淆后,再去决定要不要补充数据或调整损失权重,而不是盲目加大训练轮数。
PR 曲线是另一个关键图。每个类别都会有一条曲线,曲线越靠近右上角越好。如果你的帆船类 PR 曲线明显低于其他类,说明它不是被误检就是被漏检。这时候把鼠标悬停在曲线上找到那个“拐点”,那个位置的置信度就是该类的最优阈值。YOLOv8 推理默认用conf=0.25,但在船舶检测场景,我常常会把这个值降到 0.1 以提高召回率,因为漏掉一艘船比多一个误报框更致命。
我自己第一次做船舶检测时,全程盯着 mAP50,从 0.5 涨到 0.8 就高兴得不行,结果部署后才发现在近岸遮挡场景下,拖轮和帆船几乎全被漏掉。后来学会用混淆矩阵和 PR 曲线去“拆解”性能,才发现 mAP 只是在骗自己。从那以后,每次训练完我都会单独跑一次低置信度的验证,看看模型在最差情况下到底漏了什么,这个习惯帮我避开了很多发布会式的自嗨。
如果你拿到这份 12122 张图像的数据集,我建议先按第 2 章的脚本做诊断,再按第 3 章的命令跑一个 50 轮的基线,然后根据第 4 章的三个现象调参,最后用这一章的验证方法去评估。整个流程走下来,你对 yolo 算法在船舶检测上的掌握程度,会比看十篇论文讲解都扎实。希望这些踩坑经验能帮你少走我当年的弯路,祝训练顺利。
本文还有配套的精品资源,点击获取