news 2026/10/10 21:28:17

YOLO卫星遥感舰船检测全流程:标签转换、数据划分与训练部署实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
YOLO卫星遥感舰船检测全流程:标签转换、数据划分与训练部署实战

简介:一套面向卫星遥感舰船检测任务的目标检测数据集,适合遥感图像分析、深度学习目标检测方向的学生、算法工程师以及需要快速获得训练数据的开发者。数据集包含5000张真实场景高质量图片,覆盖港口、近岸、开阔水域等多种环境;标注采用LabelImg工具完成,整理为VOC(xml)、COCO(json)与YOLO(txt)三种格式标签,可直接用于YOLO系列模型训练。压缩包共2000个文件,除1986个xml标签文件外,还含5个txt文件与3个py数据集划分脚本、6个html配套教程,覆盖Linux/Windows环境下的YOLO环境搭建与训练案例,可帮助快速完成训练集、验证集、测试集划分并进入实战。资源包整体约418.47MB,已有651人学习下载;适合希望直接获取可用训练样本、又需要配套教程节省环境配置时间的中初级目标检测实践者。

1. 解压这套舰船检测数据集前,先想清楚三个问题

拿到“YOLO卫星遥感舰船检测数据集(含5000张图片)+对应voc、coco和yolo三种格式标签+划分脚本+训练教程.rar”这个包,大多数刚入门 YOLO 的人第一反应是解压、找 train.py、直接开训。但真实情况是:第一次训练大概率死在标签解析上,或者 Loss 怎么都降不下去。这个包的真正价值不在那 5000 张卫星图,而在“三种格式标签 + 划分脚本 + 训练教程”这三件事——它们把一份原始标注变成了能直接喂给 YOLO 的工程化数据。理解了这套东西,你不仅能跑通这个舰船检测项目,还能把同样的流程复用到其他遥感目标检测任务(飞机、油罐、车辆)里。这篇笔记就按“标签格式怎么对齐 → 划分脚本怎么写 → 训练参数怎么调 → 坑在哪 → 怎么部署”一路讲透,适合刚接触遥感目标检测的学生,也适合要把模型落到实际业务里的工程师。

2. VOC、COCO、YOLO三套标签怎么对齐:坐标约定、转换脚本与标签校验

2.1 VOC、COCO、YOLO三种标签的坐标系与存储方式

很多数据集只给一种格式,这个包给足了三套,目的是让你无论用什么训练框架都能直接用。三套标签描述的是同一个东西——图片里的目标框,但组织方式和坐标定义完全不同。

PASCAL VOC 格式是每张图片对应一个 XML 文件,文件名和图片同名。XML 里用<bndbox>记录目标框,xmin/ymin是框左上角像素坐标,xmax/ymax是右下角像素坐标,绝对坐标、左上角为原点、不归一化。类别名以字符串形式写在<name>里,可读性最好,跨工具交换也最方便。

COCO 格式是把所有图片的标注塞进一个 JSON 文件里,images数组存图片信息,annotations数组存每个框,用image_id关联。框的坐标是[x, y, width, height],同样是像素绝对坐标,但注意它是左上角坐标加宽高,不是 VOC 的“两点坐标”。COCO 的category_id从 1 开始计数,这一点后面转格式时最容易踩坑。

YOLO 格式最“朴素”:每张图对应一个同名 txt 文件,每一行是一个目标,格式是class x_center y_center width height,注意这五个值全部是相对图片宽高的归一化浮点数,取值 0~1。class 是从 0 开始的整数索引,不是字符串。

格式存储单元框坐标定义是否归一化类别ID起始
VOC每图一个XMLxmin, ymin, xmax, ymax否字符串name
COCO单JSON文件x, y, width, height否从1开始
YOLO每图一个txtx_center, y_center, width, height是从0开始

看到这三套格式的差异,你就会明白为什么标题要把“三格式标签”单独拎出来作为卖点——没有转换脚本,voc 和 coco 的数据根本喂不进 YOLO。

2.2 从VOC到YOLO到COCO:转换脚本与坐标换算细节

