简介:这份红外海洋船只检测数据集面向从事目标检测算法研究、遥感图像分析及海上智能监控开发的工程师与研究生,提供可直接用于模型训练与验证的标注数据。数据集同时包含Pascal VOC与YOLO两种标注格式,图片与标注一一对应,省去格式转换环节,便于快速接入主流检测框架。压缩包共约2000个文件,以1999个xml标注文件和1个说明用txt为主,整体约883.61MB,xml文件记录每张红外图像中船只的类别与边界框坐标,可配合同名jpg图片使用。标注类别共7类,涵盖散货船、独木舟、集装箱船、渔船、班轮、帆船与军舰,覆盖多种海上目标形态。目前已有1432人学习下载,适合红外弱小目标检测、多类别船只识别等课题的入门与进阶实验,也可作为论文对比实验的数据来源。
1. 红外海洋船只检测数据集:8402 张 VOC+YOLO 双格式,7 类目标怎么落地
做海上目标检测的同行大概率都遇到过这个场景:可见光图像在夜间、雾天、海面反光条件下几乎失效,船体轮廓和背景融为一体,模型召回率断崖式下跌。红外成像恰好补上这个短板——它靠热辐射成像,不依赖可见光,昼夜都能稳定输出船体目标。但公开的红外海上船只数据一直很稀缺,要么类别少,要么标注质量参差,要么只给单一格式,拿到手还得自己转。
这份红外海洋船只检测数据集给的是 8402 张红外图像,VOC 和 YOLO 两种标注格式同时提供,覆盖 7 个类别。它解决的核心问题就一个:让你跳过找数据、清洗、格式转换这些脏活,直接把精力放在模型训练和调参上。适合做海上监控、渔船识别、航道管理、边海防侦察方向的目标检测工程师,也适合刚入门想拿真实红外数据练手的人。下面我从数据本身、格式转换、训练配置到踩坑,把这份资源拆开讲清楚。
2. 数据集结构与 VOC/YOLO 双格式解析:先搞懂你拿到的是什么
2.1 8402 张红外图像的目录组织与类别分布
拿到压缩包解压后,常见的目录结构是这样的(不同打包方式可能略有差异,但逻辑一致):
dataset/ ├── JPEGImages/ # 8402 张红外图像,jpg 格式 ├── Annotations/ # VOC 格式 XML 标注文件 ├── ImageSets/ │ └── Main/ # train.txt / val.txt / test.txt 划分文件 ├── labels/ # YOLO 格式 txt 标注文件 └── classes.txt # 7 个类别名称红外图像和可见光图像有个本质区别:红外图是单通道灰度映射到三通道,像素值反映的是温度差异而非颜色。这意味着你不能直接套用 ImageNet 预训练模型的颜色先验,常见做法是在训练前把图像做归一化,或者用灰度化后的统计量重新算均值和方差。
7 个类别具体是什么,需要打开classes.txt确认。从海上船只检测的常见分类来看,一般会覆盖货船、渔船、快艇、客轮、军舰、帆船、浮标等。类别数直接决定检测头输出维度,这个后面配置时会用到。
8402 张的规模在红外数据集里算中等偏上。按 7:2:1 划分,训练集约 5880 张,验证集约 1680 张,测试集约 840 张。如果类别分布不均衡,小类别样本可能只有几百张,这时候数据增强策略就要针对性调整。
2.2 VOC XML 与 YOLO TXT 的字段对应关系
VOC 格式的 XML 文件长这样:
<annotation> <folder>JPEGImages</folder> <filename>IR_ship_0001.jpg</filename> <size> <width>640</width> <height>512</height> <depth>3</depth> </size> <object> <name>cargo_ship</name> <bndbox> <xmin>120</xmin> <ymin>85</ymin> <xmax>340</xmax> <ymax>260</ymax> </bndbox> </object> </annotation>YOLO 格式的 txt 文件则是归一化后的中心点坐标加宽高:
0 0.359375 0.337891 0.343750 0.341797两者的转换关系是:
| VOC 字段 | YOLO 字段 | 转换公式 |
|---|---|---|
| xmin, ymin, xmax, ymax | x_center | (xmin+xmax)/2/width |
| 同上 | y_center | (ymin+ymax)/2/height |
| 同上 | w | (xmax-xmin)/width |
| 同上 | h | (ymax-ymin)/height |
| name | class_id | 按 classes.txt 顺序映射 |
这里有个容易翻车的点:YOLO 的坐标必须归一化到 0~1 之间,且是相对于图像实际宽高。如果 XML 里的 size 字段和实际图像尺寸不一致(有些数据集制作时写错了),转换出来的框会整体偏移。我一般会先跑一遍校验脚本,确认每张图的 XML size 和 PIL 读出来的尺寸一致。
2.3 用脚本校验标注完整性并生成划分文件
在训练之前,先跑一个校验脚本,把坏图、空标注、越界框筛出来:
import os import xml.etree.ElementTree as ET from PIL import Image def validate_voc(images_dir, annotations_dir): bad_images = [] empty_annotations = [] size_mismatch = [] for xml_file in os.listdir(annotations_dir): if not xml_file.endswith('.xml'): continue xml_path = os.path.join(annotations_dir, xml_file) tree = ET.parse(xml_path) root = tree.getroot() # 检查是否有目标 objects = root.findall('object') if len(objects) == 0: empty_annotations.append(xml_file) continue # 检查图像尺寸是否匹配 filename = root.find('filename').text img_path = os.path.join(images_dir, filename) if not os.path.exists(img_path): bad_images.append(filename) continue with Image.open(img_path) as img: w, h = img.size size_node = root.find('size') xml_w = int(size_node.find('width').text) xml_h = int(size_node.find('height').text) if w != xml_w or h != xml_h: size_mismatch.append((filename, (w, h), (xml_w, xml_h))) print(f"空标注文件: {len(empty_annotations)}") print(f"缺失图像: {len(bad_images)}") print(f"尺寸不匹配: {len(size_mismatch)}") return empty_annotations, bad_images, size_mismatch validate_voc('dataset/JPEGImages', 'dataset/Annotations')这段脚本做三件事:统计没有目标框的 XML(这类文件在训练时会导致 loss 异常)、检查 XML 引用的图像是否存在、比对 XML 记录的尺寸和实际图像尺寸。跑完之后,空标注文件建议直接剔除,尺寸不匹配的要么修正 XML 要么修正图像。
校验通过后,生成训练集和验证集的划分文件:
import random all_files = [f.replace('.xml', '') for f in os.listdir('dataset/Annotations') if f.endswith('.xml')] random.seed(42) random.shuffle(all_files) split_idx = int(len(all_files) * 0.8) train_files = all_files[:split_idx] val_files = all_files[split_idx:] with open('dataset/ImageSets/Main/train.txt', 'w') as f: f.write('\n'.join(train_files)) with open('dataset/ImageSets/Main/val.txt', 'w') as f: f.write('\n'.join(val_files))random.seed(42)保证每次划分结果一致,方便复现实验。8:2 的划分比例在 8402 张的规模下,验证集约 1680 张,足够评估模型泛化能力。
3. 从 VOC 到 YOLO:转换脚本、类别映射与训练配置
3.1 批量转换脚本与类别 ID 映射
虽然数据集号称双格式,但实际拿到手经常发现 YOLO 格式的 labels 目录是空的,或者类别 ID 对不上。自己转一遍最稳妥:
import os import xml.etree.ElementTree as ET from PIL import Image CLASSES = ['cargo_ship', 'fishing_boat', 'speedboat', 'passenger_ship', 'warship', 'sailboat', 'buoy'] class_to_id = {name: idx for idx, name in enumerate(CLASSES)} def voc_to_yolo(images_dir, annotations_dir, output_dir): os.makedirs(output_dir, exist_ok=True) for xml_file in os.listdir(annotations_dir): if not xml_file.endswith('.xml'): continue tree = ET.parse(os.path.join(annotations_dir, xml_file)) root = tree.getroot() filename = root.find('filename').text img_path = os.path.join(images_dir, filename) with Image.open(img_path) as img: img_w, img_h = img.size lines = [] for obj in root.findall('object'): cls_name = obj.find('name').text if cls_name not in class_to_id: print(f"未知类别 {cls_name},跳过") continue cls_id = class_to_id[cls_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, min(xmin, img_w)) ymin = max(0, min(ymin, img_h)) xmax = max(0, min(xmax, img_w)) ymax = max(0, min(ymax, img_h)) 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 lines.append(f"{cls_id} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") txt_name = xml_file.replace('.xml', '.txt') with open(os.path.join(output_dir, txt_name), 'w') as f: f.write('\n'.join(lines)) voc_to_yolo('dataset/JPEGImages', 'dataset/Annotations', 'dataset/labels')几个关键处理:CLASSES列表的顺序必须和classes.txt一致,否则训练出来的类别索引全错;坐标裁剪防止标注框超出图像边界导致归一化后出现负值或大于 1 的值;保留 6 位小数足够 YOLO 训练精度,再多也是浪费。
转换完成后,写一个data.yaml给 YOLO 用:
path: ./dataset train: ImageSets/Main/train.txt val: ImageSets/Main/val.txt nc: 7 names: ['cargo_ship', 'fishing_boat', 'speedboat', 'passenger_ship', 'warship', 'sailboat', 'buoy']nc是类别数,必须和names长度一致。path用相对路径方便迁移,如果训练时报找不到文件,先检查这个路径的基准目录是哪里。
3.2 红外图像训练的参数调整:为什么不能照搬可见光配置
红外图像和可见光图像在训练配置上有几个必须调整的点。第一,预训练权重。COCO 预训练模型是在可见光图像上训的,颜色通道的统计分布和红外图差异很大。常见做法是加载预训练权重后冻结 backbone 前几层,或者直接用较小的学习率微调。我一般会把初始学习率从 0.01 降到 0.001,让模型慢慢适应红外域。
第二,数据增强。可见光常用的 HSV 色彩抖动对红外图没意义,因为红外图没有真正的色彩信息。应该换成灰度变换、对比度调整、高斯噪声。Mosaic 和 MixUp 可以保留,但要注意红外目标的热特征在拼接后可能被稀释。
第三,输入分辨率。红外图像的目标通常比可见光小,因为热辐射在远距离衰减快。如果原始图像是 640×512,建议训练时上采样到 640×640 或更高,避免小目标在降采样过程中丢失。
from ultralytics import YOLO model = YOLO('yolov8n.pt') results = model.train( data='data.yaml', epochs=100, imgsz=640, batch=16, lr0=0.001, lrf=0.01, warmup_epochs=5, hsv_h=0.0, # 关闭色相增强 hsv_s=0.0, # 关闭饱和度增强 hsv_v=0.3, # 保留亮度增强 degrees=10.0, # 小角度旋转 translate=0.1, scale=0.5, mosaic=1.0, mixup=0.1, patience=20, device=0 )hsv_h和hsv_s设为 0 是因为红外图没有色相和饱和度概念,开了反而引入噪声。hsv_v保留 0.3 模拟不同温度增益下的亮度变化。patience=20表示 20 个 epoch 验证指标不提升就早停,避免过拟合。
3.3 用验证集评估模型并定位漏检类别
训练完成后,用验证集跑一遍评估:
metrics = model.val(data='data.yaml', split='val') print(f"mAP50: {metrics.box.map50:.4f}") print(f"mAP50-95: {metrics.box.map:.4f}") # 逐类别看 AP for i, name in enumerate(metrics.names.values()): print(f"{name}: AP50={metrics.box.ap50[i]:.4f}")逐类别看 AP 是关键步骤。海上船只检测里,小船和远距离目标的 AP 通常明显低于大船。如果某个类别 AP 低于 0.3,要么是该类别样本太少,要么是标注质量有问题。我一般会把低 AP 类别的验证集图像单独抽出来可视化,看是漏检还是误检。
漏检多的话,考虑提高输入分辨率或增加小目标检测层。误检多的话,检查是否有背景区域被误标,或者类别之间的特征太接近(比如渔船和货船在红外图里可能都是热源团块)。
4. 避坑与排查:红外船只检测数据集落地时的五个血泪教训
4.1 坑一:YOLO labels 目录为空或类别 ID 错位
现象:解压后发现labels/目录是空的,或者训练时 loss 一直是 nan,模型完全不收敛。
原因:数据集打包时可能只放了 VOC 格式,YOLO 格式是后来补的但没补全;或者classes.txt的顺序和实际标注里的类别名对不上,导致类别 ID 映射错位。
解决:不要信任压缩包里的 YOLO labels,自己用 3.1 的脚本转一遍。转换前先统计 XML 里出现的所有类别名,和classes.txt比对,确认没有遗漏或拼写差异。如果类别名有大小写不一致(比如Cargo_Ship和cargo_ship),统一转小写再映射。
4.2 坑二:红外图像被当可见光处理导致预训练权重失效
现象:加载 COCO 预训练权重后,训练 loss 下降很慢,前 20 个 epoch 几乎不收敛。
原因:COCO 预训练模型的浅层卷积核是针对 RGB 颜色边缘训练的,红外图的灰度边缘分布完全不同,浅层特征提取器相当于随机初始化。
解决:两个方案。一是冻结 backbone 的前 3 层,只训练后面的层,等 loss 稳定后再解冻微调。二是直接用灰度化的 ImageNet 预训练权重(如果有的话),或者从头训但加大 warmup epoch 到 10 以上。我一般用第一种,省时间且效果稳定。
4.3 坑三:小目标漏检严重,mAP 虚高但实际不可用
现象:整体 mAP50 看着有 0.75,但可视化后发现远距离的小船几乎全漏,检测出来的都是近处大船。
原因:红外图像里远距离船只的热辐射信号弱,像素占比可能只有十几个像素,经过 backbone 的 32 倍降采样后在特征图上几乎消失。
解决:把输入分辨率从 640 提到 1024 或 1280,让更多小目标在特征图上保留下来。或者在模型里加一个 P2 小目标检测层(YOLOv8 可以通过修改配置文件实现)。另外,验证时不要只看整体 mAP,要单独统计小目标(面积小于 32×32)的 AP。
4.4 坑四:训练集和验证集划分时同一场景图像泄漏
现象:验证集 mAP 很高,但拿实际场景图像测试时效果差很多。
原因:8402 张图可能来自连续视频帧,相邻帧的图像几乎一样。如果随机划分,训练集和验证集里会有大量相似帧,导致验证指标虚高。
解决:按场景或按视频片段划分,而不是按单帧随机划分。如果数据里有场景 ID 或视频来源信息,按来源分组后再划分。没有的话,用图像哈希或感知哈希做去重,把相似帧归到同一组再分。
4.5 坑五:类别不均衡导致小类别被模型忽略
现象:7 个类别里,货船和渔船 AP 正常,但帆船和浮标的 AP 接近 0。
原因:小类别样本可能只有几百张,在 loss 里被大类别淹没,模型倾向于全部预测为大类别来降低整体 loss。
解决:在data.yaml里给每个类别加权重,或者在训练时用类别平衡采样。YOLOv8 支持通过cls参数调整分类 loss 权重,但更直接的办法是对小类别做过采样,复制到和其他类别差不多的数量。另外,评估时看混淆矩阵,确认小类别是被漏检还是被误分为其他类别。
5. 进阶技巧:用 TensorRT 加速红外船只检测推理
训练完模型只是第一步,实际部署时推理速度往往比精度更关键。红外监控场景通常要求实时处理多路视频流,用 PyTorch 原生推理很难满足。把 YOLO 导出为 TensorRT 引擎,在 T4 上跑 640 分辨率,单路延迟可以压到 10ms 以内。
导出 ONNX 再转 TensorRT 的流程:
# 导出 ONNX yolo export model=best.pt format=onnx opset=12 simplify=True # 用 trtexec 转 TensorRT 引擎(FP16 精度) trtexec --onnx=best.onnx \ --saveEngine=best_fp16.engine \ --fp16 \ --workspace=4096 \ --minShapes=images:1x3x640x640 \ --optShapes=images:8x3x640x640 \ --maxShapes=images:16x3x640x640--fp16开启半精度推理,T4 对 FP16 有专门优化,速度比 FP32 快近一倍,精度损失通常在 1% 以内。--workspace=4096给 4GB 显存用于算子优化,如果显存紧张可以降到 2048。minShapes、optShapes、maxShapes定义动态 batch 范围,optShapes是实际推理时最常用的 batch 大小,TensorRT 会针对这个尺寸做最优 kernel 选择。
导出后验证推理一致性:
import tensorrt as trt import pycuda.driver as cuda import numpy as np # 加载引擎 logger = trt.Logger(trt.Logger.WARNING) with open('best_fp16.engine', 'rb') as f, trt.Runtime(logger) as runtime: engine = runtime.deserialize_cuda_engine(f.read()) # 分配显存、创建执行上下文 context = engine.create_execution_context() context.set_input_shape('images', (1, 3, 640, 640)) # 准备输入输出 buffer input_host = np.random.randn(1, 3, 640, 640).astype(np.float32) input_device = cuda.mem_alloc(input_host.nbytes) output_device = cuda.mem_alloc(1 * 7 * 8400 * 4) # 7 类,8400 个候选框 # 拷贝输入到显存 cuda.memcpy_htod(input_device, input_host) # 执行推理 context.execute_v2([int(input_device), int(output_device)]) # 拷贝输出回主机 output_host = np.empty((1, 7, 8400), dtype=np.float32) cuda.memcpy_dtoh(output_host, output_device)这段代码的核心是context.execute_v2,它触发 TensorRT 引擎执行推理。输入输出 buffer 的尺寸必须和引擎定义一致,8400是 YOLOv8 在 640 分辨率下的候选框数量(80×80 + 40×40 + 20×20 = 8400)。如果换模型版本或输入尺寸,这个数字要跟着变。
实际部署时,我习惯先用 PyTorch 和 TensorRT 各跑 100 张图,逐张比对输出框的坐标和置信度。如果偏差超过 1%,说明导出过程有问题,常见原因是 ONNX opset 版本和 TensorRT 版本不兼容,或者动态 batch 设置不对。
从那以后我每次导出 TensorRT 引擎,都强制走一遍 PyTorch 和 TensorRT 的输出比对,确认坐标偏差在可接受范围内才上线。红外船只检测这个场景对漏检容忍度低,推理加速不能以牺牲精度为代价。希望帮到你。
本文还有配套的精品资源,点击获取