ARTICLE DETAIL

资讯详情

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

YOLO多类别船舶检测数据集:12122张图像从标签校验到训练部署全流程

YOLO多类别船舶检测数据集:12122张图像从标签校验到训练部署全流程 简介面向YOLO系列算法的多类别船舶检测数据集包含12122张带标注图像覆盖航空母舰、潜水艇、游船、集装箱船、散货船、帆船等常见船舶类别可直接用于目标检测模型的训练、验证与测试。压缩包内共2000个文件以XML标注文件为主总大小约231.66MB同时提供YOLO格式txt与VOC格式xml两套标签分别存放便于不同框架直接读取。包体结构清晰标签与图像分开存放方便按目录检索。包内已包含data.yaml配置文件数据已划分好训练集、验证集和测试集接入YOLOv5、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLO11等版本即可快速开始实验。YOLO标签采用归一化中心点与宽高格式该格式规范且无需额外预处理适合进行迁移学习、算法效果对比、消融实验或船舶检测专项优化。目前已有155人学习下载适合需要现成船舶数据集进行快速验证的开发者与研究者使用。1. 多类别船舶检测数据集12212 张带标签图像到底能解决什么做港口航道监控或海上目标识别时最卡人的环节往往不是模型选型而是数据。你要的船舶图像可能分布在各种分辨率、各种天气、各种拍摄角度里一张张标注既费时间又费人力。这份标题里的 YOLO 多类别船舶检测数据集12122 张带标签图像覆盖航空母舰、潜水艇、游船、集装箱船、散货船、帆船等类别拿到手就能直接喂给 YOLO 系列做训练省掉从零采集和标注的那段最枯燥的周期。适合两类人一类是做船舶识别落地项目、需要快速拿到可用 baseline 的工程师另一类是刚接触 YOLO 训练流程、想用一份真实数据把整个链路跑通的学习者。但数据在手不代表一定能训出好东西格式校验、类别映射、训练参数这几关不过再干净的数据也会让你翻车。2. 解压与格式解读ZIP 里的 images/labels 结构与 YOLO 标签规范2.1 拿到 ZIP 后的第一件事解压并核对目录结构这份数据集以 zip 压缩包形式分发第一步不是急着训练而是先把目录结构看清楚。常见做法是在项目根目录下单独建一个data/文件夹存放避免解压出来的文件散落在各处。解压命令本身没什么玄学但有两个参数值得留意-q静默解压避免刷屏-d指定目标目录防止压缩包内文件名混乱把当前目录搞得一团糟。mkdir -p ./ship_dataset unzip -q yolo算法-多类别船舶检测数据集-12122张图像带标签--航空母舰-潜水艇-游船-集装箱船-猛拉-散货船-帆船.zip -d ./ship_dataset cd ./ship_dataset ls -la解压后我一般会先看两件事第一images和labels目录是否存在且同级第二两边文件数量是否一致。数据集的常规组织方式就是 images 放图像、labels 放同名 txt 标注文件两者通过文件名前缀关联比如0001.jpg对应0001.txt。文件数量不一致往往意味着某个类别的标注缺失或图像损坏这在后续训练里会造成AssertionError或 loss 异常越早发现越好。运行find . -name *.jpg | wc -l和find . -name *.txt | wc -l对比两侧总数12122 张图像对应的标注文件数量理论上也应相等。如果发现少了文件优先检查是不是有零字节的空 txt 被系统过滤了。2.2 YOLO 的 txt 标签不是框坐标是归一化坐标很多人第一次接触 YOLO 格式的标注文件时会下意识以为里面存的是x1 y1 x2 y2这类像素坐标实际上 YOLO 系yolov5 / yolov6 / yolov8统一使用归一化中心点格式。每一行由五个数值组成class_id x_center y_center width height其中x_center、y_center是目标中心点相对图像宽高的比例width、height是目标框宽高相对图像宽高的比例取值范围理论上应该在 0 到 1 之间。# 打开一个标注文件看格式 cat ./ship_dataset/labels/0001.txt比如某一行是2 0.5432 0.4561 0.1203 0.0876含义就是类别 id 为 2目标中心位于图像横向 54.32%、纵向 45.61% 的位置约占图像宽度 12.03%、高度 8.76%。这种格式的好处是无论输入图像缩放到 640 还是 1280标注都不用跟着改模型在预处理时统一做 letterbox 填充框仍然有效。但这也会带来一个坑从标注工具或网上爬下来的数据如果是像素坐标必须经过转换才能用于训练。转换公式并不复杂x_center (x1 x2) / 2 / img_wwidth (x2 - x1) / img_w纵轴同理。强烈建议写个脚本批量处理完再人工抽样验证别在转换环节省时间因为一旦格式错了训练时模型学到的框位置全是偏移的。2.3 类别文件与 YAML训练前必须先搞清楚类别顺序YOLO 标签中的class_id只是数字真正对应什么类别由classes.txt或data.yaml里的names决定。这个顺序错位是数据集使用里最常见的翻车点之一。假设标题里列出了航空母舰、潜水艇、游船、集装箱船、猛拉、散货船、帆船这七个类别那么names数组的索引 0 到 6 必须和标注文件里的 class_id 一一对应。如果训练时data.yaml里把names写成了先帆船再航空母舰那模型会把所有标注为 0 的航空母舰当成帆船来学而且整个过程没有任何报错最后 val 出来的混淆矩阵会非常诡异。# ship.yaml 示例 path: ./ship_dataset train: images/train val: images/val nc: 7 names: 0: aircraft_carrier 1: submarine 2: cruise_ship 3: container_ship 4: fishing_boat # 对应标题中的猛拉需确认实际含义 5: bulk_carrier 6: sailboat注意示例中的“猛拉”一类这个中文名比较特殊我在处理这类数据集时一般会先查原始标签文件或 README 确认对应的英文类别名。如果是直接网上下载的数据集强烈建议打开所有 txt 文件统计每种类别 id 出现的次数确认没有额外类别混入。这一步不需要写复杂脚本一条 shell 命令就能做到# 统计所有标签文件中各类别出现的次数 cat ./ship_dataset/labels/*.txt | awk {print $1} | sort | uniq -c输出的结果里如果出现大于等于 7 的 id说明标签里有超范围类别训练时nc设置会和实际标签冲突。这种问题在 YOLOv8 上通常会报IndexError但在部分旧版本可能只是 loss 异常务必提前排查。3. 训练前必修课数据校验、样本可视化与 train/val 划分3.1 写一个标签校验脚本把越界框和空标注一次筛出来做过几次数据集清洗后你会发现网上公开的数据集基本都带点小毛病坐标越界、宽高为 0、标注文件为空、class_id 超出类别总数。这些问题单独看影响不大但累积在一起会让训练过程变得极不稳定尤其越界框会导致 YOLO 在计算 loss 时出现梯度异常。我每拿到一份新数据集都会先跑一个校验脚本把非法标签一次性筛出来再决定是删除还是修正。# validate_labels.py import os from pathlib import Path labels_dir Path(./ship_dataset/labels) images_dir Path(./ship_dataset/images) num_classes 7 # 和 data.yaml 中 nc 保持一致 invalid_files [] empty_files [] class_id_out_of_range [] for label_file in sorted(labels_dir.glob(*.txt)): lines label_file.read_text().strip().splitlines() if len(lines) 0: empty_files.append(str(label_file)) continue for line in lines: parts line.split() if len(parts) ! 5: invalid_files.append((str(label_file), 字段数不为5: line)) continue cls_id int(float(parts[0])) x_center, y_center, width, height map(float, parts[1:]) if cls_id num_classes: class_id_out_of_range.append((str(label_file), cls_id)) if width 0 or height 0: invalid_files.append((str(label_file), 宽高非正: line)) # 越界检查中心点不在0~1或框超出图像范围过多 if not (0 x_center 1 and 0 y_center 1): invalid_files.append((str(label_file), 中心点越界: line)) if width 1 or height 1: invalid_files.append((str(label_file), 宽高比例超1: line)) print(f空标注文件: {len(empty_files)} 个) print(f格式非法文件: {len(invalid_files)} 个) for f, reason in invalid_files[:10]: print(f {f} - {reason}) print(f类别ID超范围: {len(class_id_out_of_range)} 个) for f, cls_id in class_id_out_of_range[:10]: print(f {f} - class_id{cls_id})这个脚本的逻辑很简单但有三个参数要注意。第一num_classes必须和你后续训练配置里的nc一致否则类别 id 越界检查没有意义。第二宽高为负或为 0 的框必须删除因为 YOLO 不会自动帮你过滤这些框会让 anchor 匹配阶段产生无效正样本。第三中心点坐标越界的框不建议直接删除有些是标注工具导出时的四舍五入误差偏移量在千分之一以内可以保留如果偏移超过 0.05大概率是标注和图像没对齐需要回到可视化阶段确认。脚本跑完后把非法文件单独挪到一个quarantine目录而不是直接删除给自己留个后悔药。3.2 可视化标注结果发现标签和图像对不上的最快方式校验脚本只能查出格式问题查不出内容问题——比如图像里明明是一艘散货船标签却标成了集装箱船或者框的位置整体偏移到了背景上。这种错标在公开数据集里并不罕见尤其是多类别数据集标注质量参差不齐。最快的方式是用 OpenCV 把标注框画到图像上批量生成可视化图片然后人工抽查几十张。# visualize_labels.py import cv2 from pathlib import Path images_dir Path(./ship_dataset/images) labels_dir Path(./ship_dataset/labels) output_dir Path(./ship_dataset/visualized) output_dir.mkdir(exist_okTrue) class_names [ aircraft_carrier, submarine, cruise_ship, container_ship, fishing_boat, bulk_carrier, sailboat ] # 随机挑20张做可视化避免全部生成浪费时间 import random all_images list(images_dir.glob(*.jpg)) random.seed(42) sample_images random.sample(all_images, min(20, len(all_images))) for img_path in sample_images: img cv2.imread(str(img_path)) h, w img.shape[:2] label_path labels_dir / (img_path.stem .txt) if not label_path.exists(): continue for line in label_path.read_text().strip().splitlines(): parts line.split() cls_id, x_center, y_center, bw, bh ( int(float(parts[0])), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) ) x1 int((x_center - bw / 2) * w) y1 int((y_center - bh / 2) * h) x2 int((x_center bw / 2) * w) y2 int((y_center bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, class_names[cls_id] if cls_id len(class_names) else str(cls_id), (x1, max(0, y1 - 5)), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) out_path output_dir / (img_path.stem _vis.jpg) cv2.imwrite(str(out_path), img) print(f已生成: {out_path})可视化脚本里有几个细节值得说。坐标换算时要把归一化坐标乘回原始图像宽高而不是乘 640 或模型输入尺寸——因为原图没有经过 letterbox 缩放标注坐标本来就是相对于原图的。画框时类别标签字符串建议直接用class_names查表而不是打印数字 id这样抽查时一眼就能看出类别是否标错。另外抽查策略不要全用随机建议每种类别至少抽两张尤其是样本数少的类别比如航空母舰因为少样本类别的标注质量往往更不稳定。如果发现有大量的标签错位别犹豫直接用标注工具重新标注这一小部分数据也不要试图让模型去拟合脏标签。3.3 按比例切分 train/val不留重复图像到两个集合数据划分看似简单但有一个容易被忽略的坑如果直接按顺序取前 80% 做训练、后 20% 做验证而这份数据集的图像本身就按类别或场景存放就可能导致某些类别在训练集里几乎没出现过。正确的做法是按类别做分层抽样保证每个类别在 train 和 val 中的占比大致相同。# split_dataset.py import random import shutil from pathlib import Path random.seed(42) images_dir Path(./ship_dataset/images) labels_dir Path(./ship_dataset/labels) val_ratio 0.2 # 收集所有样本路径 samples [] for img_path in images_dir.glob(*.jpg): label_path labels_dir / (img_path.stem .txt) if label_path.exists(): # 读取第一个类别id作为分层依据一个图像可能有多个目标取第一个 first_cls int(label_path.read_text().strip().splitlines()[0].split()[0]) samples.append((img_path, label_path, first_cls)) # 按类别分组 from collections import defaultdict grouped defaultdict(list) for img_path, label_path, cls_id in samples: grouped[cls_id].append((img_path, label_path)) for cls_id, items in grouped.items(): random.shuffle(items) val_count max(1, int(len(items) * val_ratio)) val_items items[:val_count] train_items items[val_count:] # 创建目标目录 for split, subset in [(train, train_items), (val, val_items)]: images_dst Path(f./ship_dataset/images/{split}) labels_dst Path(f./ship_dataset/labels/{split}) images_dst.mkdir(parentsTrue, exist_okTrue) labels_dst.mkdir(parentsTrue, exist_okTrue) for img_path, label_path in subset: shutil.copy2(str(img_path), str(images_dst / img_path.name)) shutil.copy2(str(label_path), str(labels_dst / label_path.name)) print(f类别 {cls_id}: 训练 {len(train_items)} 张, 验证 {len(val_items)} 张)分层抽样的核心价值在于避免小样本类别在验证集里被分走太多。比如整个数据集里航空母舰只有 80 张按 20% 划分应该留 16 张做验证如果随机划分可能一张都分不到验证集也可能分走 30 张导致训练集不够。这个脚本按类别分别 shuffle 后再切分保证每个类别的验证集至少有一张。还有个提醒copy2会保留原始文件但如果你磁盘空间吃紧可以直接把shutil.move搬家到对应目录。船类数据集图像普遍在几百 KB 到几 MB12122 张全部复制一份可能多占几个 GB提前确认磁盘余量。4. 用 YOLOv8 训练船舶检测模型yaml 配置、预训练权重与关键参数4.1 data.yaml 与类别数为什么 nc 必须和实际标签严格一致训练 YOLOv8 自己的数据集第一步是准备好data.yaml。上一章给的示例文件已经包含了路径信息但这里要展开讲两个经常出问题的字段。path是数据集根目录的绝对路径或相对路径YOLOv8 会基于这个路径拼接train和val的值。如果你把train写成images/train而path是./ship_dataset那么最终读取的目录就是./ship_dataset/images/train。很多人在这个环节报FileNotFoundError或者训练时提示Found 0 images十有八九是path路径没对上。建议直接写绝对路径避免相对路径在不同终端工作目录下解析不一致。nc是最容易出低级错误但也最致命的一个参数。标注文件里class_id为 0 到 6nc就必须是 7写成 8 或者 6 都会出问题。写成 8 时模型输出维度比实际大训练不会报错但最后一类永远没有正样本mAP 会莫名其妙偏低写成 6 时训练过程大概率直接崩溃因为解析标签时发现 class_id 超界。我的习惯是在写完 yaml 后先跑一个快速校验python -c import yaml; dyaml.safe_load(open(ship.yaml)); print(d[nc], len(d[names]))如果nc和names长度不一致训练前就修掉别把问题留给训练日志。另外names的顺序决定了混淆矩阵里的显示顺序建议按照数据集中类别 id 的实际含义填不要按自己喜好重排。4.2 选择预训练权重yolov8s.pt 与从头训练的区别YOLOv8 训练时通过model参数指定模型权重文件。常见做法是写yolov8s.pt此时框架会自动下载 YOLOv8s 在 COCO 上的预训练权重然后在这个基础上 fine-tune。这样做的好处是模型已经具备通用的特征提取能力船舶的纹理、轮廓、边缘等低级特征和 COCO 里很多类别是共通的收敛速度和最终精度都会比从零训练好不少。与之相对的是yolov8s.yaml——注意后缀是.yaml而不是.pt这表示完全随机初始化、不做任何预训练。除非你的数据集和 COCO 差异极大比如医学影像、卫星遥感否则不推荐这么做预训练权重带来的提升通常在 5 到 10 个点的 mAP 以上而且训练轮数可以大幅缩减。# 首次运行会自动下载 pre-trained 权重之后会缓存在本地 yolo detect train dataship.yaml modelyolov8s.pt epochs150 imgsz640 batch16 device0 lr00.01 close_mosaic20这个命令里yolov8s.pt选择的是 small 版本。如果你的显存比较紧张或者只需要快速验证效果可以换yolov8n.ptnano 版本如果追求最高精度且显存足够yolov8m.pt或yolov8l.pt是更好的选择。不同尺寸模型在 V100 32G 上的表现差异明显nano 版本 batch 可以开到 64训练速度极快但精度有限small 版本是精度和速度的平衡点large 版本 batch 通常只能开到 16 到 24但船舶这种中等尺寸目标的表现会更好。这里我一般会先跑 small 版本拿到一个 baseline再根据结果决定是否换更大的模型而不是一开始就上最重的模型浪费时间。4.3 训练命令与超参数bs、imgsz、mosaic 关闭时机的搭配训练命令看着简单但参数之间的搭配才是真正影响结果的地方。imgsz640是输入分辨率对船舶检测来说这个值通常够用前提是目标在图像中占据的面积不算太小。数据集里如果有大量远距离小目标比如无人机俯拍画面里的散货船建议把imgsz提到 1280代价是显存占用翻倍、训练时间变长但小目标的 recall 会显著改善。batch16受限于显存V100 32G 上跑yolov8s可以开到 32 以上但要注意lr0需要相应调整。一般经验是 batch 翻倍时学习率也可以翻倍不过别轻易动这个参数默认的 0.01 在大多数数据集上表现稳定。close_mosaic20是 YOLOv8 比较新的一个参数作用是在训练最后 20 个 epoch 关闭 mosaic 数据增强。mosaic 增强把四张图拼成一张训练样本能大幅提升模型对不同尺度目标的适应能力但在训练后期目标检测的精度已经接近收敛时mosaic 引入的拼接边界和目标截断反而会扰动模型微调导致最后的 mAP 不稳定。这个参数可以看作一个免费的精度提升技巧很多人在训练时完全不调它结果模型在最后 10 个 epoch 的 val 波动特别大。建议在总 epoch 数较大时150 以上设置close_mosaic10~20如果 epoch 只有 50 到 80可以把close_mosaic设置成 5 到 10避免 mosaic 增强的有效训练时间过短。训练过程中的日志需要盯几个关键指标。box_loss和cls_loss应该整体稳步下降如果出现某个 epoch 突然飙升再恢复不用太在意但如果loss变成nan或者开始剧烈震荡就需要停下来了这个问题下一章专门说。训练结束后模型保存在runs/detect/train/weights/best.pt和last.ptbest.pt是按验证集指标选出的最优权重后续验证和推理都用它。5. 训练过程中的五个高频坑BN 崩溃、炸框与标签不匹配5.1 BN 崩溃现象是 loss 突然变 NaN这是 YOLO 系列训练里最有名的一个玄学问题。现象是训练正常跑了几十个 epoch某个 epoch 开始 loss 突然变成nan然后后面全部是nan怎么调学习率都没用。原因通常有三个一是 batch size 太小导致 BatchNorm 层的统计量不稳定尤其在batch4甚至更小时极易触发二是学习率设置过大梯度更新一步跨太远直接导致数值溢出三是训练数据里存在异常标注比如某个框的宽或高为 0或者坐标值远超 1导致 loss 计算时出现除零或 log 0。解决办法按优先级来。第一步把batch提到至少 8如果显存实在不够就换yolov8n模型、降低imgsz到 416也要保证 batch 不低于 8第二步把lr0降到 0.001重新从预训练权重开始训练损失掉两三个 epoch 的进度总比重头再来好第三步回到第 3 章的校验脚本把所有宽高为 0、坐标为负的标签全部隔离掉。我在实践中发现第三个原因最容易坑到人因为数据集大多不是自己标注的有些标注工具导出的空框会被写成一个全 0 行模型读到这种标签直接算 loss 就会得到nan。遇到 BN 崩溃别急着调网络结构99% 的情况是数据或超参数问题和模型本身没有关系。5.2 混淆矩阵总合不唯一先检查标签再怪模型训练完成后看混淆矩阵你会发现一个诡异现象每个类别行加起来的值不等于该类别在验证集中的真实样本数对角线外的数值总和也对不上。很多人都遇到过这个情况第一反应是模型预测出了问题但做数据分析后会发现根因往往在验证集的标注上。YOLOv8 的混淆矩阵统计的是「真实框 预测框」的匹配关系如果验证集里同一个目标被标注了两次重复框或者一张图像同时出现在 train 和 val 两个集合里混淆矩阵的计数就会错乱。解决路径分两步。第一步回到数据划分环节检查你的 train 和 val 是否真的没有重叠图片。很多人手动划分时用了不带 seed 的随机函数两次运行得到不同的划分结果或者复制图片时没有检查文件名冲突。第二步检查标注文件内是否有完全重复的行——两个框的中心点和宽高完全一致这基本可以判定为同一目标的重复标注。写一个去重脚本把这些行删掉就行。如果这两步确认没问题再考虑模型本身的问题比如某个类别的置信度阈值设太低导致大量误检进入混淆矩阵统计。记住一个原则数据集的问题永远比模型的问题更常见先怀疑数据。5.3 小目标船舶漏检严重注意 imgsz 与 mosaic 的相互作用船舶检测有一个天然难点目标尺寸分布极其不均。航空母舰可能在图像里占据大片区域而远处的帆船可能只有十几个像素宽。如果你的训练结果里小目标类别的 recall 明显偏低首先去看数据集里目标框的尺寸分布而不是急着换更复杂的模型。统计所有标签中归一化宽度乘以imgsz得到像素宽度如果大量目标在 20 像素以下说明当前 640 的输入分辨率把这些目标细节基本抹掉了。解决方案有两个方向。第一把imgsz提高到 1280让模型有更多像素去学习小目标特征这是最直接有效的手段但显存占用会变成原来的四倍batch要相应减半。第二如果imgsz已经提到 1280 仍然漏检考虑使用 SAHI 这类切片推理工具把大图切成带重叠的块分别推理再合并结果本质上相当于把每个目标放大到模型更容易识别的尺度。mosaic 增强在这个场景下是一把双刃剑它让模型见到更多不同尺度的目标拼接但也可能让过小的目标在拼接后更难以辨认。我一般会在训练后期用close_mosaic关闭它让模型在最后阶段专注学习完整目标的特征。5.4 陌生类别名“猛拉”导致训练中断或类别错乱标题里出现了“猛拉”这个类别我在处理类似数据集时遇到过两种情况。第一种是纯粹的中文翻译名对应某个英文学名比如渔船fishing boat或拖网渔船trawler翻译工具直译后变成了一个生僻词。第二种是数据集作者自定义的类别可能代表某种特定船型。不管哪种情况如果训练时只把names配置成 7 个类别但标签文件中出现了第 8 个 class_id就会触发IndexError或者类别错乱。排查方法其实前面已经写过用awk {print $1}统计所有 class_id 的范围。一旦发现异常打开对应标签看看这个 id 关联的目标长什么样再决定是归并到已有类别还是新增一个类别。我个人的建议是除非目标形态差异足够大否则归并到相近类别更稳妥因为新增类别会大幅降低该类的样本量导致类别不平衡问题加剧。这里特别强调不要想当然地跳过这一步数据集的类别体系必须与你的业务需求对齐模型才能学到你想要的东西。如果你要做的是通用船舶检测可以把“猛拉”归并到散货船或渔船如果业务上需要精确区分那就要确保该类有足够的样本量至少几百张。5.5 类别严重不平衡航空母舰样本远少于帆船时怎么办多类别数据集的通病是类别分布高度不均。标题里航空母舰、潜水艇这类特定船型采集难度大样本量可能只有几十张而帆船、游船是常见目标样本量可能上千张。如果不做处理模型会把所有精力放在拟合多数类上少数类几乎学不到有效特征验证时少数类的 recall 趋近于零。处理类别不平衡有几种常见做法按生效优先级排列。第一是数据层面的欠采样和过采样对多数类随机抽取一部分对少数类做复制增强让各类样本量相对均衡。但复制增强要注意别在同一个 batch 里出现太多重复样本否则模型会对复制样本过拟合。第二是损失函数层面的加权YOLOv8 目前没有直接暴露类别权重参数但可以手动修改loss.py或者使用第三方实现给cls_loss乘上每个类别的权重系数权重一般设为总样本数除以该类别样本数的平方根避免权重过大导致训练不稳定。第三也是最容易被忽略的——不要为了迁就少数类把训练轮数拉得太长过长的训练会让多数类严重过拟合少数类却不一定会变得更好。优先解决标注质量再谈类别均衡。6. 从 mAP 到部署评估结果怎么看推理参数怎么调6.1 用 val 脚本拿 mAP50 与混淆矩阵训练完后跑验证是必须做的一步别只看训练日志里的 loss 曲线就草率收工。YOLOv8 的 val 命令会输出一整套指标文件包括results.csv、confusion_matrix.png、F1_curve.png、PR_curve.png等。其中最有价值的两个文件是results.csv里的分类别 AP 和confusion_matrix.png里的可视化结果。分类别 AP 能告诉你每类船型的检测精度到底如何而不只是看一个被我称为“平均幻觉”的 mAP 数值。# 用 best.pt 验证输出指标到 runs/detect/val yolo detect val modelruns/detect/train/weights/best.pt dataship.yaml跑完命令后打开runs/detect/val/confusion_matrix.png重点看每一行的对角线数值占比。如果帆船这类多数类的对角线数值很高而航空母舰几乎全是背景误检说明类别不平衡的影响比模型能力更严重。此时回看results.csv里对应类别的 recall结合第 5 章的类别平衡方案重新组织数据通常比更换更大的模型更有效。另一个容易被忽略的点是看val时的imgsz参数验证时的推理分辨率应该和训练时保持一致否则指标对比没有意义。如果你的验证环境显存不够跑 1280 的分辨率也要在报告中注明评估条件避免后续对比时误判模型退化。6.2 推理阶段的 NMS 参数与目标大小适配训练好的模型最终要用于推理部署此时conf_thres和iou_thres这两个参数比模型权重本身更影响实际效果。conf_thres是置信度阈值默认 0.25意味着模型认为目标存在的概率大于 25% 才输出检测框。船舶场景里远距离小目标的置信度普遍偏低如果按默认阈值过滤很多小目标会被误杀建议下调到 0.1 甚至 0.05。iou_thres是 NMS 的 IoU 阈值默认 0.7用于决定哪些重叠框需要合并。船舶密集停靠的场景中相邻船只的检测框会有大量重叠阈值太高会导致多个框同时保留造成重复检测阈值太低又会把本来应该分离的两艘船合并成一个框。# 推理示例conf 调低以召回小目标iou 调低以抑制密集场景的重复框 yolo detect predict modelruns/detect/train/weights/best.pt source./test_images conf0.1 iou0.4在 V100 或 Jetson 这类设备上做实际部署时还要考虑模型导出格式。ONNX 是跨平台部署的基础TensorRT 在 NVIDIA 设备上能额外获得 20% 到 30% 的加速但 float16 精度模式下部分小目标检测结果会有轻微退化需要实测权衡。我在自己的项目里遇到过类似情况把 YOLOv8s 导出成 TensorRT 后帆船类别的 recall 掉了 3 个点最后只能在速度和精度之间选了速度优先但保留了一个置信度阈值较低的推理模式来弥补。没有万能的参数组合每份数据集都要单独调跑一次 val 看结果再改阈值比任何经验值都可靠。做这份数据集训练时我把“猛拉”当成一个普通类别直接跑结果训练中后期航空母舰的 AP 一直为零查了标签才发现该类别下所有目标都被标成了另一个 id。从那以后我养成一个习惯每次拿到新数据集先写校验脚本、再可视化抽查、最后才划分训练集。这套流程虽然多花一两个小时但省下的调试时间往往是以天计的。希望帮到你。本文还有配套的精品资源点击获取
返回列表