拿到这个包,第一步不是训练,而是确认三套标签是否真的对齐。最常见的情况是:三套标签是不同工具转出来的,其中一套坐标有偏差。我自己一般会用下面这个脚本把 VOC 转成 YOLO,然后拿转换结果和自带的 yolo 标签做对比校验。

import glob import os import xml.etree.ElementTree as ET from PIL import Image # 类别映射表:以你的数据集实际类别名为准 CLASS_MAP = {"ship": 0, "boat": 1} # 这里按实际XML里的name维护 voc_xml_dir = "Annotations" voc_img_dir = "JPEGImages" yolo_label_dir = "labels" # 转换输出目录 os.makedirs(yolo_label_dir, exist_ok=True) for xml_path in glob.glob(os.path.join(voc_xml_dir, "*.xml")): tree = ET.parse(xml_path) root = tree.getroot() # 图片名优先从XML里取,取不到再fallback到文件名 img_name = root.findtext("filename") if not img_name: img_name = os.path.splitext(os.path.basename(xml_path))[0] + ".jpg" img_path = os.path.join(voc_img_dir, img_name) # 尺寸优先用XML里的size字段,因为标注时记录的才是基准 size_el = root.find("size") if size_el is not None: img_w = int(size_el.findtext("width")) img_h = int(size_el.findtext("height")) else: with Image.open(img_path) as img: img_w, img_h = img.size lines = [] for obj in root.iter("object"): name = obj.findtext("name").strip() if name not in CLASS_MAP: print(f"[skip] {img_name}: unknown class '{name}'") continue box = obj.find("bndbox") xmin = float(box.findtext("xmin")) ymin = float(box.findtext("ymin")) xmax = float(box.findtext("xmax")) ymax = float(box.findtext("ymax")) # 绝对坐标 -> 归一化中心点+宽高 x_center = (xmin + xmax) / 2 / img_w y_center = (ymin + ymax) / 2 / img_h w = (xmax - xmin) / img_w h = (ymax - ymin) / img_h # 有的标注会越界,clip到[0,1]避免训练报错 x_center = min(max(x_center, 0.0), 1.0) y_center = min(max(y_center, 0.0), 1.0) w = min(max(w, 0.0), 1.0) h = min(max(h, 0.0), 1.0) lines.append(f"{CLASS_MAP[name]} {x_center:.6f} {y_center:.6f} {w:.6f} {h:.6f}") out_path = os.path.join(yolo_label_dir, os.path.splitext(img_name)[0] + ".txt") with open(out_path, "w") as f: f.write("\n".join(lines))

这个脚本的逻辑很简单,但有三点必须留意。第一,CLASS_MAP必须覆盖 XML 里出现过的所有类别名,否则遇到未知类会静默跳过,导致漏标——所以脚本里打印了[skip]日志,转换完要检查有没有 skip 记录。第二,坐标换算的顺序是“先算中心点和宽高,再除以图片尺寸”,不是“先归一化再算中心”,顺序反了算出来是错的。第三,VOC 的 XML 里<size>字段偶尔会和图片实际尺寸不一致,优先用 XML 里的值,因为标注框是画在 XML 记录的那个尺寸上的。

反过来,如果要转成 COCO 格式,核心逻辑是下面这段:

import json coco = { "images": [], "annotations": [], "categories": [{"id": 1, "name": "ship"}], } for img_id, img_name in enumerate(img_list): # img_w, img_h由图片实际尺寸决定 coco["images"].append({ "id": img_id, "file_name": img_name, "width": img_w, "height": img_h, }) for obj in objects: # 每个目标的xmin, ymin, xmax, ymax coco["annotations"].append({ "id": ann_id, "image_id": img_id, "category_id": 1, # 从1开始,不是从0开始 "bbox": [xmin, ymin, xmax - xmin, ymax - ymin], "area": (xmax - xmin) * (ymax - ymin), "iscrowd": 0, })

COCO 的bbox是[x, y, width, height],x和y是左上角坐标,不能直接把 VOC 的xmax/ymax填进去。category_id从 1 开始,而 YOLO 的 class 从 0 开始,很多转换脚本直接拿 YOLO 的 class id 当 COCO 的 category_id,转出来类别全体错位,这是个特别隐蔽的坑。

