
简介基于DOTA数据集的YOLO训练资源包面向目标检测与计算机视觉方向的课程设计、期末大作业及毕业设计。整套资源涵盖Python源码、网络配置文件、数据映射文件、训练脚本及说明文档共18个文件压缩包约517KB包括YOLO数据转换、图片切分、测试等脚本以及DOTA数据集的类别与数据配置便于直接复现和二次开发。资源代码思路清晰、注释详细并已通过运行测试内置预训练参数与示例输出能帮助理解遥感旋转目标检测的训练流程。已有1323人学习下载适合具备一定深度学习基础的学生或算法入门者参考。1. 为什么航拍目标检测绕不开 DOTA 与 YOLO 的组合做航拍图像目标检测的人几乎都躲不开 DOTA 数据集。它是目前航拍场景下最常用的基准之一图像分辨率高、目标尺度差异极大小到车辆大到港口而且目标排列密集且带有任意朝向。直接用普通水平框的检测器去套很容易出现两个问题框内混入大量背景导致分类置信度虚高密集场景下 NMS 抑制掉本该保留的相邻目标。很多人第一反应是上旋转框检测器但工程上如果只是做落地部署、数据标注是水平框YOLO 系列依然是最省成本的选择。这套资源就是用 YOLOv3 在 DOTA 上完整跑通训练、预训练加载、验证的全流程并附带了源代码、cfg 配置、训练脚本以及文档说明适合计算机视觉方向的课程设计、毕业设计也适合刚接触航拍检测的工程师用来做基线。它能让你跳过大段时间消耗在环境配置和数据格式转换上的过程直接看到从 DOTA 原始图像到 YOLO 可训练数据再到模型权重这一条链路是怎么走通的。2. DOTA 数据集的齿轮分块切割与旋转框拆分DOTA 原始图像通常是几千乘几千像素的大图直接塞进 YOLO 训练不仅显存放不下而且小目标会被严重压缩。预处理阶段的关键手段是切图split同时把旋转框标注转换成 YOLO 所需的水平框格式。2.1 DOTA 原始标注与 YOLO 标注的歧义DOTA 数据集提供的是四个顶点坐标表示一个任意方向的四边形。而 YOLOv3 只认cx, cy, w, h形式的水平边界框。常见做法是取四点的外接矩形但要注意如果目标本身很长并倾斜外接矩形的面积会远大于目标实际面积导致正样本中背景比例剧增。更稳妥的做法是先按角度聚类分析如果目标角度分布集中比如车辆多沿道路方向可以考虑保留角度分支否则就用外接矩形做近似。# 四顶点坐标 (x1,y1,x2,y2,x3,y3,x4,y4) 转外接矩形 xs [x1, x2, x3, x4] ys [y1, y2, y3, y4] cx (min(xs) max(xs)) / 2 cy (min(ys) max(ys)) / 2 w max(xs) - min(xs) h max(ys) - min(ys)上面的 Python 代码给出了最简单的转换逻辑直接取四顶点中最小的和最大的坐标值组成矩形。转换后需要注意归一化在切图时每个子图的尺寸是固定的坐标需要除以子图宽高而不是原始大图的宽高。这是最容易出错的地方很多初学者的 mAP 突然变成 0原因就是归一化分母用错了。2.2 split.py 的切图策略与重叠参数源码中的split.py负责把大图切成小图同时把标注文件跟着切。切图窗口大小一般选 1024×1024 或 608×608和训练输入尺寸匹配。重叠率overlap建议设为 0.2 到 0.5 之间。为什么不设 0因为航拍目标可能正好横跨切图边界完全切掉就丢失了一个有效样本。重叠率高会让相邻子图共享很多目标相当于隐式提升了这些目标的采样频率但也增加了冗余推理量。训练阶段用 0.25 的重叠率比较均衡。python split.py --src_dir ../DOTA/images --label_dir ../DOTA/labels \ --out_dir ../DOTA_yolo/images --out_label_dir ../DOTA_yolo/labels \ --size 1024 --overlap 0.25上面命令把原始图像和标注文件夹同时处理--size决定子图边长--overlap控制相邻图重叠比例。切图后还需要过滤掉目标过小或完全在边界之外的标注否则 YOLO 会大量预测出面积只有几个像素的噪声框拖慢收敛速度。一般把宽高小于 10 像素的目标直接丢弃保留大于阈值的。# split.py 中的过滤逻辑示意 if w 10 or h 10: continue这里的 10 像素是经验值如果你的验证集里小目标占比高可以直接降到 4 或 5但需要接受更多误检的代价。过滤逻辑不要放在切图之前因为切图之后原本的大目标可能被切成小目标这个“小”和原图的小不是一个概念。2.3 imagetrans.py 与数据增强不只是翻转源码里的imagetrans.py实现了图像变换包括随机翻转、90 度旋转和马赛克增强。你可能会疑惑DOTA 是航拍图旋转 90 度不会改变目标语义吗实际上航拍图无所谓绝对方向旋转后目标类别不变模型学到的是特征本身而不是相机朝向。所以旋转增强对 DOTA 非常有效能显著提升模型的泛化能力。# 90度旋转增强示例 def rotate_image_90(image, boxes): h, w image.shape[:2] new_image cv2.rotate(image, cv2.ROTATE_90_CLOCKWISE) new_boxes [] for box in boxes: x, y, bw, bh box # 坐标旋转映射 new_x (h - y - bh) / h new_y x / w new_w bh / h new_h bw / w new_boxes.append([new_x, new_y, new_w, new_h]) return new_image, new_boxes注意上面的归一化坐标计算第一行new_x用了h - y - bh这是在坐标已经归一化到 [0,1] 的前提下对 x 方向反转。这组公式很容易写错错误的结果是边界框全部偏到左上角或右下角。如果不想折腾旋转后坐标映射就直接采用左右翻转和上下翻转这两种操作对 YOLO 的格式最友好只需要把x变成1 - x - wy保持不变。航拍数据集的增强优先级建议是随机翻转 亮度对比度扰动 马赛克 旋转。马赛克增强能显著丰富背景但要注意 DOTA 图像本身已经有很多小目标马赛克四图拼接后目标会更小适当降低增强概率。3. cfg 配置对 DOTA 场景的针对性调整YOLOv3 的 cfg 文件dota-yolov3-416.cfg和dota-yolov3-tiny.cfg是整个训练的灵魂。416 表示输入尺寸tiny 是为了在低算力设备上跑。很多人直接下载一个通用 yolov3.cfg 就去训练 DOTA结果精度不尽如人意其实原因都出在 anchor 尺寸和数据加载策略上。3.1 anchor 重聚类的必要性与方法DOTA 的目标尺寸分布和 VOC、COCO 截然不同。COCO 上小目标多anchor 从 10×10 到 60×60 不等而 DOTA 中操场、桥梁这类目标动辄几百上千像素同时车辆等小目标又可能只有十几个像素。直接用默认 anchor 会让大目标的初始回归很难小目标又被平均给拖累。常见做法是使用 k-means 在切图后的标注框上重新聚类 anchor。# 使用 darknet 自带的 k-means 工具或使用源码中的脚本 python kmeans_anchor.py --label_dir ../DOTA_yolo/labels --num_clusters 9k-means 的距离度量是 IoU 的补数d(box, centroid) 1 - IoU(box, centroid)目的是让 anchor 和真实框的 IoU 尽可能大。聚类完成后把 9 组宽高填到三个 yolo 层的anchors 字段里。如果你用的是 tiny 版本则只需 6 组。还要注意按面积排序大的 anchor 放到最深的 yolo 层。3.2 修改 classes、filters 与 max_batchesDOTA 原始数据集有 15 个类别飞机、轮船、储罐、棒球场、网球场、篮球场、地面跑道、港口、桥梁、大型车辆、小型车辆、直升机、环形交叉口、足球场、游泳池。资源中的dota.names应该也是 15 个所以每个 yolo 层前面的卷积filters必须改成(15 5) * 3 60否则训练时维度不匹配。classes15在 yolo 层也要同步修改。参数默认值DOTA 建议值说明batch6416~32航拍图目标密集batch 太大容易显存溢出subdivisions18~16实际送入显存的 batch 是 batch/subdivisionswidth / height416608 或 416608 对小目标更友好但训练时间增加约 1 倍max_batches5020060000DOTA 类多训练更久更稳steps40000,4500048000,54000学习率衰减点提前会导致后期收敛不足learning_rate0.0010.001加载预训练权重后可保持不变上面是参数对比表核心原则如果显存不够先增大subdivisions不要减小width/height减少 batch 会导致 BN 统计量抖动较大配合小幅提高learning_rate到 0.002 可能更好。但稳妥的做法是 batch32、subdivisions8对大多数 11GB 显存卡适用。steps一般设为max_batches * 0.8和0.9在这个项目里也就是 48000 和 54000。3.3 dota.data 与数据路径配置dota.data文件指向train.txt、valid.txt和dota.names。路径最好写成绝对路径否则训练时经常报Couldnt open file。别为了省事写相对路径因为你可能在不同目录下启动训练命令相对路径会瞬间失效。classes15 train/home/user/DOTA_yolo/train.txt valid/home/user/DOTA_yolo/valid.txt names/home/user/DOTA_yolo/dota.names backup/home/user/DOTA_yolo/backupbackup目录要手动创建darknet 不会自动新建。训练中断时backup里的.weights文件就是你恢复的救命稻草。train.txt的内容格式是每行一张切好的子图图片绝对路径路径后缀建议 .jpg与图片实际格式保持一致。4. 预训练参数加载与训练脚本的确定性这个资源的关键词里有“预训练的参数”指的就是 darknet 在 ImageNet 上预训练的主干网络权重。YOLOv3 用darknet53.conv.74tiny 版本用yolov3-tiny.conv.15。加载预训练权重后前 50 层主干被初始化得比较好目标检测层是随机初始化的因此训练初期 loss 会快速下降但仍有较大波动这属于正常现象。4.1 train.sh 脚本解读与启动顺序项目中给出了train.sh标准运行逻辑是先加载预训练再断点续训。下面是类似的标准命令#!/bin/bash ./darknet detector train dota.data dota-yolov3-416.cfg \ darknet53.conv.74 -gpus 0,1 2train.log使用两张卡时cfg 里batch会被自动按卡数切分实际每张卡的 batch 等于 batch/GPU 数所以如果之前设 batch64双卡则变成 32。这会影响 BN 的统计量必要时要手动调整 batch。2train.log是把日志重定向到文件方便训练后排查 loss 曲线。如果只是想测试环境先加载预训练跑 100 步看 loss 是否下降是最快的校验方式。./darknet detector train dota.data dota-yolov3-416.cfg darknet53.conv.74 -gpus 0 -dont_show -map-map参数会在训练过程中定期计算 mAP输出每条验证集的结果。默认每 4 epoch 计算一次如果你想更频繁需要在dota.data里加一行eval_period2。这个参数对调参非常有用能让你及时看到哪个 anchor 尺寸匹配度低。4.2 断点续训auto-resume-training.sh 的逻辑项目里的auto-resume-training.sh脚本做的事情很简单但很实用查找backup目录下最新的.weights文件然后接着训练。由于训练意外中断后你手动找最新权重很容易找错比如把 30000 步的权重当成 15000 步的加载导致学习率重新计算精度反而下降。# auto-resume-training.sh 核心逻辑 latest_weight$(ls -t backup/*.weights | head -1) echo Resume from $latest_weight ./darknet detector train dota.data dota-yolov3-416.cfg $latest_weight -gpus 0这里要特别说明用backup里的权重恢复训练和学习率衰减的步数统计是独立的。darknet 会从权重文件名里恢复已经迭代的次数因此只有确保文件名包含_step标记的权重比如backup/dota-yolov3-416_50000.weights才会自动接续学习率。如果你改过 cfg 里的max_batches那么恢复训练时会以新参数为准但已走的步数不会回退此时如果步数已经超过新的steps阈值学习率会被压低到最低档这是一个没人提醒过的隐坑。4.3 训练日志与 loss 判定训练期间 loss 数值前 100 步会从 20 以上快速降到 5 以内之后下降缓慢。航拍目标密集的 DOTA 上loss 很难降到低于 1.0因为小目标回归难度大。更好的观测指标是avg Recalldarknet 在每批量结束后会打印avg Recall当模型开始对目标有稳定预测时这个值会从 0 开始上升到 0.3 以上。v3 (iou loss, Normalizer: (iou: 0.07, obj: 1.00, cls: 1.00) Region 82 Avg IOU: 0.50, Class: 0.85, Obj: 0.70, No Obj: 0.01, .5R: 0.70, .75R: 0.42, count: 17看上面日志.5R表示 IoU 阈值 0.5 的召回率count 表示这批图中匹配到的目标数。如果count一直很小小于 10而你的训练集里每个子图差不多有几十个目标那大概率是坐标归一化错误或ignore_thresh设置太高导致大部分目标没有匹配到任何 anchor。此时需要检查data_transform后的 label 文件是否和图片对应打印其中一个 label 文件即可判断。5. 验证、可视化与小目标性能优化技巧训练完的权重文件在backup目录下直接用test.py跑验证集会输出标注好的检测图。output.jpg和test.png应该是项目附带的结果样例说明作者已经跑通过一次完整流程。5.1 test.py 的推理调用与阈值调整test.py的调用方式和 darknet 的 C API 绑定在一起核心部分通常是加载网络、设置阈值、跑 forward。下面是伪代码骨架# test.py 中的推理逻辑 from darknet import load_network, detect_image net, class_names, colors load_network( dota-yolov3.cfg, dota.names, backup/dota-yolov3_final.weights ) detections detect_image(net, class_names, test.png, thresh0.35) for label, confidence, bbox in detections: x, y, w, h bbox print(f{label}: {confidence:.3f} at ({x:.1f}, {y:.1f}) {w:.1f}x{h:.1f})thresh参数在可视化时设为 0.35 比较合理因为训练结束的模型在验证集上往往偏向保守置信度偏低。如果实测大量真实目标落在 0.3 以下说明训练尚不充分或者 anchor 与目标尺寸匹配不佳此时盲调阈值没有意义。如果你需要高精度的推理结果建议对同一图片跑两次不同输入尺度416 和 608然后在原图上做 NMS 合并能有效提升小目标检测率。5.2 针对 DOTA 小目标的最后优化手段如果你用这套流程训练后 mAP 不理想最优先检查的不是网络结构而是预处理DOTA 原图切 1024 后输入到 416 训练小目标小于 32 像素占比过大时应该直接改用 608 输入。其次修改ignore_thresh从默认 0.7 降到 0.5让 IoU 低于 0.5 的预测框也被视为正样本参与损失计算注意这会略微增加训练噪声但能提升小目标的召回。最后对大目标类别如港口、桥梁的样本进行过采样或者在dota.data里额外加一份仅包含大目标的训练列表让模型多看几轮。序列密度上不要盲目调整 NMS 阈值先观察测试图上同一目标是否产生大量重复框如果是再去调nms_iou_thresh到 0.4。5.3 一个快速验证训练是否正常的小技巧训练第 100 步时把backup里刚保存的权重拿出来跑test.png如果输出图上能在目标位置看到高亮但低置信度的框说明梯度传导已经激活。如果完全没有任何框连空白区域都没反应检查dota.names里的类别数量是否和 cfg 里一致不一致会导致检测层输出维度错位这类错误在 darknet 的日志里不会直接报错只会有莫名其妙的 NaN loss。还有一个容易忽视的点训练时如果开了random1每个 batch 会随机改变输入尺度这会让预训练权重的感受野被打乱建议在 DOTA 这种目标尺度跨度极大的数据集上保持random1但前 1000 步可以临时设为 0等模型稳定后再改回来。本文还有配套的精品资源点击获取