资讯动态

YOLO多类别船舶检测数据集实战:从数据体检到模型调优

发布时间:2026/10/18 6:08:04 来源:尧图企业网站定制
简介目标检测是计算机视觉的核心任务之一旨在让算法在图像中定位并识别多个对象。YOLO作为经典的实时目标检测算法以单阶段回归方式同时完成目标定位与分类被广泛应用于工业质检、安防监控、海事巡航等场景。在船舶检测应用中检测模型的性能高度依赖训练数据的质量和分布尤其是多类别船舶数据集往往面临类别不平衡、标签噪声、小目标漏检等挑战。针对这些工程痛点处理一套12122张带标签图像的多类别船舶检测数据集需要从目录结构校验、标签格式体检、类别分布统计做起再结合YOLO的训练配置、损失调整以及Copy-Paste等增强策略才能有效提升模型表现。围绕这一实践流程覆盖数据预检、训练优化与小目标处理的关键经验可为相关项目提供可复用的参考。1. 多类别船舶检测数据集12122 张带标签图像到底能做什么拿到一个名为“YOLO算法-多类别船舶检测数据集”的压缩包里面是12122张带标签的船舶图像覆盖航空母舰、潜水艇、游船、集装箱船、猛拉、散货船、帆船七个类别。未经体检的数据集只是半成品——直接扔进训练脚本大概率会在两三个小时后收获一个 mAP 惨淡的模型然后开始在类别平衡、标签清洗上返工。这个数据集的实际价值在于它把 YOLO 训练的前半程压缩了目录结构、标签格式、类别文件理论上都已就位你要做的是验证它、修正它然后把训练跑起来。这篇文章面向两类人想用现成数据快速验证船舶检测方案的工程师以及需要理解多类别检测数据从落地到部署全过程的初学者。我会照自己处理同类数据集时的流程来写先查数据再谈训练先说坑再调参数。12122 张图不算小但对七个类别来说每个类别的样本量差异会直接决定模型的天花板这一点后面会展开说。2. 从 ZIP 到训练集目录结构、标签格式与数据体检脚本2.1 解压后的目录规范images 和 labels 的对应关系常见的做法是数据集按 YOLO 约定的目录树组织你解压后应看到类似下面的结构boat_dataset/ ├── images/ │ ├── train/ │ ├── val/ │ └── test/ └── labels/ ├── train/ ├── val/ └── test/如果你的压缩包解压后不是这个结构而是所有图片堆在一个文件夹里、标签混在一起第一步就是用脚本把它们归位。我的习惯是先看有没有data.yaml或classes.txt这两个文件决定了训练时类别怎么映射。一个容易忽略的点YOLO 要求同名的图片和标签文件一一对应例如images/train/0001.jpg对应labels/train/0001.txt。如果图片是.jpg而标签是.txt这没问题但如果图片是.png而标签文件名却以.jpg.txt结尾匹配就会失败。解压后第一件事不是打开图片看而是跑一个配对检查。cd boat_dataset find images/train -name *.jpg | wc -l find labels/train -name *.txt | wc -l逻辑说明find列出两个目录下的文件数量数字对不上说明有图片缺失标签或有多余标签。很多新手在这里吃过亏——用数据加载器训练时报FileNotFoundError查了半天才发现是解压时标签子目录没解全。数量一致不代表文件名匹配所以还要做一层严格校验。for f in images/train/*.jpg; do base$(basename $f .jpg) if [ ! -f labels/train/$base.txt ]; then echo missing label: $base fi done参数说明basename $f .jpg去掉路径和扩展名得到纯文件名编号循环里逐个检查同名 txt 是否存在。跑完后如果没有输出说明配对正常。这个脚本只管图片到标签的单向检查反向检查有标签无图片同理只是把循环方向反过来。2.2 标签文件内容一行一个目标的 YOLO 格式YOLO 的标签格式是纯文本每行代表一个目标格式为class x_center y_center width height四个坐标值都是归一化后的比例值范围 0 到 1 之间不是像素值。类别class从 0 开始计数对应data.yaml里的类别顺序。看一个实际用例# 打开一个标签文件例如 labels/train/0042.txt cat labels/train/0042.txt预期输出形如3 0.512345 0.678912 0.082341 0.056789 0 0.123456 0.234567 0.045678 0.032145逻辑说明第一行第一个数字3代表该类在classes列表中的索引假设索引 3 是集装箱船后面依次是目标框中心的 x、y 坐标以及框的宽高都是相对图片尺寸的归一化比例。坐标值大于 1 或小于 0 都意味着标签有问题后面章节会专门讲这个坑。我看到不少数据集会在labels目录里混入classes.txt或_darknet.labels文件这些不是训练必需的标签文件反而可能被误读。处理方式是确认类别顺序后把这类文件移出labels目录避免框架在读取时产生歧义。2.3 数据体检用脚本统计类别分布和标签健康度拿到数据先别急着训做一次体检能省下后期大量调参时间。我一般写一个 Python 脚本统计每个类别的目标数量、图片数量、平均每张图的标注框数以及检测坐标是否越界。import os from collections import Counter labels_dir labels/train class_counter Counter() box_counter Counter() invalid_files [] for fname in os.listdir(labels_dir): if not fname.endswith(.txt): continue file_path os.path.join(labels_dir, fname) with open(file_path, r) as f: lines f.readlines() if not lines: invalid_files.append((fname, empty label file)) continue box_counter[len(lines)] 1 for line in lines: parts line.strip().split() if len(parts) ! 5: invalid_files.append((fname, fmalformed line: {line.strip()})) continue cls int(parts[0]) coords [float(v) for v in parts[1:]] if any(v 0 or v 1 for v in coords): invalid_files.append((fname, fcoords out of range: {line.strip()})) class_counter[cls] 1 print(class distribution:, sorted(class_counter.items())) print(boxes per image:, sorted(box_counter.items())) print(invalid files:, len(invalid_files)) for fname, reason in invalid_files[:20]: print(fname, reason)逻辑说明脚本遍历labels/train下所有.txt文件用Counter统计每个类别的目标总数、每张图的标注框数分布并检查三类异常——空标签文件、行格式错误、坐标越界。运行后重点关注class distribution如果某个类别的数量只有几十而其他类别有几千训练时就该考虑类别权重或数据增强策略。参数说明coords检查用any(v 0 or v 1)因为 YOLO 归一化坐标的合法区间是[0, 1]任何超出都说明标签有问题。空标签文件在训练中不会导致报错但会造成该图片的梯度贡献为零数量过多时会影响整体收敛。同样的脚本可以套用到labels/val上。重要的是把体检结果记录下来作为后续模型效果分析的基线——如果某个类别训练后 AP 特别低回看分布就能确认是样本太少还是目标太小。3. 七类船舶的形态差异与检测难度的现实评估3.1 从航空母舰到帆船目标尺寸和特征区分度多类别检测的难点从来不只是“类别更多”而是类间相似与类内差异同时存在。这个数据集里七个类别的情况可以分成几组看类别典型目标尺寸主要特征检测难点航空母舰大目标占图比例高平直甲板、舰岛偏置训练样本可能偏少类别内形状较统一潜水艇小到中目标细长艇体水面只露一小截水面背景对比度低容易被漏检游船中目标多层上层建筑与散货船、集装箱船轮廓有相似集装箱船中到大目标甲板满载集装箱方块感强集装箱纹理密集相互遮挡猛拉疑为拖船 tug 的机翻小目标短粗船体驾驶室靠前样本量可能不多且与游船易混淆散货船中到大目标长甲板舱口盖空载时上层建筑特征弱帆船小目标桅杆和帆面背景复杂时小目标特征弱从检测角度看航空母舰这类大目标的边界框容易回归准但类别区分度依赖甲板细节帆船和潜水艇这类小目标则考验特征提取层的细粒度。游船、散货船、集装箱船三者的船体轮廓都是“长条上层建筑”如果标注框只是大致框住船体模型很容易把局部特征学混。处理这类数据时我习惯先看类别间的目标尺寸分布方法是对标签里的宽高做统计。宽高都是归一化值乘上图片尺寸就能还原像素大小但这个数据集的图片尺寸不统一跨图比较时直接用归一化宽高更稳妥。import os import numpy as np labels_dir labels/train widths, heights [], [] cls_list [] for fname in os.listdir(labels_dir): if not fname.endswith(.txt): continue with open(os.path.join(labels_dir, fname)) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls_list.append(int(parts[0])) widths.append(float(parts[3])) heights.append(float(parts[4])) for cls_id in set(cls_list): w np.array([widths[i] for i, c in enumerate(cls_list) if c cls_id]) h np.array([heights[i] for i, c in enumerate(cls_list) if c cls_id]) print(fclass {cls_id}: median w{np.median(w):.4f}, median h{np.median(h):.4f})逻辑说明这段脚本按类别统计了目标框的归一化宽度和高度的中位数。中位数比平均值抗离群干扰能反映该类别典型的框尺寸。比如帆船的中位宽度可能是 0.05而航空母舰可能是 0.5——两者相差一个数量级锚框设置就要兼顾。参数说明np.median计算中位数set(cls_list)去重得到类别列表。如果某个类别的中位宽度和高度都显著小于 0.1这个类别就属于模型的小目标人群会在训练配置和增强策略上单独考虑。3.2 类别不均衡与样本量陷阱先看数量再谈模型12122 张图听起来很多但摊到七个类别上不均衡几乎是必然的。常见情况是集装箱船和散货船的样本占了大头帆船、潜水艇这类小众类别只有几百张。训练时模型会偏向样本多的类别导致小类别 AP 很低。处理不均衡有几个层次的措施按投入产出排序第一层是训练时给少样本类别更高的损失权重。YOLO 的损失函数中各类别的权重可以通过配置调整常见做法是让少样本类的损失贡献放大 1.5 到 3 倍让模型对其误判付出更大代价。# 假设 data.yaml 中类别顺序对应 # 0: 航空母舰, 1: 潜水艇, 2: 游船, 3: 集装箱船, 4: 猛拉(tug), 5: 散货船, 6: 帆船 class_weights: 0: 1.0 1: 2.0 2: 1.0 3: 1.0 4: 2.5 5: 1.0 6: 2.5注意不是所有 YOLO 系列框架都直接支持这种权重配置。有些版本要在损失函数里手动挂权重有些通过--weight参数传入类别权重的文件路径。具体要看框架实现不展开到某个具体版本。第二层是图像级重采样。对包含少样本类别的图片做复制或重复采样让模型在同一个 epoch 里更多次看到这些样本。这个做法代价低但要注意别把某几张图反复训练导致过拟合。第三层是在数据增强层面针对小类别做处理这一层放到最后一章展开讲。这里先确认一种错误做法很多人直接删除样本量少的类别让模型只检测四五个类。如果业务确实需要七类删除等于砍掉需求如果业务只关心主要船型适当合并相似类别反而是合理的。需要警惕的是标注质量问题数据集的标注框如果框得太松类别边界模糊模型 AP 会集体下降。抽 50 张图目测检查标注框是否贴合船体、类别标签是否张冠李戴这个步骤建议不要跳过尤其是标题里类别名混乱的情况。一个快速抽查方案用 OpenCV 把标签框画回图像随机抽 50 张保存到check/目录肉眼过一遍。import cv2, os img_dir images/train label_dir labels/train out_dir check os.makedirs(out_dir, exist_okTrue) names os.listdir(img_dir)[:50] for name in names: base os.path.splitext(name)[0] img_path os.path.join(img_dir, name) label_path os.path.join(label_dir, base .txt) if not os.path.exists(label_path): continue img cv2.imread(img_path) h, w img.shape[:2] with open(label_path) as f: for line in f: parts line.strip().split() if len(parts) ! 5: continue cls, cx, cy, bw, bh int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, str(cls), (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 0, 255), 2) cv2.imwrite(os.path.join(out_dir, name), img) print(done)逻辑说明这段脚本把归一化坐标还原成像素坐标画出边界框和类别序号输出到check/目录。h, w img.shape[:2]取图像实际尺寸归一化坐标乘回尺寸得到真实像素位置标签中心点和宽高换算成左上角右下角坐标供cv2.rectangle绘制。参数说明os.listdir(img_dir)[:50]只取前 50 张做抽查cv2.putText的0.6是字号红字为类别索引号。抽查时优先关注小目标类别的框是否准、有没有把码头设施或另一艘船并进来。4. 用 YOLO 把训练跑通数据划分、参数设置与收敛判断4.1 分层划分训练/验证/测试集别让随机划分毁掉评估多数数据集在压缩包里已经划分好train/val/test但划分是否合理要验证两个点一是类别分布是否均衡地出现在各分集里二是同一条船的不同图片是否被拆到训练和验证两个集合中。对船舶检测来说同一艘船在不同角度、不同光照下的照片如果同时出现在训练集和验证集验证指标会虚高。这个数据集是图像数据集而非视频序列难以完全避免图像级重合但至少不要在划分时人为加剧这个问题。如果压缩包内没有划分目录或者你打算重新划分我习惯按 8:1:1 的比例分层抽样保证每个类别在三个集合中的占比接近原始分布。import os, random, shutil from collections import defaultdict random.seed(42) img_dir images label_dir labels out_base boat_split image_files [f for f in os.listdir(img_dir) if f.endswith(.jpg)] class_to_images defaultdict(list) for fname in image_files: base os.path.splitext(fname)[0] label_path os.path.join(label_dir, base .txt) if not os.path.exists(label_path): continue with open(label_path) as f: first_line f.readline().strip() if first_line: cls int(first_line.split()[0]) class_to_images[cls].append(fname) for cls, files in class_to_images.items(): random.shuffle(files) n len(files) n_train int(n * 0.8) n_val int(n * 0.1) splits { train: files[:n_train], val: files[n_train:n_train n_val], test: files[n_train n_val:] } for split_name, split_files in splits.items(): os.makedirs(os.path.join(out_base, images, split_name), exist_okTrue) os.makedirs(os.path.join(out_base, labels, split_name), exist_okTrue) for fname in split_files: base os.path.splitext(fname)[0] shutil.copy(os.path.join(img_dir, fname), os.path.join(out_base, images, split_name, fname)) shutil.copy(os.path.join(label_dir, base .txt), os.path.join(out_base, labels, split_name, base .txt))逻辑说明脚本按每个类别独立划分用每个图片标签文件的第一行来确定类别归属。因为一张图可能同时包含多个类别的目标这里只按第一个目标分类存在一定误差但对分层划分来说足够。random.seed(42)保证每次运行结果一致便于复现。参数说明n_train int(n * 0.8)是训练集数量n_val int(n * 0.1)是验证集数量余下是测试集。如果你的数据量小可以把测试集并入验证集只留 train/val 两分12122 张图规模不需要这样做。4.2 训练配置模型尺寸、epoch、batch 的第一组参数训练前的核心配置是模型输入尺寸、batch size、epoch 数。对船舶检测来说我默认从 640×640 输入开始原因有两个这个数据集中存在帆船、潜水艇等小目标过小的输入尺寸如 320会让小目标的特征在浅层就消失过大的输入尺寸如 1280虽然对小目标更友好但训练速度大幅下降显存占用也翻倍。batch size 受显存限制。以一张 24 GB 显存的显卡为例batch 16是比较稳妥的起点显存不足时降到batch 8配合梯度累积效果接近。epoch 数不宜一上来就拉很高先跑 100 轮观察收敛趋势后期用早停机制判断。训练命令因框架而异这里给出一个通用的训练入口参数表参数建议值说明输入尺寸640×640兼顾小目标与速度batch size1624G 显存显存不足时用 8epoch100 起步配合早停优化器AdamW 或 SGD默认 AdamW收敛更稳初始学习率0.01SGD/ 0.001AdamW过大易发散过小收敛慢数据加载线程8CPU 瓶颈时可调类别权重按 3.2 节设置平衡不均衡类别训练过程还会用到的两个配置是 mosaic 增强和多尺度训练。Mosaic 把四张图拼成一张对小目标检测提升明显代价是训练初期 loss 波动较大前几轮可以关闭或降低概率等模型稳定后再打开。多尺度训练让模型适应不同输入尺寸但对显存不宽裕的环境意义不大。4.3 训练完成后怎么判断效果从 loss 曲线到逐类 AP训练不是跑完就结束判断模型是否收敛、效果是否达标主要看三样东西训练和验证 loss 曲线、每个类别的 AP、以及验证集上的可视化预测结果。Loss 曲线的判断标准是训练 loss 稳步下降后趋于平缓验证 loss 在下降后不再明显波动。如果验证 loss 在某个 epoch 后开始上升但训练 loss 继续下降就是过拟合信号此时应停止训练并采用验证 loss 最低的权重。逐类 AP 是评估多类别检测的核心。船舶数据集的常见结果是集装箱船和散货船 AP 较高帆船和潜水艇 AP 较低。如果某个类别的 AP 比中位数低 20 个百分点以上处理的优先级最高。一个容易被忽略的验证手段是把模型在验证集上的预测结果可视化。预测框和标注框叠在一起看能直观发现漏检是发生在背景复杂的海面还是发生在目标重叠区域这些信息在调整 NMS 阈值和置信度阈值时会用到。import cv2 from pathlib import Path # 假设模型推理结果的格式为: 每行 [x1, y1, x2, y2, conf, cls] results_dir Path(val_predictions) img_dir Path(images/val) for res_file in sorted(results_dir.glob(*.txt)): base res_file.stem img cv2.imread(str(img_dir / f{base}.jpg)) with open(res_file) as f: for line in f: parts line.strip().split() if len(parts) ! 6: continue x1, y1, x2, y2, conf, cls map(float, parts) cv2.rectangle(img, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2) # 保存到结果目录 cv2.imwrite(fval_visual/{base}.jpg, img)逻辑说明这段脚本读取模型输出的文本文件将预测框画回原图用于人工检查。map(float, parts)把六个字段转成浮点数conf是置信度cls是类别坐标已经是像素值所以直接传给cv2.rectangle。参数说明预测结果文件通常包含置信度低于 0.25 的框可视化时可以在循环里加if conf threshold: continue来过滤低置信度预测我更习惯先保留所有框观察模型在低置信度区间的行为。5. 船舶检测数据集的常见坑与排查从标签错位到类别混淆5.1 现象训练时报坐标越界或大量负损失训练开始后不久日志中出现RuntimeError: index out of bounds或损失值变成负数这是标签坐标异常的直接表现。原因标签文件中的归一化坐标存在小于 0 或大于 1 的值常见于标注工具导出时的坐标系错误——比如把像素坐标当成归一化坐标写入或者图像的 EXIF 方向信息导致标签和图像实际宽高不一致。解决用 2.3 节的体检脚本全量扫描所有标签目录找出越界文件按类别和越界维度分组统计。如果越界数量少几十个以内直接删除对应图片和标签或手工修正越界值如果数量大先检查标签生成脚本确认坐标系转换处是否乘了正确的宽高。import os def fix_coords(label_path, img_w, img_h): with open(label_path) as f: lines f.readlines() fixed [] for line in lines: parts line.strip().split() if len(parts) ! 5: continue cls, cx, cy, bw, bh int(parts[0]), float(parts[1]), float(parts[2]), float(parts[3]), float(parts[4]) cx max(0.0, min(1.0, cx)) cy max(0.0, min(1.0, cy)) bw max(0.0, min(1.0, bw)) bh max(0.0, min(1.0, bh)) fixed.append(f{cls} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(label_path, w) as f: f.write(\n.join(fixed) \n)逻辑说明这个修正函数把每个坐标值压缩到合法区间。max(0.0, min(1.0, v))的含义是如果 v 小于 0 取 0如果大于 1 取 1否则保留原值。这只是一种应急处理如果框的中心点本身被标错压缩坐标不会让框变对只是消除越界报错。5.2 现象游船和帆船两个类别互相误检验证集上出现系统性误检游船被识别成帆船帆船被识别成游船而且置信度还不低。原因一方面是这两类船在视觉上存在重叠——小型游船可能也带桅杆帆船靠岸时发动机工作状态和游船区分度低另一方面是标注边界不清晰部分标签里同一个目标在不同图片中被标成不同类别。解决先做标注一致性检查找出所有同时包含这两个类别的图像观察是不是同一场景中的同一个目标被标了两遍。如果确实存在标注不一致最省事的方案是合并这两个类别把业务需求里能接受的类目合并掉如果业务必须区分就需要补充针对性训练样本。检查脚本的核心逻辑是找出同时包含两个类别的图片import os def find_multi_class_images(label_dir, cls_a2, cls_b6): result [] for fname in os.listdir(label_dir): if not fname.endswith(.txt): continue classes set() with open(os.path.join(label_dir, fname)) as f: for line in f: parts line.strip().split() if parts: classes.add(int(parts[0])) if cls_a in classes and cls_b in classes: result.append(fname) return result targets find_multi_class_images(labels/train, 2, 6) print(len(targets), images contain both classes)这里的数值 2 和 6 是示例实际要按data.yaml里的类别顺序改。运行后看这些图片的标注判断是船体被标成了两类还是确实一图两船。5.3 现象类别名“猛拉”导致标签解析错位标题中的“猛拉”不是一个常见中文船型名称大概率是tug拖船的机翻结果。如果classes.txt或data.yaml中写着“猛拉”而标注文件中的类别索引 4 实际对应拖船模型训练不会报错但部署时类别名会误导业务方。原因原始数据集来自英文或标注工具的自动翻译类别名词典生成时没有校验。解决手工确认该类别在图片中的真实形态比对多张图后把data.yaml中的名称改为“拖船”或保留英文tug。注意修改名称不会影响类别索引只要顺序不变标签文件里的数字不用改动。5.4 现象小目标类别 AP 极低训练完成后潜水艇、帆船两个类别的 AP 明显低于其他类别可视化结果中漏检集中在小尺寸目标上。原因这两种目标在图像中的像素面积占比小特征在经过多层下采样后可能已经丢失。加上类别样本量可能偏少模型既看不到足够多的样本又难以提取细粒度特征。解决最直接的手段是把输入尺寸从 640 提到 960 或 1280代价是训练和推理速度下降其次是靠增强策略在训练时人工增加小目标出现频率这个思路在下一章展开。还有一种做法是使用模型内置的小目标检测头使浅层特征图直接参与预测但会增大计算量需根据实际场景取舍。排查这一步时先确认小目标类别到底有多少训练样本如果样本不足百张任何模型改动都救不回来优先补数据如果样本过千但 AP 仍低再考虑输入尺寸和检测头配置。6. 让模型再进一步针对小目标与少样本类别的增强策略数据集规模固定在 12122 张时提升模型上限的路径集中在增强策略和难例挖掘上。Mosaic 增强已经是很多框架的默认行为但针对船舶数据的特点有两类增强值得单独调整参数。第一类是 Copy-Paste 增强。它的思路是把小目标从一张图中裁剪出来随机粘贴到另一张训练图上同时生成对应的标签。对样本量少的类别尤其有效——比如潜水艇在一张训练图中只出现一次通过复制粘贴同一个目标可以在多张图的组合中出现多次。实际操作中我习惯对少样本类别做 0.3 到 0.5 的粘贴概率过多会让模型记住目标的拼接特征过少作用不明显。import cv2 import numpy as np import random def copy_paste_object(src_img, src_labels, dst_img, dst_labels, target_cls, paste_prob0.4): if random.random() paste_prob: return dst_img, dst_labels candidates [l for l in src_labels if l[0] target_cls] if not candidates: return dst_img, dst_labels line random.choice(candidates) cls, cx, cy, bw, bh line h, w dst_img.shape[:2] # 从原图上裁剪目标区域 x1 int((cx - bw / 2) * w) y1 int((cy - bh / 2) * h) x2 int((cx bw / 2) * w) y2 int((cy bh / 2) * h) obj src_img[y1:y2, x1:x2].copy() # 在目标图上随机找一个可放置的位置 ph, pw obj.shape[:2] if ph h or pw w: return dst_img, dst_labels nx random.randint(0, w - pw) ny random.randint(0, h - ph) dst_img[ny:ny ph, nx:nx pw] obj dst_labels.append([cls, (nx pw / 2) / w, (ny ph / 2) / h, pw / w, ph / h]) return dst_img, dst_labels逻辑说明函数先从源图的标签中筛选出目标类别的框随机选一个按像素坐标裁剪出目标区域然后在目标图上随机找一个可容纳的位置粘贴并把新位置的归一化坐标追加到标签列表。paste_prob控制整体执行概率对少样本类别调高。参数说明random.randint(0, w - pw)确保粘贴位置不越界dst_img[ny:nyph, nx:nxpw] obj是直接像素赋值速度快但不做边缘融合实际工程中如果发现粘贴痕迹明显可以在边缘加高斯模糊过渡。第二类是小目标过采样策略。把包含小目标的图片在训练数据中重复多轮让模型每个 epoch 看到更多小目标样本。与 Copy-Paste 的区别在于它不做图像拼接只是提高采样频率。具体做法是在数据加载器的sampler层做类别加权小目标图片权重设为2.0其他类别权重为1.0。框架不直接支持时可以复制这些图片的路径到训练列表中效果等价。训练策略上还值得尝试的是分阶段训练先用 640 输入跑 60 轮收敛再用 960 输入在已有权重上微调 20 轮。这种两段式方案对小目标 AP 的提升立竿见影且不会显著增加总训练时间——第一阶段模型已经学会了船体轮廓第二阶段只需要在小分辨率基础上细化特征。验证阶段的技巧也不难把验证集按目标尺寸分成小目标归一化宽度小于 0.1和大目标两组分别计算 AP。如果小目标组的 AP 比大目标组低 30 个百分点就说明增强策略还不到位两者差距缩小到 10 个百分点以内模型在船舶检测上的表现就比较均衡了。全套流程走下来这个数据集的产出物不应该只是一个权重文件还应该有一份数据体检报告、一套可复现的训练配置、以及验证失败案例的截图归档。我会把数据增强参数、训练参数、各类别 AP 记录在训练日志旁边下次遇到相似数据集时直接复用这套参数。走过的弯路越多越明白一份干净的数据比一个花哨的模型更值钱。希望这些经验能帮你在船舶检测这条路上少踩几个坑。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