2.3 解压后先跑一遍标签校验脚本

三套标签“都有”不等于三套标签“都对”。数据集流传过程中,经常出现图片被批量压缩、重命名,导致 XML 里的 filename 和实际图片对不上,或者 YOLO txt 里的坐标超过 [0,1] 范围。这些问题在训练时才会爆发,表现为 parse labels 报错或者训练 Loss 直接 NaN。下面这个校验脚本能帮你把问题提前暴露出来:

import os from PIL import Image img_dir = "JPEGImages" label_dir = "labels" img_names = [f for f in os.listdir(img_dir) if f.lower().endswith((".jpg", ".jpeg", ".png"))] for img_name in sorted(img_names): img_path = os.path.join(img_dir, img_name) # 1) 图片是否能正常打开 try: with Image.open(img_path) as im: im.load() except Exception: print(f"[broken image] {img_name}") continue # 2) 每张图是否有对应的yolo标签 base = os.path.splitext(img_name)[0] label_path = os.path.join(label_dir, base + ".txt") if not os.path.exists(label_path): print(f"[missing label] {img_name}") continue # 3) 每行是否5个字段,坐标是否在[0,1]区间 with open(label_path) as f: for line_no, line in enumerate(f, 1): parts = line.strip().split() if len(parts) != 5: print(f"[bad line] {img_name}:{line_no}: {line.strip()}") continue cls_id, xc, yc, w, h = parts for val in (xc, yc, w, h): if not (0.0 <= float(val) <= 1.0): print(f"[out of range] {img_name}:{line_no}")

校验脚本不能修数据,但能让你在训练前知道这一批数据的真实质量。如果[missing label]的数量超过了图片总数的 5%,说明标签同步出了问题,直接开训就是在浪费 GPU 时间。

提示:校验通过只是起点。三套标签之间的逐框对齐校验也值得做——随机抽 20 张图,把 VOC 转换结果和自带的 YOLO 标签逐行对比,坐标偏差超过 0.001 就要警惕。

3. 划分脚本的正确姿势:train/val/test同步划分与类别均衡检查

3.1 为什么划分脚本能决定你的mAP上限

很多公开数据集直接给好了 train.txt、val.txt,这个包只给了“划分脚本”让你自己分。你要意识到:划分方式会直接影响模型评估的可信度,甚至比模型结构本身更影响最终效果。

划分的核心矛盾是“数据泄露”。遥感数据和自然图像最大的区别在于:同一景卫星大图会被切成很多张训练小图,这些切片的背景(海面纹理、云层、陆地边缘)高度相似。如果直接把所有图片随机打乱再按 8:1:1 划分,同一个区域的不同切片会同时出现在 train 和 val 里,val 分数会虚高。等你把模型部署到另一片海域,mAP 立刻跳水。

所以划分的第一原则是:按“图源批次”或“区域编号”划分,而不是按单张图片随机划分。如果你的文件名有规律,比如scene1_0001.jpg、scene1_0002.jpg,就按scene1这个前缀分组,前缀级别划分。这比随机划分更接近真实业务——你的训练数据来自已知海域,测试数据应该是模型没见过的海域。

3.2 固定随机种子的分组划分:同时产出train.txt、val.txt与三套标签

下面这个脚本按文件名前缀分组,保证同一个场景的所有切片进同一个集合,同时固定随机种子,保证可复现。这一步务必在训练前跑完,并且把划分结果保存好——深度学习里,可复现性比什么都重要。

