简介这是一套面向国际算法算例大赛遥感图像物体检测任务的完整工程包包含Python源代码、文档说明与数据集适合计算机、人工智能、通信等专业的在校生作为毕业设计、课程设计或竞赛练习使用也适用于希望入门目标检测实战的开发者。整个压缩包共405个文件体积约7.55MB其中350个Python脚本构成核心检测与训练逻辑22个YAML文件负责模型与超参数配置22个Markdown文档提供使用说明与项目结构解读另附CSV格式的标注/结果数据、Jupyter Notebook演示以及示例图片便于快速检索、二次开发或用于课程报告。所有源码均经过运行测试答辩评审平均分达到96分整体完成度与参考价值较高也可直接用于项目初期立项演示。目前已有274人下载学习若深入理解其代码逻辑可在此基础上升级网络结构、扩展检测类别从而适配自己的科研或工程场景。1. 遥感图像物体目标检测赛题入门门槛不高跑通容易跑好难遥感图像物体目标检测是国际算法算例大赛里出现频率最高、也最容易让参赛者产生“入门很简单”错觉的赛题。训练集里几百张高分遥感图目标却可能小到只有十几个像素——一架停在机场的飞机、一艘漂在港口的货轮在整张几千乘几千的图像里就是个亮点。用通用目标检测套路直接上去跑十有八九在验证集上拿到一个不算低的 mAP却在小目标类别上全面崩盘。这篇笔记不打算复述某个现成项目的源码而是把这类赛题从零搭基线、调模型、修边界问题的完整路径讲清楚覆盖技术选型、数据准备、训练调参和提交前验证适合准备参赛或正在被遥感目标检测折磨的开发者。2. 赛题与数据集拆解先弄清评测指标和标注格式再谈模型选型2.1 遥感目标与自然图像目标差在哪自然图像里的目标人、车、猫狗通常占据画面较大面积且方向稳定——人不会横着走车不会倒立。而遥感图像是俯视视角目标方向完全任意。一艘船舶在港口里可以朝任意角度停靠一架飞机在停机坪上更是四面八方都有。如果沿用自然图像里“水平锚框”的思路一个水平框往往同时框住了两三个不同朝向的相邻目标特征被互相污染检测头在回归时也会左右为难。另一个显著差异是目标尺度分布。高分遥感图像的原始分辨率往往到几千甚至上万像素目标却集中在几十像素量级。飞机、车辆、储油罐这类小目标在整图里占比极低直接整图缩放到固定尺寸训练目标会缩到几个像素特征完全丢失。这就是为什么遥感检测的通用做法是“切块训练”或设计强多尺度结构而不是像自然图像那样整图缩放。不少人拿通用目标检测模型直接训遥感图发现 loss 降不下去根源就在尺度失配。类别不均衡也是翻车重灾区。遥感场景中背景区域占比极高一片裸地、一块云层、建筑阴影都比目标更“像目标”。分类头很容易学出“宁可信其有”的倾向导致误检框数量爆炸。所以在赛题中置信度阈值、NMS 阈值的调参优先级往往比网络结构本身的调整更高。这些差异最终决定了技术选型的方向需要更强的多尺度能力、需要处理任意方向的目标、需要在大量负样本中维持正样本的纯度。2.2 水平框还是旋转框评测指标说了算赛题评测指标是决定的唯一依据。多数自然图像检测用 IoU 0.5 或 0.5:0.95 下的 mAP遥感赛题中常见两种设定一种是仍然用水平框评测那么水平框检测器就足够另一种是用旋转框评测水平框检测器无论预测多准IoU 天然吃亏。旋转框的标注通常用四端点坐标或“中心点宽高角度”表示四端点格式每行是“类别名 x1 y1 x2 y2 x3 y3 x4 y4”四个点按顺时针或逆时针排列。解析时最容易出问题的是坐标顺序不一致有的数据从左上角开始排有的从任意角开始排这直接决定了旋转 IoU 能不能算对。选型建议分三种情况。评测用水平框、目标密度低直接用常规水平框检测器把精力放在数据增强和调参上性价比最高。评测用水平框、目标密集且方向复杂建议仍然用水平框检测器但数据增强里加随机旋转 90 度的倍数让模型学会不同朝向下目标的特征。评测用旋转框必须上旋转框检测器这是绕不开的。判断方法很简单看大赛提供的评测脚本里调用了什么函数。如果脚本里出现旋转 IoU 的计算逻辑、或者标注文件里每个目标有四个点的坐标那十有八九是旋转框评测。我曾在第一次参加这类赛题时吃过亏——标注给的是四端点我却强行转成水平框训练验证集分数掉了近 5 个点。这属于典型的“没看评测脚本就动手”费了几天训练时间最后推倒重来。所以拿到赛题的第一件事不是急着跑通模型而是把评测脚本读一遍确认评测框类型、IoU 阈值和类别定义。这一步多花半小时后面能省几天。2.3 数据集目录结构和标注格式动手前必须读懂的基础拿到数据集后第一步不是写模型而是把目录结构和标注格式完整摸清楚。这类赛题的数据集常见组织方式如下dataset/ ├── train/ │ ├── images/ # 训练图像 │ └── labels/ # 训练标注 ├── val/ │ ├── images/ │ └── labels/ ├── test/ │ └── images/ ├── class_names.txt # 类别名称列表 └── train_val_split.txt # 划分文件标注格式常见有 VOC XML、COCO JSON 和 TXT 三种。遥感赛题里 TXT 格式最普遍因为旋转框用四个点表达最方便。读 TXT 标注的代码通常长这样import numpy as np def read_dota_label(label_path): 读取四端点格式的旋转框标注 每行: class_name x1 y1 x2 y2 x3 y3 x4 y4 difficult boxes, labels [], [] with open(label_path, r, encodingutf-8) as f: for line in f: parts line.strip().split() if len(parts) 9: continue class_name parts[0] # 四端点共8个坐标值按顺序组成4个点 coords np.array(parts[1:9], dtypenp.float32) # 末尾的difficult标记决定是否参与训练 difficult int(parts[9]) if len(parts) 9 else 0 if difficult: continue boxes.append(coords.reshape(4, 2)) labels.append(class_name) return boxes, labels这段代码做了三件事解析类别名、提取四端点坐标、过滤 difficult 样本。参数说明parts[1:9]取的是从第一个坐标开始的 8 个数值reshape(4, 2)把一维数组变成 4 个二维点坐标。difficult 标记是很多数据集的隐藏坑如果评测时不计入这些样本而训练却把它们的梯度算进去模型的置信度会被带偏。这里有个细节值得注意坐标值直接用原图分辨率不是归一化后的。之后无论切块还是缩放都要同步变换坐标否则标签和图像对不上。我习惯在解析后立刻打印一组样本的坐标和对应图像尺寸做交叉验证确认坐标没有超出图像范围。这个习惯帮我避免过至少三次标注坐标系不一致的问题。目录下还可能附带类别名字典务必以它为准不要自己重排类别 ID——最终提交的预测文件里类别编号错了直接零分。3. 复现基线从环境配置到跑通训练与验证的最小路径3.1 环境依赖与版本匹配conda、CUDA 与算子编译遥感图像的检测框架依赖项比自然图像多一些主要是旋转框相关的计算库。我建议新建独立环境避免把服务器上其他项目的依赖搞乱。常见的做法是用 conda 隔离conda create -n remote_det python3.8 -y conda activate remote_det pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118 pip install mmcv-full openmim mim install mmdet第一行创建 Python 3.8 环境这个版本兼容性最稳主流检测框架都对它有完整支持。PyTorch 的安装索引指定了 CUDA 11.8 版本如果你本机驱动只支持 CUDA 11.3 及以下这里要改成对应版本否则推理时会报驱动不匹配。mmcv-full 是关键依赖很多检测框架的旋转 IoU、自定义算子都依赖它编译如果 pip 下载慢可以用 mim 安装预编译包。安装完成后建议立刻跑一次算子自检python -c import mmcv, mmdet; print(mmcv.__version__, mmdet.__version__)这一步能确认核心依赖装齐没有。常见翻车现场是 mmcv 和 mmdet 版本不匹配训练到第一个 epoch 直接报算子不存在。解决办法是核对框架官方文档中声明的版本对应关系不要盲目追最新版。这个检查看起来像是在记流水账但在比赛这种赶时间的场景里它能拦住一大半的环境类报错。3.2 把标注转成训练管线认识的格式四端点转中心表示拿到数据集后通常需要把标注转换成训练框架支持的格式。以旋转框检测框架为例它一般只认 DOTA 格式的 txt 或 COCO 格式的 json。如果你的数据集恰好是四端点 TXT但框架要求“中心点长宽角度”格式就需要一次转换。转换代码的常见做法如下import math def quad_to_theta(quad): 四端点转 cx cy w h angleangle 单位是弧度 # 取四个点的均值作为中心点 cx quad[:, 0].mean() cy quad[:, 1].mean() # 以第一个点与第二个点的连线计算长边方向角度 dx quad[0, 0] - quad[1, 0] dy quad[0, 1] - quad[1, 1] w math.hypot(dx, dy) h math.hypot(quad[0, 0] - quad[3, 0], quad[0, 1] - quad[3, 1]) angle math.atan2(dy, dx) return cx, cy, w, h, angle逻辑说明四端点旋转框转为带角度的中心表示是模型输入前最常用的预处理步骤。这里用均值求中心点用第一条边长度作宽、第四条边长度作高。角度 atan2 的取值范围在 [-pi, pi]不同框架对角度定义域有不同习惯有的定义域在 [-pi/2, pi/2)有的在 [0, pi)转换后要统一到框架预期范围。这段代码的边界问题值得留意如果四端点的起点不是同一个角比如有的从左上角开始排有的从右上角开始排计算出的角度会偏移 90 度或出现宽高互换。我通常会在转换后随机抽几张图把旋转框画出来人工核对一次方向确认角度定义一致再批量转换。这个“画框验证”的步骤每次都会做比写一百行测试代码都管用。3.3 训练、验证与生成预测文件的三个命令配置文件和数据集结构就绪后训练、验证、推理各有一个核心命令。这些命令在多数检测框架里是通用约定# 训练指定配置和工作目录 python tools/train.py configs/remote_retinanet.py \ --work-dir work_dirs/remote_retinanet # 验证加载训练好的权重跑验证集指标 python tools/test.py configs/remote_retinanet.py \ work_dirs/remote_retinanet/latest.pth \ --out results.pkl # 推理生成提交用的预测文件 python tools/inference.py configs/remote_retinanet.py \ work_dirs/remote_retinanet/latest.pth \ --save-path submission/训练命令里的 config 文件是核心它同时定义数据路径、模型结构、优化器参数和训练策略。--work-dir 决定日志和 checkpoint 的存放位置建议每个实验单独建目录方便后续对比。验证命令的 --out 参数很有用它把每张图每个框的预测结果和置信度序列化成 pkl 文件后续画图分析、调阈值、做集成都要靠它。推理命令则负责把预测结果整理成大赛要求的提交格式通常是一个 txt 或 csv每行一个目标框。这里有个容易被忽略的关联三个命令不是割裂的。验证集上的指标只反映模型状态提交文件则决定最终排名。我一般会在验证通过后手动检查推理脚本输出的内容和验证集指标对得上——比如验证集 mAP 是多少推理脚本输出的框数量是不是同一个量级。如果推理脚本里忘了过滤低置信度框提交文件会膨胀得离谱轻则评测变慢重则直接被判无效。3.4 文档说明里最容易跳过的三处信息源码包里的文档说明不能跳过的地方有三个。第一是类别定义和编号。数据集的类别列表和论文里常用的类别往往不完全一致某些数据会把“船”拆成“大船”“小船”这时需要去读文档里对每个类别的定义或者看标注里实际的框大小分布避免类别语义理解错位。第二是评测脚本的输入输出约定。部分赛题要求预测文件的坐标格式是浮点数有的要求四舍五入到整数有的要求框按置信度从高到低排序有的脚本默认顺序。这些细节全写在文档说明里但很少人逐字读。第三是训练集和验证集的划分方式。有的数据集自带划分文件有的要求参赛者自己划分。如果文档提到划分规则要以它为准不要自己重新随机划分否则验证集指标无法横向对比别人。文档说明里关于提交格式和评测逻辑的部分重要性高于模型结构说明。我曾经跳过了一个关键说明——提交文件里的坐标是相对于切块后的图像坐标不是原图坐标结果所有预测框都画错了位置提交分数接近零。后来养成习惯每次拿到源码包先打开文档搜“评测”“提交”“坐标”把这三处摘抄到笔记里再开始读代码。4. 遥感目标检测避坑指南五个让我翻车的真实问题4.1 小目标大面积漏检注意力全被大目标带走现象验证集 mAP 看着还行但按类别拆分后飞机、车辆这类小目标的 AP 不到 10。原因在于模型的多尺度特征里深度特征图对尺度小于 16×16 像素的目标几乎不敏感而这类目标在遥感图里恰恰是数量最多的。解决最有效的是图像切块训练。把原始大图切成 512×512 或 1024×1024 的 patch目标在 patch 里的相对尺度变大检测器能学到足够细节。切块时要让相邻 patch 有重叠比如重叠 128 像素避免目标正好被切在边界上。同时配合多尺度训练随机在 0.5 到 1.5 倍尺度之间缩放。提示 切块不是越碎越好。patch 越小单张图需要的推理次数越多而且切碎后目标上下文信息丢失误检反而可能增加。512 到 1024 是一个常见平衡区间我通常从 1024 开始试验证集不足再往下降。4.2 旋转框角度回归不稳换了检测器 mAP 反而掉现象把水平框检测器换成旋转框检测器后mAP 不升反降。这在第一次做旋转框时很常见。原因角度回归的损失函数对角度边界非常敏感。旋转框的角度定义域存在周期性0 度和 180 度其实是同一个框但数值上差距巨大。如果不做周期处理模型在边界附近产生巨大梯度训练震荡loss 曲线像锯齿一样上下跳。解决优先选择带角度周期性的损失函数或编码方式比如把角度拆成 sin 和 cos 两个值交给回归头避开角度数值跳变或者用高斯方式对角度做平滑编码。另一个实用技巧是训练初期给角度损失一个较低权重等框的位置和尺寸回归稳定后再逐步放开角度权重。这个课程式训练策略在很多框架的配置文件里都有对应参数值得一试。4.3 背景误检裸地和云层被判成目标现象推理结果里出现大量框落在裸地、云层阴影上置信度还不低。原因是遥感图的背景方差大模型天然容易把纹理丰富的区域当作目标。解决第一步提高置信度阈值从默认的 0.05 提到 0.1 或 0.15误检数量会明显下降。但阈值提高会牺牲召回更本质的方案是增加难负样本挖掘。常见做法是在数据加载时随机生成一些不与真实目标重叠的候选框强制分类头学习“这些不是目标”。此外把背景类显式加入分类器、改用带 Focal Loss 的检测器也能缓解正负样本不平衡。4.4 验证集与测试集分布不一致本地分数高、提交翻车现象本地验证集 mAP 达到 78%提交后官方分数只有 60%。这类赛题里训练集和测试集往往来自不同区域、不同传感器存在明显分布偏移验证集上过拟合得越狠测试集上掉分越重。解决从训练集里划分本地验证集时尽量按区域或按时相划分不要纯随机。更贴近大赛评测的方法是做交叉验证训练三个模型分别在三个划分上评估取平均值估计真实水平。另一个实用技巧是减少训练 epochs。遥感赛题的过拟合拐点通常比自然图像来得早如果验证集分数开始停滞或下降果断 early stop。这个操作我用血泪经验验证过多训十轮不如早停十轮。4.5 显存不足训练中断跑了一夜才发现没存权重现象loss 曲线正常下降但日志在某个 epoch 后戛然而止重启后发现没存 checkpoint。原因一般是显存峰值出现在每轮的最后几次迭代加上日志保存和验证占用的资源叠加恰好超过显存上限。解决训练命令里设置梯度累积将有效批量大小拆成多个小批量执行显存峰值显著下降。同时开启自动保存间隔设置每两个 epoch 存一次权重避免白跑。还有一个常被忽略的细节数据加载线程数不要拉满把加载器的 num_workers 设成 4 到 8 足够过多会导致内存交换间接引发训练中断。5. 从基线到前排数据增强、推理增强和模型集成的组合拳5.1 多尺度训练与测试时增强最稳妥的免费午餐多尺度训练是遥感赛题里性价比最高的增强手段。遥感图像中同一类目标的尺寸跨度极大一个县城里既有占地上千像素的体育场也有十几个像素的汽车。让模型在一个固定尺度下学习很难同时照顾两端。多尺度训练的做法是在每个 epoch 里随机采样一个尺度因子比如从 [0.5, 0.75, 1.0, 1.25, 1.5] 里随机选一个把整批图像缩放到该尺度后送入网络。这样同一个目标在训练过程中以不同尺寸出现多次模型的多尺度特征被充分利用。测试时增强是另一层保险。推理时对同一张图做水平翻转、垂直翻转再把预测结果反变换回原图坐标后合并通常能带来 0.5 到 1.5 个点的稳定提升而且几乎不需要额外训练。TTA 的合并逻辑要注意坐标变换不能把翻转后的框直接叠加需要先还原坐标再做 NMS。import numpy as np def tta_merge(flip_boxes, origin_boxes, img_width, iou_thr0.5): 翻转预测框与原始预测框合并 flip_boxes: 水平翻转图上的预测框 [N,5] x1 y1 x2 y2 score origin_boxes: 原图预测框 flip_boxes flip_boxes.copy() # 水平翻转框坐标映射回原图 flip_boxes[:, [0, 2]] img_width - flip_boxes[:, [2, 0]] all_boxes np.concatenate([origin_boxes, flip_boxes], axis0) # 按置信度排序后做NMS keep [] idx np.argsort(-all_boxes[:, 4]) while idx.size 0: keep.append(idx[0]) iou compute_iou(all_boxes[idx[0]], all_boxes[idx[1:]]) idx idx[1:][iou iou_thr] return all_boxes[keep]这里的关键参数是 img_width翻转后的 x 坐标必须用它还原如果误用了原始图像实际宽度或忘了映射合并结果会有一半框位置错位。TTA 虽然叫“免费午餐”但推理时间成倍增加比赛中如果推理次数受限只在最后几次提交时用 TTA 即可。5.2 图像切块推理与重叠去重大图推理的标准做法训练时用切块推理时同样要切块否则训练和推理的输入分布不一致性能会打折扣。大图直接送进网络有两个问题一是超过模型输入尺寸会被强制缩放小目标信息丢失二是显存可能直接溢出。切块推理的标准做法是用一个滑窗在大图上滑动每块分别推理再把所有块的预测框映射回原图坐标最后做一次全局 NMS 去重。def slide_infer(image, model, patch_size1024, overlap128): 滑窗推理大图 返回原图坐标系下的预测框 h, w image.shape[:2] all_boxes [] stride patch_size - overlap for y in range(0, h, stride): for x in range(0, w, stride): # 裁剪patch并限制边界 y1, y2 y, min(y patch_size, h) x1, x2 x, min(x patch_size, w) patch image[y1:y2, x1:x2] # 模型推理 boxes model(patch) # 坐标偏移回原图 boxes[:, [0, 2]] x1 boxes[:, [1, 3]] y1 all_boxes.append(boxes) all_boxes np.concatenate(all_boxes, axis0) return nms(all_boxes, iou_thr0.5)overlap 是最关键的参数。重叠区域存在的意义是处理目标正好跨在切块边界上的情况如果 overlap 为 0边界目标会被切成两半两个半块各自只能看到残缺目标大概率漏检。overlap 一般设为 patch_size 的八分之一到四分之一。切块的边界坑是同一个目标可能出现在相邻两个 patch 里被重复检测所以最后必须做全局 NMS而不是每个 patch 单独出结果。我在比赛里遇到过一个诡异现象——目标没漏检但全部被重复框了两个就是因为窗口重叠后没有做全局合并。5.3 多模型集成与预测融合从 NMS 到加权框融合单模型调到头之后最直接的提升手段是模型集成。不同初始化、不同 backbone 的模型错误模式往往互补。集成的方式不止投票一种常见的做法是把多个模型的预测框合并后做加权框融合比单纯拼接再做 NMS 保留更多有效框。加权框融合的核心是聚类先把所有模型的预测框按 IoU 分组同一组的框视为对同一个目标的重复预测。def wbf(boxes_list, iou_thr0.55, weightsNone): boxes_list: [model1_boxes, model2_boxes, ...] 每项[N,5] x1 y1 x2 y2 score weights 可为每个模型分配不同权重 if weights is None: weights [1.0] * len(boxes_list) # 合并所有框并按score排序 all_boxes [] for model_boxes, w in zip(boxes_list, weights): model_boxes[:, 4] * w all_boxes.append(model_boxes) all_boxes np.concatenate(all_boxes) all_boxes all_boxes[np.argsort(-all_boxes[:, 4])] # 聚类融合 clusters [] for box in all_boxes: assigned False for c in clusters: if compute_iou(box, c[avg_box]) iou_thr: c[boxes].append(box) c[avg_score] np.mean([b[4] for b in c[boxes]]) assigned True break if not assigned: clusters.append({avg_box: box[:4], boxes: [box], avg_score: box[4]}) # 按平均置信度输出 results [] for c in clusters: results.append(np.concatenate([c[avg_box], [c[avg_score]]])) return np.array(results)wbf 里权重参数的作用是纠正不同模型的置信度偏差。如果模型 A 置信度整体偏高、模型 B 偏低不设权重时 B 的框很难参与聚类融合效果大打折扣。我在实际使用时通常给验证集 mAP 更高的模型分配更高权重并按验证集分数比例归一化。集成的成本是显存和推理时间的成倍增加比赛资源有限的情况下选两个差异最大的模型集成通常性价比最高三个以上的收益递减明显。6. 提交前的最后一小时可视化检查与分尺寸指标分析6.1 批量可视化预测结果训练到最后一版模型后不要急着生成提交文件。先做一件事随机抽验证集 50 张图把真实框和预测框画在同一张图上保存下来人工快速扫一遍。可视化能暴露指标看不出的问题——比如预测框整体偏移、旋转框角度反了、某些类别的框明显偏大或偏小。做可视化的代码量不大核心是用 OpenCV 把框画上去import cv2 def draw_boxes(image, boxes, labels, colors): 把框画到图像上, boxes: [N,4] 或 [N,8] img image.copy() for box, label in zip(boxes, labels): pts box.reshape(-1, 2).astype(np.int32) cv2.polylines(img, [pts], True, colors[label], 2) return img绘制时注意区分旋转框和水平框。旋转框要用 polylines 按四端点连线画闭合四边形水平框直接用 rectangle 画矩形。人工扫图时重点看三类错误小目标是否漏检、大目标是否被重复框出多个框、背景区域是否有成片误检。这三类问题在指标上都能反映但看图才能定位到底发生在哪些场景里。6.2 按尺寸与类别拆分指标可视化之后再做一个量化分析把验证集预测结果按目标尺寸分成小、中、大三档分别计算 AP。这个拆解能让最后半天的优化方向变得清晰而不是凭感觉调参。计算方式是在评测脚本里加入按面积分组的逻辑面积小于 32×32 算小目标32×32 到 96×96 算中目标大于 96×96 算大目标。如果发现小目标 AP 明显低于其他档位优先考虑更激进的切块策略比如把推理 patch 从 1024 降到 512或者对整图做一次放大推理。如果某一类别的 AP 显著低于其他类别针对该类别的数据增强比全局调参更有效比如对含该类别目标的图像做复制粘贴增强。这个“先可视化、再分组量化”的习惯是我在多次比赛里总结出来的最后一步流程。每次走到这一步都能在提交前发现至少一个值得修复的问题有时候是阈值没调好有时候是某个类别被其他类别压制。与其把最后时间花在盲目试参数上不如先用这组检查把问题定位到具体类、具体尺度再对症下药。我个人的教训是不要在验证集 mAP 涨了 0.2 个点时兴奋地直接提交。先把那 50 张可视化图看完把分档指标看完确认提升不是来自某种对测试集无效的过度拟合再按下提交键。希望这篇笔记能帮你在遥感目标检测赛题上少走几步弯路。本文还有配套的精品资源点击获取