首页 / 资讯中心 / 文章详情

卫星遥感舰船检测数据集实战:VOC转YOLO全流程避坑指南

卫星遥感舰船检测数据集实战:VOC转YOLO全流程避坑指南 ★ FEATURED ARTICLE
简介面向卫星遥感舰船检测任务的数据集资源适用于计算机视觉目标检测方向的学生、研究者和算法工程师可直接服务于船舶识别模型的训练与验证。数据覆盖航空母舰、辅助舰、货船、集装箱船、巡洋舰、驱逐舰、护卫舰、气垫船、油轮、潜艇、游艇等17类舰船目标同时提供Pascal VOC与YOLO两种常用标注格式省去格式转换环节便于直接接入YOLO、Faster R-CNN等主流检测框架。压缩包约109.32MB共2000个文件其中以XML标注文件为主体1999个另附TXT使用说明目录结构清晰解压后即可按需划分训练集和测试集。目前已有834人学习下载对需要高质量遥感舰船标注数据做实验、写论文或完成毕设的读者而言是一份可快速落地的数据基础能显著减少数据采集与清洗时间。1. 卫星遥感舰船检测数据集2238张图、VOCYOLO双格式能直接开训吗拿到「卫星遥感舰船检测数据集VOCYOLO格式2238张17类别.7z」这个压缩包大部分人的第一反应就是解压、翻目录、直接开跑YOLO。2238张遥感影像、VOC和YOLO两套标注、17个舰船类别这套配置对遥感目标检测入门来说确实算友好不用从零标数据解压就能训适合快速验证算法、跑迁移学习实验也适合课程设计或者论文里补一组对比实验。但它本质上是小样本起步方案不是大数据集。2238张图对舰船检测来说只能覆盖常见海域、典型泊位和常规航态真到了复杂港口、云雾遮挡、近岸密集停靠的场景掉点会非常快。想直接刷出SOTA不现实把它当预训练基础再结合自采数据做微调才是这个数据集正确的打开方式。下面按我从拿到这类数据集到训出能用模型的全流程走一遍.7z解包、数据体检、VOC转YOLO、训练配置与损失函数观察、验证和误检调优中间穿插实际踩过的坑。2. 解包与数据体检.7z不是坑格式混乱才是2.1 Linux下解压7z先装p7zip再看目录Windows 用户拿到 .7z 基本没难度右键解压就行。真正卡人的是 Linux 服务器上传完以后发现7z命令不存在。Debian/Ubuntu 系先装 p7zipsudo apt-get update sudo apt-get install -y p7zip-full # 解压到指定目录注意 -o 后面不要加空格 7z x 卫星遥感舰船检测数据集VOCYOLO格式2238张17类别.7z -o./ship_datax是解压命令它和e的区别很关键e会把压缩包内所有文件拍平到一个目录x保留原始目录层级。遥感数据集解压后一般有JPEGImages、Annotations这种多层结构用e会把几百个 xml 和 jpg 全混在一起后面路径匹配全是麻烦。-o指定输出目录写成-o./ship_data最稳妥中间加空格可能导致目录名被截断。解压完先别急着写训练脚本花两分钟看目录结构cd ship_data find . -maxdepth 2 -type d | sort这一步常见两种情况一种是解压出来套了两层同名目录比如ship_data/ship_data/JPEGImages训练脚本里路径写错一层日志直接报No such file or directory另一种是标注目录叫VOC2007/Annotations、图片目录叫VOC2007/JPEGImages和标题描述的组织方式有出入。血泪经验先确认结构再动代码能省下大量肉眼查路径的时间。还有一个和 .7z 本身相关的坑如果解压时提示“密码错误”但发布说明里明明给了密码多半是复制时多带了一个空格或换行符少数字典型密码还会因为中文文件名的编码问题在部分 Linux locale 下解出乱码目录名导致后续脚本找不到文件。遇到这种情况先检查密码周围有没有多余字符再确认系统 locale 是 UTF-8不要一上来就怀疑压缩包损坏。2.2 目录里通常藏着两套标注VOC的xml和YOLO的txt这类数据集最常见的组织方式是两套目录并存一套 VOC一套 YOLO共用同一批图片目录格式内容VOC/AnnotationsXML每张图一个 xml记录 object 的 name、bndbox 的 xmin/ymin/xmax/ymaxVOC/JPEGImagesJPG/PNG原始遥感影像尺寸通常偏大YOLO/labelsTXT每张图一个 txt每行为 class cx cy w h全部归一化到 [0,1]YOLO/imagesJPG/PNG与 VOC 同图可能已统一尺寸VOC 格式的坐标是像素级绝对坐标左上角是原点xmin/ymin/xmax/ymax直接对应图片上的像素位置。YOLO 格式是归一化相对坐标cx和cy是框中心点相对图片宽高的比例w和h是框宽高占图片宽高的比例。同一张图两套标注描述的是同一个物体只是坐标系不同转换关系在下一章展开。另外发布者通常会在某处放一个classes.txt写着 17 个类别的名字和顺序。这个文件极其重要——YOLO 训练时类别索引是从 0 开始的整数txt 每一行的第一个数字对应的就是classes.txt里的行序。如果解压后没找到这个文件就去 VOC 的 xml 里把所有name字段去重提取一份这也是为什么我坚持先做数据体检。2.3 数据体检先查三张图别急着开训很多人解压完直接把路径扔给训练脚本跑一半报错才发现某个 xml 的bndbox是空的或者某张图的 txt 和图片对不上。我拿到数据集必做三件事统计类别分布、检查 xml 合法性、抽查图片与标注是否对应。import os import xml.etree.ElementTree as ET from collections import Counter xml_dir ship_data/VOC/Annotations counter Counter() bad_xml [] for f in os.listdir(xml_dir): if not f.endswith(.xml): continue tree ET.parse(os.path.join(xml_dir, f)) root tree.getroot() for obj in root.findall(object): name obj.findtext(name).strip() counter[name] 1 box obj.find(bndbox) if box is None: bad_xml.append((f, missing bndbox)) continue vals {} for key in [xmin, ymin, xmax, ymax]: vals[key] float(box.findtext(key)) if vals[xmax] vals[xmin] or vals[ymax] vals[ymin]: bad_xml.append((f, finverted box: {vals})) print(类别分布:, dict(counter)) print(非法xml:, bad_xml[:20], 共, len(bad_xml), 个)脚本逻辑很简单遍历 xml 目录解析每个 xml统计所有object的name同时检查bndbox是否存在、坐标是否有效。核心排查点有两个——xmax xmin说明标注框反了或退化成了线这种 xml 要么修框要么连图带标注一起删掉不处理会在训练早期让损失函数震荡得很难看。类别分布这里注意一点遥感舰船数据集普遍类别极不平衡常见的分布是大头全在货轮、渔船、油轮上航母、潜艇可能只有几十张甚至几张。看分布能帮你决定要不要做类别重采样也能帮你确认 17 类是不是真的都有数据。我遇到过自称 17 类的数据集实际 xml 里只有 12 类有标注另外 5 类根本没图。不查的话模型训练完你以为它学会了 17 类其实它压根没见过那 5 类。3. VOC转YOLO转换脚本、落地细节与避坑3.1 为什么要转YOLO训练只认txt不认xml虽然标题说数据集已经带了 YOLO 格式但实际用起来往往要自己再转一次。常见原因有三个一是发布者生成的 YOLO txt 是按他自己的类别顺序排的未必和你的训练配置一致二是解压后 YOLO 目录缺文件某个子集只有图片没有 txt三是你想重新定义类别集合比如只训航母、驱逐舰、护卫舰三类那就要按自己的类别映射重新生成。另一个现实需求是补标。你后面自己用 LabelImg 或 Labelme 补标数据时原始产物大概率是 VOC 或 COCO 格式照样要走一遍转换流程。转换规则一句话讲完cx (xmin xmax) / 2 / widthcy (ymin ymax) / 2 / heightw (xmax - xmin) / widthh (ymax - ymin) / height所有值归一化到 [0,1]。有个常见误用是拿xmin直接除以图片宽度当成 x 用这是把cx和xmin搞混了。YOLO 记录的是中心点坐标加宽高不是左上角加宽高。一个框如果标得越界xmax大于图片宽度不处理的话归一化后 w 会大于 1训练时 anchor 匹配逻辑直接崩。3.2 转换脚本一份能直接跑的pythonimport os import cv2 import xml.etree.ElementTree as ET # 类别顺序必须和后续训练 yaml 完全一致 # 实际以你解压出的 classes.txt 为准下面只是结构演示 CLASSES [aircraft_carrier, destroyer, frigate, cruiser, amphibious, supply, submarine, container, cargo, oil_tanker, fishing, passenger, tug, sailboat, speedboat, patrol, other] def convert_one(xml_path, img_w, img_h, out_path): tree ET.parse(xml_path) root tree.getroot() lines [] for obj in root.findall(object): name obj.findtext(name).strip() if name not in CLASSES: print(fskip unknown class: {name} in {xml_path}) continue cls_id CLASSES.index(name) box obj.find(bndbox) xmin max(float(box.findtext(xmin)), 0) ymin max(float(box.findtext(ymin)), 0) xmax min(float(box.findtext(xmax)), img_w) ymax min(float(box.findtext(ymax)), img_h) if xmax xmin or ymax ymin: print(fskip bad box in {xml_path}: {xmin},{ymin},{xmax},{ymax}) continue cx (xmin xmax) / 2 / img_w cy (ymin ymax) / 2 / img_h w (xmax - xmin) / img_w h (ymax - ymin) / img_h lines.append(f{cls_id} {cx:.6f} {cy:.6f} {w:.6f} {h:.6f}) with open(out_path, w) as f: f.write(\n.join(lines)) xml_dir ship_data/VOC/Annotations img_dir ship_data/VOC/JPEGImages out_dir ship_data/YOLO/labels os.makedirs(out_dir, exist_okTrue) for xml_name in os.listdir(xml_dir): if not xml_name.endswith(.xml): continue base xml_name[:-4] img_path os.path.join(img_dir, base .jpg) if not os.path.exists(img_path): img_path os.path.join(img_dir, base .png) if not os.path.exists(img_path): print(fimage not found for {base}, skip) continue img cv2.imread(img_path) h, w img.shape[:2] convert_one(os.path.join(xml_dir, xml_name), w, h, os.path.join(out_dir, base .txt))这段脚本里有三个容易被忽略但关键的细节。第一cls_id CLASSES.index(name)决定了类别映射CLASSES 列表顺序必须和你后面写进 data.yaml 的 names 完全一致差一个索引训练不会报错但指标全部失真。第二坐标做了 clipxmax不会超过图片宽度、xmin不会小于 0这一步能拦掉大量越界框。第三对图片不存在的 xml 直接跳过并打印提示避免生成一个空 txt 让训练中断。注意一个依赖问题脚本里用cv2.imread读图片来取宽高跑之前需要pip install opencv-python。VOC 的 xml 里没有图片尺寸信息取宽高这一步绝对不能省。也可以用 PIL 的Image.open().size替代效果一样。3.3 四个边界坑现象、原因与解决办法这四个坑是我在转换流程里真实踩过的每一条都值得提前排掉。坑一坐标越界导致训练出现 NaN。现象是训练到某个 epoch 突然 loss 变成 nan或者前期 loss 根本不降。原因是 xml 里xmax比图片实际宽度还大常见于标注工具在 exif 旋转后没有回写坐标或标注时缩放不当。解决转换时统一做 clip也就是上面脚本里的min(xmax, img_w)和max(xmin, 0)clip 完再校验框是否退化退化就跳过。坑二空标注文件导致训练中断。现象是训练日志中突然出现assertion failed: labels should not be empty。原因是某张图的所有框都被过滤了可能类别不在 CLASSES 里、框退化或 xml 本身就是空的。解决转换后做一个交叉校验筛选出“txt 为空或对应图片不存在”的样本统一移到exclude/目录不要直接删后悔药先留着。坑三类别错位模型把渔船当航母训了。现象是训练一切正常、损失下降、mAP 看着还行但一可视化发现框完全对不上。原因是发布者的 YOLO txt 是按某种字母序排列类别的和你自定义的 CLASSES 顺序不一致索引错位后模型在拿错误标签学习。解决转换前把 CLASSES 列表和压缩包内 classes.txt 人工比对一遍以 xml 里的name实际值为准别偷懒。坑四图片和标注文件名对不上。现象是训练正常、精度正常但可视化时发现框的位置和船头方向完全对不上。原因是有人整理数据时把图片重命名成了 000001 到 002238但 xml 文件名还是原始编号只是恰好数量和顺序一致。解决训练前抽样 10 张图把 xml 坐标反画到图片上肉眼核对五分钟能拦下几乎所有“看起来正常但全错”的情况。4. 训练配置从数据集划分到损失函数判“死缓”4.1 划分训练集/验证集先shuffle再放种子按图片不按场景YOLO 训练需要 train 和 val 两组图片可以写路径清单也可以按目录组织。遥感数据集我习惯按单张图片随机划分不做按场景分片。原因是同一个场景内舰船类型高度相关如果按场景分会把同类型船全分到训练集或验证集验证指标严重失真。import os import random random.seed(42) img_dir ship_data/VOC/JPEGImages imgs [f for f in os.listdir(img_dir) if f.endswith((.jpg, .png))] random.shuffle(imgs) val_ratio 0.1 val_count int(len(imgs) * val_ratio) train_imgs imgs[val_count:] val_imgs imgs[:val_count] def write_list(path, img_list, prefix): with open(path, w) as f: for name in img_list: f.write(os.path.join(prefix, name) \n) write_list(train.txt, train_imgs, /data/ship_data/images) write_list(val.txt, val_imgs, /data/ship_data/images) print(ftrain: {len(train_imgs)}, val: {len(val_imgs)})random.seed(42)保证每次划分结果一致这是可复现实验的前提。10% 做验证够用了显存大或想提高验证可信度可以放到 15%~20%但前提是每个类别在验证集里都有正样本。我每次划分完会把第 2 章的类别统计脚本再跑一遍确认没有哪个冷门类恰好被全分进验证集否则训练时等于把那个类删了。4.2 数据yaml与训练命令YOLOv8/v5通用写法data.yaml 是训练入口YOLOv5 和 YOLOv8 的核心结构一致# ship.yaml path: /data/ship_data train: train.txt val: val.txt names: 0: aircraft_carrier 1: destroyer 2: frigate 3: cruiser 4: amphibious 5: supply 6: submarine 7: container 8: cargo 9: oil_tanker 10: fishing 11: passenger 12: tug 13: sailboat 14: speedboat 15: patrol 16: other训练命令以 ultralytics 包为例yolo detect train dataship.yaml modelyolov8m.pt \ epochs120 imgsz640 batch16 patience20 device0参数说明epochs120对 2238 张小数据集够用patience20表示验证集指标连续 20 个 epoch 不涨就自动停不用死等 120 轮跑完。imgsz640是 YOLO 默认值但遥感舰船这种小目标场景我一般至少开到 1024 或 1280代价是显存占用翻倍。modelyolov8m.pt表示加载 COCO 预训练权重做迁移学习小数据集上比从零初始化收敛快得多、最终精度也更高这是遥感目标检测的通行做法。注意一点如果 txt 是上一章的脚本重新生成的请务必保证 data.yaml 里的 names 顺序和转换脚本里的 CLASSES 顺序完全一致。错一个索引训练不会报错但你看到的 mAP 全是假的。4.3 看损失函数曲线判断训练是否翻车三条线分开看训练日志里的 loss 是三条线很多人只看总 loss 看到它在降就安心其实三条线的含义完全不同。box_loss是预测框和真值框的回归损失反映定位精度正常应该前 20 个 epoch 快速下降之后缓慢收敛。cls_loss是分类损失反映类别预测得对不对它会比 box_loss 更早进入平台期。dfl_loss是 Distribution Focal Loss负责框边界的精细回归遥感小目标上它降得慢是正常的因为船的小框边界像素太少边界回归本身就更难。我的判“死缓”标准是如果某一条线在前 10 个 epoch 完全没有下降趋势先别急着加 epoch先查是不是类别定义错了或者某类样本数过少导致梯度被其他类淹没。另一个高频现象是 640 输入下 loss 在某个值附近震荡不降这大概率是小目标被压没了把 imgsz 提到 1280 往往立竿见影。损失函数不是黑匣子每条线背后对应的是一个具体的学习信号缺失问题找到缺失源比盲目调学习率有用。4.4 遥感舰船场景的必调参数imgsz、mosaic和颜色增强imgsz 是遥感场景第一个要调的参数这不玄学。遥感影像里一艘货轮在全幅图中经常只有几十个像素640 输入下缩到十几个像素特征提取器基本失去判别力。我一般先跑一版 640 的 baseline再跑 1280 的对比两组实验出来再谈别的调参。第二个是 mosaic 增强。YOLO 默认在训练早期开 mosaic把 4 张图拼成 1 张。自然图像里很好用但遥感数据上容易出问题拼接边缘会出现海洋区域相邻、拼缝明显的情况模型学到的是“海洋中间有条缝”泛化到正常图片时误检率偏高。常见做法是前 10 个 epoch 正常用 mosaic 做预热之后通过close_mosaic10把它关掉用原图精修。第三个容易被忽略的是颜色增强。遥感影像是传感器成像色调相对稳定不像自然图需要大幅颜色扰动来提高泛化。hsv_h调太高会让模型对颜色不敏感把不同材质但颜色相近的物体混淆。我一般设hsv_h0.01或直接置 0hsv_s0.2hsv_v0.2在遥感数据上效果比默认参数稳。5. 验证与误检调优置信度门限、类别阈值和难例5.1 跑一遍测试图先看可视化再谈指标训练完先用最佳权重对测试图跑一次预测把结果可视化出来再谈 mAPyolo detect predict modelruns/detect/train/weights/best.pt \ sourcetest_images/ saveTrue conf0.25 iou0.45输出目录下每张图都带框落盘。打开图片重点看三类东西船头方向的小艇有没有检出、港口密集区有没有把建筑物标成船、大型油轮有没有被拆成多个框。这三类问题可视化里藏不住指标里反而不容易看出来。5.2 置信度门限不是越高越好conf和iou怎么配很多人看到误检多就调高conf表面看误检少了实际上把低置信度的真目标也压没了。遥感舰船场景我的经验是conf0.25、iou0.45先跑再看可视化分两类调漏检多就降conf到 0.15误检多则优先调iou而不是conf把重叠度阈值从 0.45 提到 0.6减少相近框融合对港口密集停靠场景很有效。指标上遥感小目标场景要多看mAP50而不是只盯mAP50-95后者对框边界过于苛刻遥感影像有云层边缘模糊的问题定位要求过严意义不大。mAP50类别对、能把目标兜住就算正确更贴合实际任务。5.3 误检重灾区建筑、码头、集装箱实际项目里最容易误检成船的第一位是沿海建筑群和码头栈桥连排的几何结构在低分辨率下和集装箱船轮廓高度相似第二位是港口集装箱堆场块状纹理和集装箱船上层建筑极像第三位是防波堤和海中礁石。这三类误检有个共同特征类别不确定性高。我会打开预测结果里的类别置信度矩阵看哪两类互相混淆最严重然后做两件事一是把混淆严重的类别训练样本手动挑出来重新标注增强二是在后处理加几何约束——船的长宽比几乎不会超过 5:1而港口建筑群的长宽比经常到 10:1 以上按目标宽高比过滤能立刻去掉一批低级误检。这也是我给这个数据集定位的最后一环2238 张图决定了模型上限但你没充分利用 xml 里的精确标注做难例挖掘、没在推断环节做几何约束的话连上限的一半都拿不到。我的习惯是每版模型跑完测试都留一批误检截图存档攒够二十张就回头补一次标注。这个习惯帮我挡掉了至少三次在演示现场翻车的风险希望你也能用上。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站