import os import random from collections import defaultdict random.seed(42) # 固定种子,换机器跑结果也一致 img_dir = "JPEGImages" output_dir = "splits" os.makedirs(output_dir, exist_ok=True) train_ratio, val_ratio = 0.7, 0.15 test_ratio = 1.0 - train_ratio - val_ratio # 0.15 imgs = [f for f in os.listdir(img_dir) if f.lower().endswith((".jpg", ".jpeg", ".png"))] imgs.sort() # 按前缀分组:前缀可以是场景号、区域号或时间批次 groups = defaultdict(list) for img_name in imgs: prefix = img_name.split("_")[0] groups[prefix].append(img_name) prefixes = sorted(groups.keys()) random.shuffle(prefixes) n = len(prefixes) train_prefixes = set(prefixes[: int(n * train_ratio)]) val_prefixes = set(prefixes[int(n * train_ratio): int(n * (train_ratio + val_ratio))]) test_prefixes = set(prefixes[int(n * (train_ratio + val_ratio)):]) def write_split(path, selected_imgs): # 写绝对路径,YOLO训练时直接读取 with open(path, "w") as f: f.write("\n".join( os.path.join(os.path.abspath(img_dir), x) for x in selected_imgs )) write_split(os.path.join(output_dir, "train.txt"), [i for p in train_prefixes for i in groups[p]]) write_split(os.path.join(output_dir, "val.txt"), [i for p in val_prefixes for i in groups[p]]) write_split(os.path.join(output_dir, "test.txt"), [i for p in test_prefixes for i in groups[p]]) print(f"train: {len(train_prefixes)} prefixes, " f"val: {len(val_prefixes)} prefixes, " f"test: {len(test_prefixes)} prefixes")

跑完这个脚本,train.txt、val.txt、test.txt 就生成了。但还差一步很关键:三种格式的标签要跟着同步划分。如果只把图片划分了,COCO 那个大 JSON 里还是会混着 val 图片的标注——训练时无所谓,但最后评估 test 时 COCO 评估脚本会把训练数据也算进去,结果失真。常见做法是按图片 id 过滤 JSON 里的annotations数组,把 train/val/test 各自导出独立 JSON;VOC 的 XML 则直接按图片名复制到对应目录。这个“同步”动作不需要什么技巧,但忘了就是学术不端的级别。

3.3 划分后的类别统计:先看看数据账再开训

划分完之后别急着训练,先跑一遍统计脚本,看看每个类别在 train/val/test 里的分布。舰船检测数据集很容易出现两种极端情况:某个类别(比如“渔船”)只有几十个样本;或者一大半图片是纯海面没有船(负样本)。前者会导致模型对该类别完全学不动,后者会让 val mAP 虚高——因为模型随便输出空检测也能“猜对”大量负样本。

import os from collections import Counter label_dir = "labels" train_txt = "splits/train.txt" class_counter = Counter() empty_count = 0 with open(train_txt) as f: img_paths = [line.strip() for line in f if line.strip()] for img_path in img_paths: base = os.path.splitext(os.path.basename(img_path))[0] label_path = os.path.join(label_dir, base + ".txt") if not os.path.exists(label_path): continue with open(label_path) as f: lines = [line.strip() for line in f if line.strip()] if not lines: empty_count += 1 continue for line in lines: cls_id = line.split()[0] class_counter[cls_id] += 1 print(f"total images in train: {len(img_paths)}") print(f"empty images: {empty_count} ({empty_count/len(img_paths):.1%})") print("class distribution:", dict(class_counter))

如果某个类别在 train 里少于 100 个框,就要考虑类别权重或者多复制几份;如果空图占比超过 30%,训练时建议把它们单独拎出来做负样本采样,而不是全量喂进去。这一步属于“数据账”,账没算清,后面所有训练技巧都是空中楼阁。

4. 用YOLOv8把5000张舰船图训成模型:PyCharm环境、data.yaml与损失函数调参

4.1 用PyCharm搭YOLOv8环境并写好data.yaml

这个包自带“训练教程”,但我先说一套最通用的落地路径,以 YOLOv8 为例(YOLOv5、YOLOv11 同理)。环境搭建最简单的方式:PyCharm 里新建虚拟环境,然后执行pip install ultralytics。用 PyCharm 的好处是 SSH 远程调试和本地跑小样本都方便,环境出问题看得到完整 traceback。

# 在PyCharm Terminal里执行 python -m venv venv # Windows: venv\Scripts\activate Linux/Mac: source venv/bin/activate pip install ultralytics python -c "from ultralytics import YOLO; print(YOLO.__version__)"

能打印出版本号,环境就通了。接下来写 data.yaml,这是 YOLOv8 训练的数据入口,路径写错了一切白搭。

# data.yaml path: /home/user/ship_det # 数据集根目录,绝对路径 train: images/train # 训练图片目录 val: images/val # 验证图片目录 test: images/test # 测试图片目录(可选) names: 0: ship 1: boat

