ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

DOTA数据集上YOLO旋转框检测训练全流程:标注转换、预训练与调参

DOTA数据集上YOLO旋转框检测训练全流程:标注转换、预训练与调参 简介基于DOTA数据集的YOLO目标检测训练资源提供预训练参数、源代码与配套文档面向计算机、电子信息、数学等专业学生完成课程设计、期末大作业或毕业设计。压缩包内共18个文件包括6个Python脚本、2个YOLO网络配置文件、2个训练启动脚本、类别名称文件、数据定义文件、说明文档及示例图片整体仅517KB轻量却覆盖完整流程。代码内含数据预处理、图像转换、格式转换、模型训练等关键步骤并附运行结果图片便于对照验证整体采用参数化编程关键参数可自行调整注释清晰且已测试通过。预训练参数支持迁移学习可显著减少训练时间文档说明提供使用指引配合脚本可快速构建DOTA数据集的训练环境。资源目前已有1323人学习适合目标检测初学者从零搭建训练流程也可作为算法工程实践与论文复现的参考模板。1. DOTA数据集上的YOLO训练为什么先解决标注转换DOTA是遥感目标检测最常用的数据集之一标注是旋转矩形OBB而经典YOLO使用水平矩形框。直接拿官方YOLO训练DOTA大部分标注会被水平框强行包裹检测头学到的特征和真实物体方向严重错位mAP经常停在个位数。这里遇到的第一道坎不是网络结构而是数据格式DOTA的四个顶点坐标和YOLO需要的归一化四顶点坐标并不等价。我一般会先写一个转换脚本把DOTA标注统一成YOLO-OBB格式然后再考虑用哪一代YOLO、加载哪组预训练参数、设置哪些训练超参数。这篇文章把这一整套流程拆成可直接复现的步骤覆盖标注转换、预训练权重匹配、超参初始化和源代码文档组织适合刚接触遥感目标检测的工程师也适合需要把YOLO跑在旋转框任务上的老手。2. DOTA标注转YOLO-OBB格式格式与脚本2.1 DOTA原始标注与YOLO-OBB标签的对应关系DOTA v1.0/v1.5/v2.0的txt标注每行包含8个浮点坐标和1个类别名坐标是像素级绝对坐标四个点通常按顺时针排列。YOLO的OBB标签也是四顶点但要求把坐标归一化到0到1之间同时类别id从0开始。如果数据集是DOTA v1.0类别顺序固定转换的核心是读取图像尺寸再把每个顶点的x、y分别除以宽和高。这里有一个常见的坑DOTA的16万多个框并不是每个都严格符合矩形约束有些标注在目标被遮挡时会退化成一个接近三角形的四边形。YOLO-OBB检测头内部使用旋转矩形训练时会把输入的多边形强制转成最小外接旋转矩形。所以更稳妥的做法是先对原始四顶点使用OpenCV计算最小外接矩形得到中心坐标和角度再重新生成四个顶点进行归一化。我建议转换前先看一眼原始标签分布避免出现坐标越界或顺序混乱。比如DOTA v1.0的trainval一共有1411张图像每张图像尺寸从800到4000像素不等直接统一缩放到一个固定尺寸会导致小目标严重丢失。后面训练时使用imgsz1024配合rectFalse会好很多但标签转换必须保持原始尺寸的比例。2.2 Python脚本从DOTA txt到YOLO-OBB标签新建dota2yolo.py下面是完整的转换核心函数。这个脚本对原始DOTA坐标做去重、越界裁剪用OpenCV重新生成旋转矩形顶点最后写入YOLO-OBB标签。import os import cv2 import numpy as np def dota_to_yolo_obb(polygon, img_w, img_h): polygon: DOTA标注的8个坐标值 [x1,y1,x2,y2,x3,y3,x4,y4] 返回YOLO-OBB归一化的8个坐标顺序为顺时针。 pts np.array(polygon, dtypenp.float32).reshape(4, 2) # 退化多边形保护顶点数不足时直接用最小外接矩形 if len(pts) 4: raise ValueError(polygon must have 4 points) # 用最小外接矩形确保旋转矩形约束 rect cv2.minAreaRect(pts) box cv2.boxPoints(rect) # 4个顺时针顶点float32 box np.clip(box, 0, [img_w - 1, img_h - 1]) # 归一化 box[:, 0] box[:, 0] / img_w box[:, 1] box[:, 1] / img_h return box.flatten().tolist() def convert_dota_txt(src_label_path, dst_label_path, class_names): class_to_id {name: idx for idx, name in enumerate(class_names)} with open(src_label_path, r) as f: lines f.readlines() img_path lines[0].strip() if lines else # 实际逐行解析时去掉图像路径行这里简化为直接处理数据行 with open(dst_label_path, w) as out: for line in lines[2:]: # 跳过 DOTA 格式的 header parts line.strip().split() if len(parts) 10: continue coords [float(x) for x in parts[:8]] cls_name parts[8] if cls_name not in class_to_id: continue # difficult 字段保留但不参与类别映射 normalized dota_to_yolo_obb(coords, img_w, img_h) out.write(f{class_to_id[cls_name]} .join(f{v:.6f} for v in normalized) \n)代码里最关键的是cv2.minAreaRect的调用。DOTA原始四边形经过这一步会被强制修正为严格的旋转矩形避免YOLOv8-OBB在训练时因为四边形不闭合产生NaN梯度。np.clip将坐标限制在图像范围内防止由于标注越界导致的卷积计算异常。参数说明img_w和img_h必须来自读取图像真实尺寸而不是某个固定值。如果标注是绝对坐标而图像通道里存储的是EXIF旋转后的尺寸会导致整体错位。转换时最好先用cv2.imread或PIL打开一次图像获得原始宽高。2.3 转换后验证用OpenCV画框检查转换后不能直接训练先做一次可视化验证。以下代码读取原图、加载转换后的标签画出所有旋转框。import cv2 import numpy as np def draw_yolo_obb(image_path, label_path, class_names, output_path): img cv2.imread(image_path) h, w img.shape[:2] with open(label_path, r) as f: lines f.readlines() for line in lines: data line.strip().split() if len(data) 9: continue cls_id int(data[0]) pts np.array(data[1:9], dtypenp.float32).reshape(4, 2) pts[:, 0] * w pts[:, 1] * h pts pts.astype(np.int32).reshape(-1, 1, 2) cv2.polylines(img, [pts], True, (0, 255, 0), 2) cv2.putText(img, class_names[cls_id], (pts[0][0][0], pts[0][0][1] - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1) cv2.imwrite(output_path, img)class_names需要和训练时统一我习惯用一个classes.txt保存脚本里读取该文件即可。检查时重点看三类异常坐标框是否超出图像边界、四个顶点是否按顺时针排列、类别id是否越界。如果看到框的位置和原图目标不对应优先排查图像读取顺序和宽高是否弄反。3. 预训练参数选择backbone权重与超参数初始化3.1 为什么遥感任务也要预训练权重DOTA图像和自然图像差异很大但边缘纹理、颜色分布等底层特征仍然共享。从零初始化训练YOLO或只训练backbone在DOTA这种目标密度高、小目标多的数据上通常要100轮以上才接近可用的mAP。而加载在COCO或ImageNet上预训练的权重可以让特征提取器快速进入遥感域的微调状态尤其是前20轮的收敛速度会有明显提升。并不是所有预训练权重都能直接用于OBB任务。普通YOLO检测头的输出是4个水平框参数cx, cy, w, hOBB检测头输出的是5个参数cx, cy, w, h, angle。结构差异导致COCO预训练权重中的检测头参数无法加载。常见做法是只加载backbone部分的权重或者直接使用官方提供的OBB预训练模型。例如Ultralytics就提供过yolov8m-obb这类权重内部结构包含旋转检测头可直接作为起点。3.2 预训练模型与backbone的匹配关系选择预训练模型时模型大小和组织结构要匹配。yolov8n-obb适合算力有限的情况但遥感密集目标下漏检率偏高。yolov8m-obb或yolov8l-obb通常更合适因为DOTA中包含大量小目标模型容量不够时召回率上不去。如果手头没有匹配的OBB预训练权重另一个可靠方案是加载同系列水平检测模型的backbone权重然后通过参数名过滤只替换backbone和neck部分。下面的代码展示了如何用PyTorch手动加载部分权重import torch def load_backbone_weights(model, weights_path, devicecpu): ckpt torch.load(weights_path, map_locationdevice) # Ultralytics 权重中 model 字段包含完整状态字典 state_dict ckpt[model].state_dict() # 剔除检测头相关的键 filter_keys [ head.cv2, head.m, head.cv3, head.dfl, head.conv_s, head.distrib ] new_state_dict {} for k, v in state_dict.items(): if any(f in k for f in filter_keys): continue new_state_dict[k] v # 只加载匹配的参数 model_dict model.state_dict() matched {k: v for k, v in new_state_dict.items() if k in model_dict and model_dict[k].shape v.shape} model_dict.update(matched) model.load_state_dict(model_dict) print(fLoaded {len(matched)}/{len(model_dict)} param tensors) return model参数说明filter_keys要根据实际模型结构调整不同YOLO版本检测头名称不一样。这个函数的作用是防止形状不匹配导致加载崩溃同时也不会误加载检测头参数。加载完成后必须打印len(matched)如果数字远小于模型总层数说明backbone的键名可能不兼容。3.3 针对DOTA的超参初始化清单训练DOTA不能照搬COCO的默认超参数。遥感图像的物体方向多变、尺度跨度大需要更少的平移增强、更多的尺度抖动和关闭垂直翻转。下面的表格列出了我常用的初始化配置基于Ultralytics风格的超参数。参数名COCO默认值DOTA推荐值理由imgsz6401024DOTA小目标多输入分辨率不够直接丢失batch168分辨率升高后显存占用增大需调低epochs300300遥感任务收敛慢200以下效果不稳定lr00.010.005预训练模型已经收敛学习率过高会破坏特征lrf0.010.01保持余弦退火到低位warmup_epochs35遥感图像尺寸大梯度更新波动大需要更长热身weight_decay0.00050.0005默认即可degrees0.010轻微旋转增强遥感图像方向随机有助于旋转泛化translate0.10.1保留默认scale0.50.9遥感目标尺度变化大需要更多尺度抖动shear0.00.0旋转框任务不能加shear会破坏形状约束fliplr0.50.5水平翻转遥感图像语义不受影响flipud0.00.0垂直翻转会颠倒物体朝向建议关闭mosaic1.01.0增强小目标上下文保持开启mixup0.00.0遥感目标边界清晰mixup反而会模糊边缘表中degrees是随机旋转角度我设置10度而不是更大是因为DOTA图像存在大量垂直方向的道路和建筑过大的旋转增强会让网络对方向混淆。imgsz1024是显存和精度之间的平衡点如果显存充足可以提高到1280但训练时间会翻倍。4. YOLO训练DOTA的完整实现命令、参数与排错4.1 数据组织与yaml配置按YOLOv8-OBB的约定数据集目录结构需要分成images和labels两个分支各自包含train和val子目录。datasets/dota/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── dota.yamldota.yaml里必须指定angle_class吗这里要注意Ultralytics的OBB训练不需要显式声明角度分类框的角度是从四顶点坐标推断的。YAML只需要定义path、train、val和names。# dota.yaml path: /path/to/datasets/dota # 数据集根目录 train: images/train # 相对路径 val: images/val names: 0: plane 1: ship 2: storage-tank 3: baseball-diamond # 按DOTA v1.0 category顺序写全这里省略保存为dota.yaml注意names索引必须与转换脚本里的class_to_id一致否则会训练错目标。检查方式很直接从labels里随机挑一个文件查看第一列数字是否在0到总数-1之间。4.2 训练命令与命令行参数使用Ultralytics接口训练最简洁的命令如下yolo detect train modelyolov8m-obb.pt \ datadota.yaml \ imgsz1024 \ batch8 \ epochs300 \ lr00.005 \ warmup_epochs5 \ scale0.9 \ degrees10 \ flipud0.0 \ projectruns/dota \ nameyolov8m_obb_dota命令里的modelyolov8m-obb.pt是预训练权重会自动下载对应的模型结构并加载参数。datadota.yaml指定数据集配置。project和name控制输出目录所有日志和权重都保存在runs/dota/yolov8m_obb_dota下。参数说明scale0.9表示训练时数据增强会对图像进行0.1到0.9倍范围的随机缩放这个值偏离默认0.5是因为遥感目标在1024分辨率下仍然很小需要更多放大。flipud0.0显式关闭垂直翻转。degrees10允许随机旋转注意这个值不会影响OBB标签的坐标点因为增强会同步旋转标签。4.3 训练日志里要盯的三个指标训练过程中需要持续观察终端或TensorBoard里的指标而不是等300轮跑完再看结果。第一是box_loss和cls_loss正常情况应稳定下降如果出现震荡上升大概率是学习率过大或预训练权重加载失败。第二是mAP50-95OBB任务里这个指标和水平框任务的差别在于IoU计算使用旋转框面积建议每10轮记录一次。第三是val_combined或vflip变化如果验证损失在第50轮后不再下降基本可以提前停止。使用Ultralytics命令训练时日志会在每个epoch结束后打印一行关键指标包括训练损失、验证损失、mAP50-95等。我建议用下面的参数直接开启验证集评估yolo detect train ... \ valTrue \ save_period10valTrue让每个epoch结束在验证集上跑一遍save_period10每10轮保存一次权重。这样即使训练中途崩溃也不会丢失太多中间结果。4.4 DOTA训练中常见报错与处理报错现象根本原因处理方式CUDA out of memorybatch或imgsz过大降低batch到4或把imgsz改为768Label shape (4,) doesnt match标签文件里坐标数量不是8检查转换脚本是否遗漏顶点归一化anchors相关警告输入图像分辨率固定anchor自适应调整失败增加autoanchorFalse手动设置anchor训练正常但mAP为0类别id映射错误或标签坐标全为0用2.3节的画框脚本检查训练集标签验证时No labels foundval目录下没有对应的txt标签确认labels/val和images/val目录名匹配表格里的问题我都遇到过。其中No labels found最常见的原因是把标签文件名写成了原文件名加.txt但YOLO要求标签名必须与图像一致。比如P0001.png对应P0001.txt不能多任何后缀。5. 源代码配套文档的整理技巧与验证方法5.1 给训练配置做一份可复现的参数表源代码里如果只有train.py和weights其他人很难复现。我一般会在仓库根目录放一个experiment.yaml记录每次有效运行的全部参数。这里给一个可复现的参数表模板# experiment.yaml experiment_name: yolov8m_obb_dota_1024 dataset: name: DOTA-v1.0 train_images: 1411 val_images: 458 classes: 15 preprocessing: label_format: YOLO-OBB normalization: minAreaRect imgsz: 1024 model: architecture: YOLOv8-m head: OBB pretrained: yolov8m-obb.pt training: optimizer: SGD lr0: 0.005 lrf: 0.01 warmup_epochs: 5 weight_decay: 0.0005 epoch: 300 batch: 8 augmentation: degrees: 10 scale: 0.9 fliplr: 0.5 flipud: 0.0 mosaic: 1.0 mixup: 0.0有了这个文件任何人执行yolo train datadota.yaml时都能把超参与实验记录对齐。这里要特别说明预训练权重文件名必须精确到具体版本yolov8m-obb.pt不同批次可能在backbone权重上有微小差异记录文件名比只写使用预训练模型可靠得多。5.2 用可视化验证训练效果训练完成后只打印mAP不能证明模型真的学会了方向。还需要可视化预测框和实际标注框的位置关系。下面的代码对单张图像做预测并叠加GT框from ultralytics import YOLO model YOLO(runs/dota/yolov8m_obb_dota/weights/best.pt) results model.predict(datasets/dota/images/val/P0001.png, conf0.25, save_txtTrue, save_confTrue, saveTrue)save_txtTrue会把预测结果保存到runs/detect目录格式是class_id x1 y1 x2 y2 x3 y3 x4 y4 conf。和GT标签对照时可以写一个小的匹配脚本重点看旋转框角度是否一致。如果预测框角度偏差超过15度说明训练数据中的方向分布没有被网络捕获这时应检查是否意外启用了垂直翻转。5.3 把转换、训练、评估串成一个README最小示例源代码配套文档的核心是一份可执行的README而不是大段原理。我的最小示例包含三块命令# 1. 转换DOTA标注 python dota2yolo.py --source datasets/dota/trainval \ --output datasets/dota/labels # 2. 训练 yolo detect train modelyolov8m-obb.pt datadota.yaml \ imgsz1024 batch8 epochs300 degrees10 flipud0.0 # 3. 评估 yolo detect val modelruns/dota/yolov8m_obb_dota/weights/best.pt \ datadota.yaml imgsz1024在README里用表格列出每个脚本的入口参数并给出一个已知的训练时长说明比如单卡RTX 3090训练300轮大约需要36小时。这样阅读者可以判断自己的算力是否适合直接复刻。如果发现验证集mAP比训练集低8个百分点以上再回到第5.2节做误差分析而不是盲目调大模型或在预训练参数的坑里打转。本文还有配套的精品资源点击获取
返回列表