ARTICLE DETAIL

资讯详情

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

船舶检测数据集训练YOLO实战:从数据验收到部署避坑指南

船舶检测数据集训练YOLO实战:从数据验收到部署避坑指南 简介这是一份面向目标检测初学者与算法工程师的YOLO船舶目标检测数据集聚焦水面船只、皮划艇、独木舟、摩托艇等水上目标的识别任务可直接用于模型训练、课程实验与算法对比。数据集已按train、val、test完成划分并附带data.yaml配置文件类别仅含boat一类yolov5、yolov7、yolov8等主流框架开箱即可训练。压缩包共2000个文件以1817个txt标注文件、178张jpg图像和5个yaml配置为主整体约114.65MB标注与图像一一对应目录结构清晰便于快速接入训练流程。目前已有1521人学习下载适合需要现成船舶检测数据、验证模型效果或开展迁移学习实践的读者参考使用。1. 船舶检测数据集拿到手之后先别急着喂给 YOLO很多做内河航运、港口监控或者海上执法辅助的同行拿到yolo-boat-detect-dataset-1.zip这类船舶目标检测数据集时第一反应是解压、改data.yaml、直接yolo train。我见过太多人卡在 mAP 只有 0.3 上下然后怀疑是模型不行、显卡不行其实问题出在数据本身——船舶目标在遥感或岸基视角下长宽比极端、密集停泊时框叠框、水面反光导致标注边界模糊这些都不是换个 YOLOv8 权重能解决的。这篇笔记就围绕这个数据集把从解压验收到训练调参、再到踩坑排查的完整路径讲清楚适合已经跑通过 YOLO 官方 COCO 示例、准备切到自己垂类数据的工程师也适合刚接触yolo数据集制作、想少走弯路的同学。核心就一件事让这份船舶数据真正训得动、评得准、部署得出去。2. 拆开压缩包先做三件事目录结构、类别分布、标注质量2.1 目录结构决定你后面改多少行配置船舶检测数据集常见的组织方式有两种一种是 YOLO 原生格式images/和labels/平行放配一个data.yaml另一种是从 VOC 或 COCO 转过来的多一层Annotations或annotations.json。拿到yolo-boat-detect-dataset-1.zip后先别解压到桌面找个纯英文路径比如/data/boat_detect/因为后面labelimg 打标完yolo格式的标或者脚本转换时中文路径会让 OpenCV 读图直接返回 None这个坑我踩过不止一次。解压后执行下面这段脚本把结构打印出来同时统计图片和标签是否一一对应import os from pathlib import Path root Path(/data/boat_detect) for split in [train, val, test]: img_dir root / images / split lbl_dir root / labels / split if not img_dir.exists(): print(f[跳过] {split} 图片目录不存在) continue imgs {p.stem for p in img_dir.glob(*.jpg)} | {p.stem for p in img_dir.glob(*.png)} lbls {p.stem for p in lbl_dir.glob(*.txt)} if lbl_dir.exists() else set() print(f{split}: 图片 {len(imgs)} 张, 标签 {len(lbls)} 个, 缺失标签 {len(imgs - lbls)}, 多余标签 {len(lbls - imgs)})逻辑说明用stem做集合运算能快速定位「有图无标」和「有标无图」的情况。参数上如果你的图片是.jpeg或.bmp把 glob 模式补全即可。这一步跑完如果缺失标签超过 5%说明压缩包本身可能不完整或者标注时漏标了需要回头找数据提供方确认不要硬训。2.2 类别分布和长宽比统计船舶数据的两个命门船舶检测和通用目标检测最大的区别在于船体细长靠泊时密集远小目标多。所以第二步要统计每个类别的框数量以及宽高比的分布。下面这段脚本直接读 YOLO 格式的cls x y w himport numpy as np from pathlib import Path from collections import Counter lbl_dir Path(/data/boat_detect/labels/train) cls_counter Counter() ratios [] for txt in lbl_dir.glob(*.txt): for line in txt.read_text().strip().splitlines(): parts line.split() if len(parts) ! 5: continue c, x, y, w, h parts cls_counter[int(c)] 1 if float(h) 0: ratios.append(float(w) / float(h)) print(类别分布:, dict(cls_counter)) if ratios: arr np.array(ratios) print(f宽高比 中位数 {np.median(arr):.2f}, 90分位 {np.percentile(arr, 90):.2f}, 最大 {arr.max():.2f})逻辑说明cls_counter告诉你有没有类别极度不平衡比如「货船」几千个框、「渔船」只有几十个那训练时就要考虑加权或过采样。宽高比中位数如果超过 3说明大部分是细长船体默认的 anchor 或 YOLOv8 的 anchor-free 头虽然能适应但数据增强里的scale和mosaic参数要调否则拼接后船体被裁断模型学到的全是局部。参数上90 分位超过 5 的话建议在data.yaml里把imgsz提到 960 甚至 1280不然下采样后船体只剩几个像素。2.3 用可视化抽查标注框有没有「漂移」统计只能看数量框画得准不准必须肉眼抽检。我一般随机抽 16 张把 YOLO 格式还原成框画上去import cv2 import random from pathlib import Path img_dir Path(/data/boat_detect/images/train) lbl_dir Path(/data/boat_detect/labels/train) samples random.sample(list(img_dir.glob(*.jpg)), 16) canvas np.zeros((4*480, 4*640, 3), dtypenp.uint8) for i, img_path in enumerate(samples): img cv2.imread(str(img_path)) h, w img.shape[:2] lbl lbl_dir / (img_path.stem .txt) if lbl.exists(): for line in lbl.read_text().strip().splitlines(): c, x, y, bw, bh map(float, line.split()) x1 int((x - bw/2) * w); y1 int((y - bh/2) * h) x2 int((x bw/2) * w); y2 int((y bh/2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) img cv2.resize(img, (640, 480)) r, c divmod(i, 4) canvas[r*480:(r1)*480, c*640:(c1)*640] img cv2.imwrite(/data/boat_detect/check_grid.jpg, canvas)逻辑说明YOLO 的x y w h是归一化后的中心点和宽高还原时先乘回原图尺寸。重点看三种情况框是否把船头船尾切掉、密集停泊时框是否严重重叠、水面反光处有没有误标。如果发现某类框系统性偏移比如所有框都往左上偏那可能是标注工具导出时坐标系没对齐需要重新导出。这一步花十分钟能省后面几小时调参的玄学时间。3. 从 data.yaml 到第一次训练参数怎么设才不浪费显卡3.1 data.yaml 里三个必须改的字段船舶数据集的data.yaml通常长这样但直接拿来用往往报错path: /data/boat_detect train: images/train val: images/val test: images/test nc: 4 names: [cargo, fishing, passenger, other]必须确认的三点第一path用绝对路径别用../YOLO 在子进程里解析相对路径经常翻车第二nc和names长度必须一致如果 names 里有中文改成英文或拼音否则训练日志乱码第三如果test目录不存在直接删掉这一行不然验证阶段会报FileNotFoundError。改完用yolo checks跑一下环境确认 Ultralytics 版本和 CUDA 可用。3.2 第一次训练用哪个模型和哪些超参船舶检测我一般从yolov8s起步不直接上yolov8x因为船舶数据量通常不大大模型容易过拟合而且推理速度在边缘设备上扛不住。下面是一条能直接跑的命令yolo detect train \ data/data/boat_detect/data.yaml \ modelyolov8s.pt \ epochs150 \ imgsz960 \ batch16 \ patience30 \ lr00.01 \ lrf0.01 \ mosaic0.8 \ scale0.3 \ degrees5.0 \ fliplr0.5 \ cacheTrue \ device0 \ project/data/runs/boat \ nameexp1逻辑说明imgsz960是为了保住远小船的像素如果显存不够降到 640但 mAP 通常会掉 3 到 5 个点。mosaic0.8比默认的 1.0 略低因为船舶拼接后容易出现「半条船」的伪标签尤其密集停泊场景。scale0.3控制随机缩放幅度太大让细长船体变形。degrees5.0只做小角度旋转船舶在遥感图里方向固定大角度旋转反而引入噪声。cacheTrue把图片缓存到内存船舶数据集通常几千张16G 内存够用能明显加快 epoch 速度。3.3 训练过程中盯哪几个指标启动后不要只看 loss重点看metrics/mAP50(B)和metrics/mAP50-95(B)的差距。如果 mAP50 很高但 mAP50-95 很低说明框的位置不够准通常是标注框偏大或偏小回到 2.3 节重新抽检。如果训练集 mAP 涨但验证集 mAP 平甚至降那是过拟合把patience调小、dropout打开或者增加copy_paste增强。另外注意cls_loss如果一直不降检查类别是否标错比如把「货船」标成了「其他」。4. 船舶检测专属的避坑排查这五个问题几乎人人都会遇到4.1 现象mAP 卡在 0.4 上不去loss 震荡原因船舶数据里小目标占比高默认的imgsz640下采样 32 倍后一条 20 像素宽的船只剩不到 1 个像素特征全丢。解决把imgsz提到 960 或 1280同时在data.yaml同级目录加一个hyp.yaml把box损失权重从 7.5 提到 9.0让模型更关注框回归。4.2 现象验证时提示「No labels found」原因YOLO 找标签的路径是images替换成labels如果你的目录叫labels_train或者标签放在Annotations它就找不到。解决要么改目录名要么在data.yaml里显式写train: images/train和val: images/val并确保labels和images同级。别用软链接Windows 下软链接经常失效。4.3 现象训练到一半显存爆了原因mosaic增强会随机拼 4 张图如果原图分辨率不一致拼完的尺寸可能远超imgsz加上cacheTrue把原图也缓存了。解决训练前用脚本把所有图片统一 resize 到长边 1280 并保存或者把cache改成disk牺牲一点速度换稳定。4.4 现象推理时框重叠严重一条船出好几个框原因船舶密集停泊时NMS 的iou阈值默认 0.7 太高相邻船的框被保留。解决推理时加iou0.5或者训练时把overlap_mask关掉。如果还是不行说明标注时相邻船框本身就重叠需要在标注阶段用「不重叠框」原则重新标。4.5 现象换到自己的岸基摄像头图片上模型完全失效原因训练数据是遥感视角岸基是斜视船体外观和背景分布差异大。解决这不是调参能解决的要么补充岸基视角的标注数据要么用yolo export导出 ONNX 后做域适应。我一般会先抽 50 张目标场景图用训练好的模型跑一遍看置信度分布如果普遍低于 0.2就别硬调了直接补数据。5. 把船舶检测推到可用置信度门限、切片推理与导出部署5.1 置信度门限不是拍脑袋定的很多人推理时直接用conf0.25这是 COCO 的默认值放到船舶场景往往漏检小船。正确做法是拿验证集跑一遍画出不同conf下的 precision 和 recall 曲线找 F1 最高点。下面这段脚本直接调 Ultralytics 的 val 接口from ultralytics import YOLO import numpy as np model YOLO(/data/runs/boat/exp1/weights/best.pt) results model.val(data/data/boat_detect/data.yaml, conf0.001, iou0.6, plotsTrue) # 从 results 里取曲线数据实际使用时可直接看生成的 PR_curve.png print(mAP50:, results.box.map50) print(mAP50-95:, results.box.map)逻辑说明conf0.001是为了让 val 输出完整的 PR 曲线然后看PR_curve.png里 F1 最大值对应的 conf。船舶检测我一般会把最终部署的conf设在 F1 最大点再减 0.05留一点召回余量因为漏检一条船的代价比误检高。5.2 大图切片推理让远小船舶不再漏遥感船舶图往往 4000×4000 以上直接 resize 到 960 会丢细节。常见做法是切片推理把大图切成 1024×1024 带重叠的小块逐块推理再合并。Ultralytics 本身不带这个功能但可以用sahi库或者自己写一个滑动窗口import cv2 import numpy as np from ultralytics import YOLO model YOLO(/data/runs/boat/exp1/weights/best.pt) img cv2.imread(/data/test_big.jpg) H, W img.shape[:2] tile, overlap 1024, 128 step tile - overlap all_boxes [] for y in range(0, H, step): for x in range(0, W, step): patch img[y:ytile, x:xtile] if patch.shape[0] tile or patch.shape[1] tile: patch cv2.copyMakeBorder(patch, 0, tile-patch.shape[0], 0, tile-patch.shape[1], cv2.BORDER_CONSTANT) res model.predict(patch, conf0.3, verboseFalse)[0] for box in res.boxes: xyxy box.xyxy[0].cpu().numpy() xyxy[[0, 2]] x xyxy[[1, 3]] y all_boxes.append(xyxy) # 合并时用 NMS 去重 def nms(boxes, iou_thr0.5): boxes np.array(boxes) if len(boxes) 0: return [] x1, y1, x2, y2 boxes[:,0], boxes[:,1], boxes[:,2], boxes[:,3] areas (x2-x1) * (y2-y1) order areas.argsort()[::-1] keep [] while order.size 0: i order[0] keep.append(i) xx1 np.maximum(x1[i], x1[order[1:]]) yy1 np.maximum(y1[i], y1[order[1:]]) xx2 np.minimum(x2[i], x2[order[1:]]) yy2 np.minimum(y2[i], y2[order[1:]]) w np.maximum(0, xx2-xx1); h np.maximum(0, yy2-yy1) inter w * h iou inter / (areas[i] areas[order[1:]] - inter) order order[1:][iou iou_thr] return boxes[keep] final nms(all_boxes, 0.5) print(f切片推理得到 {len(final)} 个框)逻辑说明tile1024和overlap128是经验值重叠区保证边缘的船不被切一半。合并时用 NMS 去掉重叠框iou_thr0.5比默认严一点因为切片边缘容易产生重复检测。参数上如果显存够tile可以提到 1536但推理时间线性增长。5.3 导出 ONNX 和 TensorRT 的注意点训练完要部署yolo export一条命令能导 ONNX但船舶模型导出时有两个细节第一imgsz必须和训练时一致否则 anchor 对不上第二如果用了mosaic增强导出时不用管但推理前处理要自己写 letterbox。TensorRT 导出在 Jetson 上很常见但注意halfTrue只在支持 FP16 的设备上开否则精度掉得厉害。我一般先在 PC 上用 ONNX Runtime 验证一遍确认输出和 PyTorch 一致再上边缘设备。5.4 一个我坚持了很久的习惯每次训完船舶模型不管 mAP 多高我都会拿 20 张「最差场景」——夜雾、强反光、密集停泊——跑一遍把漏检和误检截图存到一个叫bad_cases的文件夹。下次补数据、调增强直接对着这个文件夹改。这个习惯让我从「调参玄学」里解脱出来因为问题永远在数据里不在lr0的小数点后第三位。希望帮到你。本文还有配套的精品资源点击获取
返回列表