data.yaml 里有三个点极其容易出错。第一,path必须用绝对路径,相对路径在 ultralytics 不同版本里行为不一致。第二,names的索引必须和 txt 标签里写的 class id 完全一致——如果你的 VOC 转 YOLO 时把 ship 映射成了 1,但 data.yaml 里 ship 是 0,训练不会报错,但 mAP 永远是 0。第三,如果你的数据集只有 ship 一类,names也要写成0: ship,写成{ship: 0}这种字典形式在某些版本下不兼容。

4.2 训练命令与关键参数:小目标场景为什么把imgsz拉到1280

数据准备完,训练命令其实只有一行。但这一行里的参数,值得逐个较真。

yolo detect train \ data=data.yaml \ model=yolov8s.pt \ epochs=150 \ imgsz=1280 \ batch=16 \ patience=30 \ device=0 \ cache=True

imgsz=1280是舰船检测里最该重视的参数。卫星图像里的舰船往往只占几十个像素,YOLOv8 默认的 640 输入会把小目标缩到十几个像素,特征图上一个点都覆盖不到。拉到 1280 之后,小目标的像素面积扩大了四倍,recall 的提升非常明显。代价是显存占用翻倍、训练时间变长——5000 张图、1280 分辨率、s 模型,单卡大概 4~6 小时能跑完 150 epoch,这个成本完全值得。

model=yolov8s.pt选 s 而不是 m 或 l,因为 5000 张图的数据量对 m 以上模型来说不够,强行上大模型只会过拟合。如果你的显存有余量,可以试试yolov8m.pt,但我不建议在数据量没翻倍之前这么做。

batch=16在 imgsz=1280 下,24G 显存刚好能跑。显存不够就先降到 8,不要用batch=-1让程序自动探测,那个自动值经常偏保守。

cache=True会把图片一次性缓存到内存里,5000 张图大概占用 10~15G 内存,内存不够就删掉这个参数。没有缓存的话,每个 epoch 都要重新读一遍磁盘,SSD 还好,机械硬盘会慢得让人怀疑人生。

4.3 从损失函数曲线判断训练是否健康

训练跑起来之后,你要看得懂曲线的含义。YOLOv8 的损失函数由两部分构成:分类损失cls_loss用的是 BCE(二元交叉熵),回归损失box_loss用的是 CIoU 加上 DFL(Distribution Focal Loss)。DFL 是 YOLOv8 从 v6 继承下来的,它不直接回归框的偏移量,而是让网络预测“边界落在每个像素区间的概率分布”,对模糊边界更鲁棒——这对遥感舰船这种边缘不清晰的细长目标很关键。

训练时重点看两个曲线。第一,train/box_loss应该在 30 epoch 内从高位快速下降然后趋于平缓。如果它一直在高位震荡,说明标签有问题(大概率是坐标算错了),不要继续等。第二,val/box_loss如果出现“先降后升”的 U 形,就是过拟合开始了,这时候 patience 参数会触发早停——patience=30表示 val 指标连续 30 个 epoch 不刷新就自动停掉。

一个小技巧:遥感舰船检测里,正负样本极度不平衡——大部分背景是海洋,没有目标。YOLOv8 里控制这个的损失函数参数是cls_loss的权重系数,ultralytics 默认没暴露这个参数,你需要改损失权重时,可以直接改 dataclass 里的cls项。但大多数情况下,先不动的默认值跑通流程,比一开始就调损失函数权重更明智。

注意:训练过程里 val 曲线的 mAP50 和 mAP50-95 都要盯。mAP50 对定位精度不敏感,2 个像素的偏移影响不大;mAP50-95 才是衡量框质量的金标准。如果你的 mAP50 挺高但 mAP50-95 很低,说明框的位置整体有系统性偏移,不是模型没学会,而是标签转换时坐标差了一个固定值。

5. 训练避坑:舰船遥感数据上我翻过的五个车

5.1 类别索引错位:voc转yolo后train loss怎么都压不下去

现象:训练了 50 个 epoch,train loss 还在高位,val mAP50 无限接近 0。

原因:VOC 转 YOLO 时类别映射表不完整或顺序不对。最常见的是 VOC 里的类别名有大小写差异,比如Ship和ship被映射成两个 id;或者转换脚本从 1 开始编号,而 YOLO 要求从 0 开始,导致全部标签整体错位一位。模型其实一直在学“把某个类别预测成偏移后的另一个类别”,但它永远学不对。

解决:转换前先统计 XML 里所有出现过的类别名,和CLASS_MAP对比。写一行代码打印set(root.findtext("name"))的集合,用集合差集一眼就能看出漏了哪些类。训练前更保险的做法是:把 train.txt 里所有标签文件的 class id 全部扫描一遍,打印分布,确认只有 0 和 1 而不是 0、1、2、3 这种跳跃值。

5.2 划分脚本没同步:val里混进了训练图

现象:val mAP 高达 0.9,但换了一批新图片测试,mAP 掉到 0.5。

原因:划分时只划了图片文件,没有同步处理 COCO 格式的 JSON 标注。val 评估时用的是单独的 val JSON,但那个 JSON 里可能包含了训练图片的标注,模型在训练时已经见过这些图了。另一个场景是切图数据集里,同一个大场景的相邻切片被随机分到了 train 和 val,模型的“高分”其实是背题背出来的。

解决:划分以场景前缀为最小单位,固定随机种子;划分完成后逐一对比 train.txt、val.txt、test.txt 里的图片路径,确认没有交集。COCO JSON 的同步可以用图片 id 集合做过滤,这一步不要省。

5.3 背景近岸干扰:模型把码头和防波堤当船

现象:模型在海面上的召回率不错,但一到近岸区域就疯狂误检,把码头、防波堤、大型吊车都标成船。

原因:遥感舰船检测的难点从来不是“船长得像船”,而是“码头上全是长得像船的东西”。如果你的标注只框了船身,没框船旁边的码头设施,模型在没有足够负样本的情况下会把“矩形纹理 + 靠岸位置”当成船的强特征。

解决:别急着调模型,先看数据。检查训练集里近岸样本占比,如果太少,考虑加入负样本。另一个有效手段是难例挖掘——用当前模型对训练集做一次推理,把置信度高于 0.3 的误检框提取出来,作为额外的负样本加入下一轮训练。这个办法对遥感场景尤其管用,因为遥感数据集的负样本不像自然图像那么容易天然存在。

5.4 旋转增强翻车:海天线和泊位语义被毁掉了

现象:开了degrees=90之后,val loss 不降反升,训练集本身 loss 倒是降得飞快。

原因:旋转增强对普通目标检测是常规操作,但舰船场景里“船靠码头”的方向语义会被旋转破坏。一张原本船头朝左的船被旋转 90 度后,船头朝上了,但船和码头的位置关系变成了“船悬在半空”。模型学到了错误的上下文特征,在真实场景里自然翻车。

解决:遥感舰船检测的旋转增强要克制。degrees=10以内能增加方向多样性,又不破坏泊位语义;degrees=90这种大角度旋转只适合海面中央的孤立舰船,不适合近岸场景。如果你的数据集里近岸样本占比高,直接把 degrees 关掉都比开大角度强。

5.5 5000张图不等于5000个实例:数据账要提前算

现象:训练了 200 个 epoch,某个细分类别(比如渔船)的 recall 始终是 0。

原因:5000 张图听起来很多,但遥感大图切出来的小图里可能只有 1~2 艘船,甚至很多图里没有船。真正有标注的舰船实例可能只有 1 万个。假设其中有 4 个类别,分配到“渔船”这个类头上的可能只有 200 个实例——模型根本学不够。

解决:训练前先跑 3.3 的统计脚本,算清楚每个类别有多少个实例、多少张图完全为空。如果某个类别少于 500 个实例,优先考虑类别复制增强、MixUp,或者干脆合并成“船只”大类。记住:5000 张图是数据包的量,实例数才是训练的有效量。

6. 验证与进阶:大图切块推理与ONNX部署的实战技巧

6.1 大图切块推理:滑窗、重叠与检测结果合并

用这个数据集训练出的模型,最终要面对的是几千乘几千像素的原始卫星大图。直接整图送进模型,即使等比缩放到 1280,小目标也早已缩成几个像素。我现在的习惯是:训练时用 1280 分辨率,推理时对大图做滑窗切块,块大小 1280,重叠率 10%,逐块推理后再把检测框映射回原图坐标,然后用 NMS 合并重叠块之间重复检出的框。

# 滑窗推理的核心逻辑示意 overlap = 0.1 for y in range(0, H - crop_size, int(crop_size * (1 - overlap))): for x in range(0, W - crop_size, int(crop_size * (1 - overlap))): patch = big_img[y:y+crop_size, x:x+crop_size] results = model(patch, imgsz=1280) for box in results[0].boxes: # 把patch坐标平移到原图坐标 x1, y1, x2, y2 = box.xyxy[0] dets.append([x + x1, y + y1, x + x2, y + y2, box.conf, box.cls]) # 最后做一次全局NMS,消除重叠区域的重复检测

切块推理的收益远超想象:小目标的召回率能提升 10 个百分点以上。代价是推理时间线性增加——但舰船检测是典型的离线处理场景,一小时出结果完全能接受,比漏掉一艘船强得多。

6.2 导出ONNX并在AGX Orin上落地

训练完的best.pt要落地到边缘设备,比如 AGX Orin 这类嵌入式平台,常规做法是导出 ONNX 后用 TensorRT 加速,int8 量化后帧率至少翻一倍。导出命令很简单:

yolo export model=best.pt format=onnx dynamic=True imgsz=1280

导出后有三件事要验证:第一,ONNX 模型的输入输出 shape 是否是 dynamic batch——遥感推理里单张送和多张送都常见,dynamic batch 比 dynamic shape 更实用;第二,用 ONNX Runtime 跑一遍样例,对比 PyTorch 的检测输出,坐标偏移要在 1 个像素以内;第三,在 AGX Orin 上用 TensorRT 加载 ONNX 时,算子是否全部支持,不支持的算子需要用trtexec做兼容性检查。遥感切块推理对单帧延迟不敏感,但对吞吐量敏感,TensorRT 的 int8 量化能把整张大图的处理时间从几分钟压到几十秒。

最后说一个我自己的血泪教训:早期做舰船检测时,舍不得切图,直接把大图缩到 1280 推理,val mAP 看着还过得去,但到了真实业务里,小目标漏检漏到怀疑人生。后来养成一个习惯——任何遥感检测项目,训练前先想清楚“模型最终面对的是什么尺寸的图”,再决定是否需要切图、切多大、重叠多少。这个决策做在前面,能省掉后面一整轮的返工。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 21:27:49

Matlab实现核岭回归(KRR)多变量预测完整指南:原理、代码与调参

1. 对KRR多变量预测这件事的整体拆解先说说这类“多输入单输出”预测到底在解决什么问题。你手里有一堆特征&#xff0c;比如温度、压力、湿度、转速&#xff0c;要预测一个结果值&#xff0c;比如材料强度、能耗、产量、房价。特征和结果之间往往不是简单的线性关系&#xff0…

作者头像 李华
网站建设 2026/10/10 21:27:12

控制保障与机器学习任务规划:三层架构、训练调参与上线验证

简介&#xff1a;这是一份西安电子科技大学硕士学位论文PDF&#xff0c;主题围绕控制保障系统中的任务规划软件设计与实现&#xff0c;适合从事软件架构、自动化调度、人工智能与机器学习应用开发的工程师及相关专业学生深入学习。论文以某试验验证系统为背景&#xff0c;针对复…

作者头像 李华
网站建设 2026/10/10 21:22:44

指甲病变目标检测数据集:2923图4类YOLO+VOC双格式

简介&#xff1a;本资源是一套面向计算机视觉初学者与医疗AI研究者的指甲病变目标检测专用数据集&#xff0c;聚焦肢端雀斑样痣黑、甲沟炎、甲弯曲及泰瑞氏甲四类临床常见指甲疾病识别任务&#xff0c;适用于YOLO系列、Faster R-CNN等主流检测模型的训练与验证。压缩包共2000个…

作者头像 李华