☰
YOLOv5 OBB旋转框训练实战:从DOTA数据准备到调参避坑全流程
2026/10/4 23:59:52 网站建设 项目流程

简介:一套面向旋转目标检测场景的YOLOv5 OBB训练DEMO,适合需要识别树木、车辆、遥感地物等倾斜对象的深度学习开发者,可帮助快速理解OBB标注格式、角度回归与训练范式。资源包共583个文件、约445.58MB,数据侧包含166个XML标注文件、95个TXT标签文件和93张PNG图片等,代码侧涵盖Python脚本、YAML配置、预训练权重、ONNX模型、C++/CUDA后处理源码,并带有训练日志、Jupyter示例与Dockerfile,基本覆盖旋转框检测从数据准备到模型推理的完整工具链。已有1814人学习下载;包内提供了可直接运行的训练、预测、导出脚本,也给出了OBB标签和配置文件示例,便于对照理解旋转框回归、角度损失等关键设计。整体上是一份适合入门到进阶实践的旋转框检测参考项目,尤其适合遥感解译、无人机巡检、工业质检等场景,可用于快速搭建实验环境、二次开发并迁移到自有数据集。

1. yolo v5 OBB 旋转框训练 demo:解决什么问题,适合谁

无人机航拍里一架飞机在停机坪上任一角度停着,车辆检测里集装箱斜着摆放,遥感图里存储罐排成斜线——这些目标如果还用水平矩形框,一个框里经常塞进另一辆车或一片背景,mAP 被噪声拉低,NMS 也会因为重叠区域过大而误删目标。YOLOv5 OBB 旋转框就是在水平框的 4 个参数上多回归一个角度,用 5 参数格式或者 4 顶点格式描述一个“带方向的矩形”,让网络学会贴着目标本身的走向输出检测框。本文围绕“yolo v5 OBB 旋转框训练 demo”这条完整链路展开:先用 DOTA 格式准备数据,再写一套转换脚本,最后把训练命令、必调参数和那些容易翻车的细节串起来。适合已经会跑普通 YOLOv5、手头有倾斜目标数据、想尽快看到旋转框效果的从业者,也适合准备把检测模型从水平框升级到 OBB 的团队做技术预研。

2. 旋转框数据从哪来:DOTA 与本地标注格式对齐

2.1 旋转框标注和水平框的差异:8 个坐标 vs 4 个坐标

水平框只要记录左上角和右下角两个点,即 x1, y1, x2, y2,目标旋转后框内会混入大量背景。旋转框则用带角度的四边形包住目标,常见的存储方式有两种:一种是中心点加尺寸加角度,即 cx, cy, w, h, angle,适合回归头直接输出;另一种是四个顶点的 8 个坐标,x1, y1, x2, y2, x3, y3, x4, y4,适合精细标注。社区里训练 YOLOv5 OBB 时,更常见的是把标签统一成“类别 id + 归一化后的 8 个顶点坐标”,这样数据加载逻辑可以直接复用水平框那套,只要把解码和 NMS 换成旋转框版本即可。

DOTA 是目前最常被提及的遥感旋转框基准数据集,原始标签里每一行是一个目标的 8 个坐标、类别名和 difficult 标志。它的坐标是像素级绝对值,类别名是字符串,比如“plane”“ship”“storage-tank”。训练 demo 的第一步就是把这套像素坐标归一化到图像尺寸,并把类别映射成从 0 开始的数字 id。如果你手上有自己的业务数据,格式转换的原则同样一致:先统一顶点顺序,再归一化,最后确认类别表顺序,三件事缺一不可。

2.2 用 roLabelImg 或 X-AnyLabeling 标注自己的旋转框数据

没有现成 DOTA 数据时,需要自己画旋转框。老牌工具 roLabelImg 适合小批量标注,界面和传统 LabelImg 类似,画框时右键选择 create rotated box,按四个点画出四边形,缺点是快捷键和扩展性一般。更推荐 X-AnyLabeling,它支持旋转框标注、自动保存、类别导入,导出格式可以选择 DOTA 或者 VOC 变体,上手成本低。

标注完自己的数据后,建议先导出一份和 DOTA 一致的文本格式,再统一走转换脚本,避免在不同工具之间反复倒格式。因为后面训练脚本读取的是“类别 id + 归一化坐标”的 txt 文件,所以无论用哪个工具,最终都要落成这套格式。标注时还要格外注意顶点顺序:同一个实现里,四个顶点的顺序一旦不一致,计算多边形面积和 IoU 时会得到完全错误的数值。最常见约定是顺时针或逆时针,你只需要保证转换脚本全统一成一种顺序即可。

2.3 把 DOTA 转成 yolov5_obb 需要的格式:转换脚本与四个边界坑

下面这段脚本是过去训练航拍数据时保留下来的最小可运行版本,作用是把 DOTA 风格的原始标签转成训练用的归一化标签。这里以类别为 ship、airplane、storage_tank 三类为例,你的数据按实际类别替换。

import os import glob from PIL import Image src_txt = "labelTxt/train" # 原始 DOTA 标签目录 out_txt = "labels/train" # 转换后标签目录 class_names = ["ship", "airplane", "storage_tank"] os.makedirs(out_txt, exist_ok=True) for txt_path in glob.glob(os.path.join(src_txt, "*.txt")): img_path = txt_path.replace("labelTxt", "images").replace(".txt", ".png") if not os.path.exists(img_path): img_path = img_path.replace(".png", ".jpg") width, height = Image.open(img_path).size out_lines = [] with open(txt_path, "r", encoding="utf-8") as f: for line in f: parts = line.strip().split() if len(parts) < 9: continue x1, y1, x2, y2, x3, y3, x4, y4 = map(float, parts[:8]) cls_name = parts[8] if cls_name not in class_names: continue coords = [ x1 / width, y1 / height, x2 / width, y2 / height, x3 / width, y3 / height, x4 / width, y4 / height, ] cls_id = class_names.index(cls_name) out_lines.append( str(cls_id) + " " + " ".join(f"{c:.6f}" for c in coords) ) out_path = os.path.join(out_txt, os.path.basename(txt_path)) with open(out_path, "w", encoding="utf-8") as f: f.write("\n".join(out_lines))

这段脚本的逻辑很直接:遍历每个标签文件,读取同名图片的宽高,把像素坐标除以宽高完成归一化,再把类别名替换成 class_names 列表中的索引。写脚本一定要先确认 class_names 的顺序,因为后面 data.yaml 里的 names 要和这个列表严格一致。如果原数据里类别名和列表不匹配,这一行会被过滤掉,日志上看不出来,最后模型只会对其中一部分类别有响应。

脚本里有一个隐藏很深的坑:DOTA 图片可能是 png 或 jpg,直接 replace 后缀会失败,所以代码里做了二次替换兜底。另一个坑是 DOTA 原始标签最后还有一个 difficult 字段,脚本只取了前 8 个坐标和类别,紧凑但实用。如果你的数据里存在跨切图边界的旋转框,切图后必须重新截取标签,不能直接拿大图标签硬训,否则会出现大量标签坐标超出小图范围的情况,训练时损失直接炸。

3. 跑通训练 demo:环境、配置与最小命令

3.1 环境准备:clone 合适分支与依赖安装

普通 YOLOv5 官方仓库并不支持角度回归,需要的是社区维护的 yolov5_obb 分支。这类分支通常是在 YOLOv5 基础上新增了角度输出头、旋转框解码和旋转 NMS。环境建议 Python 3.8 到 3.10,PyTorch 1.13 或 2.x,CUDA 对应版本即可。先把代码拉到你信任的一个 fork,然后安装依赖,常见做法是:

git clone <你的yolov5_obb仓库地址> cd yolov5_obb pip install -r requirements.txt

装完依赖后,建议先跑一个不带权重的空 forward 脚本确认所有自定义算子编译通过。由于旋转框分支里普遍包含 C++/CUDA 算子,比如旋转 NMS 或 KLD 损失里的自定义函数,很多分支第一次运行时占用编译几分钟,日志会输出一堆 warning。只要最终没有报“undefined symbol”之类的错误,就说明环境可以继续。

如果你是新手,建议直接用已有预训练权重初始化,而不是从零训练。常见做法是拿官方 YOLOv5s 的水平框 COCO 权重做 backbone 初始化,角度输出头随机初始化。加载权重时会出现部分键名(比如 angle 分支的参数)不匹配的提示,这是正常现象。

3.2 data.yaml 怎么写:类别、路径与角度参数

旋转框训练的 data.yaml 和水平框结构类似,但多了角度相关配置。下面是一个经过实测的配置样例:

train: datasets/dota/train/images val: datasets/dota/val/images nc: 3 names: ['ship', 'airplane', 'storage_tank'] angle_deg: 180 kld: True

train 和 val 指向存放图片的目录,加载器会自动在相邻的 labels 目录里查找同名 txt。nc 必须和 names 长度一致,否则训练会在第一次迭代时报错。angle_deg 是角度表示范围,180 表示角度值落在 [-90, 0) 这类半开区间,360 表示全角度范围。不同实现定义不同,建议以你拉取的分支文档为准。kld 表示是否使用 Kullback-Leibler Divergence 作为角度回归损失的一部分,旋转框训练中 KLD 对角度周期性有更好的容忍度,建议保持 True。

有一点要警惕:data.yaml 的 names 顺序必须和转换脚本里的 class_names 顺序完全一致。这里错位最常见的表现是 loss 掉得很正常,但检出来的目标类别张冠李戴,甚至 mAP 一直在个位数徘徊。我一般会在 data.yaml 旁边放一个 classes.txt,转换脚本和配置都从同一个文件读取,从源头避免顺序漂移。

3.3 最小训练命令与参数含义

环境验证通过、配置写好之后,训练命令和普通 YOLOv5 差别不大,核心参数是 data、weights、batch-size、img、epochs:

python train.py \ --data data/dota.yaml \ --weights yolov5s.pt \ --batch-size 12 \ --img 640 \ --epochs 100

这条命令会以 yolov5s 为初始权重,在 640x640 输入下训练 100 轮。batch-size 取决于显卡显存,12 适合 16GB 左右显存;显存 8GB 就降到 8。img 不建议一开始就放大到 1280,先跑通 demo 再追求精度。epochs 对旋转框来说 100 是底线,因为角度回归比水平框更难收敛,尤其是数据量大、目标长宽比差异大的场景,建议先跑到 100 观察曲线走势。

训练过程中要重点看两个指标:一个是 loss 曲线的总 loss 是否稳定下降,另一个是验证集上的 mAP50 在最后一个阶段是否还有上升趋势。如果前 30 轮 mAP 一直为 0,不用慌,旋转框分支的输出头是新加的,前期需要先学好分类和水平位置;但超过 60 轮仍是 0,大概率是数据格式或类别映射有问题,直接停掉去检查上一章提的转换步骤。

4. 影响旋转框精度的 4 个必调参数:角度范围、损失权重、anchor 与超参

4.1 角度范围选择:180° 还是 360°

角度回归的本质是预测一个连续值,但这个值有周期性。比如 89° 和 -91° 其实是同一个朝向,如果网络直接回归角度的绝对值,稍有扰动就会产生巨大误差,梯度也容易来回震荡。常见实现用角度范围 180° 或 360° 来规范化目标值:180° 表示以长边为轴,角度落在 [-90°, 0°) 之间;360° 则覆盖 [0°, 360°)。选择哪种不是随意的,要看你标注原始数据的格式。

如果数据的旋转框是从 DOTA 转换来的四个顶点,推荐统一到 180° 范围,这样每个矩形只有唯一表示,训练更稳定。如果你用的是 cv2.minAreaRect 得到的方向角,默认范围是 [-90°, 0),也对应 180° 范围。千万不要把 180° 和 360° 混着用:训练用 180°,推理时后处理又写成 360°,结果就是同一批目标时而角度正确、时而翻转 90°,mAP 剧烈抖动。我一般会在配置文件里写死 angle_deg,并在验证可视化脚本里加一个断言,确保推理角度范围和训练一致。

4.2 损失权重:KLD 和角度项如何配比

旋转框检测头通常输出 cx, cy, w, h, angle 五项,损失由三部分组成:分类损失、水平框回归损失、角度损失。角度损失如果直接对角度差做 L1,遇到周期性边界会失效,因此常见分支里引入 KLD 或者 GWD 这类基于高斯分布的距离度量。KLD 的思想是把旋转框建模成二维高斯分布,两个框的分布距离作为回归损失,这样即使角度落在边界附近,损失依然平滑。

训练日志中 loss 字段会分成 cls_loss、box_loss、angle_loss 或 kld_loss。假如发现 loss 在下降,但可视化结果里目标的方向总是偏 30° 或 90°,优先检查是不是角度权重太小。常见的超参配置里有一个角度权重项,比如 hyp_mask2former?不是,yolov5_obb 的 hyp 文件里可能有一个 angle 权重。调整建议是从默认权重开始,如果 angle_loss 比 box_loss 小一个数量级,就把它调大到 1.5 到 2 倍再训一轮。注意不要一次性拉到 10 倍,那样主干特征会为了迁就角度而牺牲位置精度。

4.3 anchor 自适应:旋转框和水平框的差异

很多 YOLOv5 OBB 分支仍然使用水平 anchor 作为预设。anchor 是从训练集标签里聚类出来的候选框,水平框聚类时只需要统计目标的宽高。旋转框虽然多了一个角度,但 anchor 依然可以用旋转框的水平外接矩形来统计。问题是,如果目标长宽比极大,比如细长的飞机或船舶,聚类出来的 anchor 数量不足,网络无法有效覆盖所有朝向。

建议第一轮训练前启用 autoanchor:

python train.py --data data/dota.yaml --epochs 100 --autoanchor

启用后脚本会计算出适合当前数据的 anchor,并打印新旧 anchor 的召回率对比。如果原来的 anchor 在 640 输入下最佳召回率已经超过 0.9,那大概率不需要调;如果召回率低于 0.85,特别是你的目标大多是细长形状,就需要提高 anchor 数量或者增加一个偏向大长宽比的 anchor 尺度。很多 demo 跑不出效果不是因为网络结构,而是 anchor 和角度没有适配。

4.4 超参文件里值得调的 5 个参数

旋转框训练在默认 hyp 文件基础上,最值得动的是学习率、分类损失权重、box 损失权重、角度权重和 weight decay。下表给了常见的调整方向和参考范围:

参数名默认范围调整建议说明
lr00.001 ~ 0.01小数据集用 0.001,大数据用 0.01旋转框收敛较慢,不要一开始就开超大学习率
lrf0.01 ~ 0.1保持 0.01余弦退火的最终学习率倍数
cls0.5 ~ 1.0类别不平衡时调高影响分类损失在整个 loss 中的占比
box0.05 ~ 0.1旋转框回归不稳时调高影响位置回归权重
angle / kld 权重0.5 ~ 2.0角度偏了调高具体字段名以分支实现为准

我习惯先跑 30 轮快速验证,看 loss 曲线里几个分项的下降趋势。如果 box_loss 下降很快但 angle_loss 基本不动,就优先调角度权重;如果整体 loss 都降但验证 mAP 不涨,回过去看数据,而不是继续堆 epoch。

5. 旋转框训练避坑:5 个翻车案例与排查手记

5.1 现象:loss 掉到 0.05 但 mAP 一直是 0

这是第一次跑旋转框 demo 最常见的翻车现场。训练日志里分类和回归损失都正常下降,看起来学得很对,但每次 val 结束 mAP50 都打印 0。检查模型输出时,目标框位置大致是对的,但类别全部对不上。原因绝大多数是转换脚本里的 class_names 顺序和 data.yaml 里的 names 不一致,比如脚本里 data 是按“飞机、船、罐”排序,配置里写的是“船、飞机、罐”。解决方法是写一个独立校验脚本,随机抽一张训练图的标签,用 OpenCV 按 class_id 映射回 class_names 画框,和原图人工核对一遍。这个过程能在训练前清掉大部分低级错误。

5.2 现象:训练正常,可视化输出的框是水平框

有的分支在推理阶段默认代码里仍然调用水平框解码,只取了 cx, cy, w, h,把 angle 丢了。也可能是后处理时用了官方 YOLOv5 原版的 non_max_suppression,它内部按水平框的坐标格式裁剪预测结果,角度自然被截断。解决方法是确认你用的分支里推理入口是否调用了 rotate_nms 或 engine.rotate_decoder,并查看输出张量的最后一维尺寸:如果是 5 + 类别数,表示带角度;如果是 4 + 类别数,说明输出头没接上。也有一种特殊情况是训练时 angle 权重随机初始化后没收敛,模型学出来的角度接近 0 或固定值,这时去看 angle_loss 是否仍然很高,而不是怀疑后处理。

5.3 现象:DOTA 转格式后类别顺序错乱

DOTA 原始 15 个类别名包含飞机、船、罐、棒球场等,实际使用时常只挑其中几类。写转换脚本时如果把类别名过滤和类别 id 分配写反,比如先用 index 而不是名称映射,就会出现所有第一行的目标标成 0 类,第二行的标成 1 类,每张图里类别分布完全随机。解决方法是先遍历全部标签文件,打印出所有出现的类别字符串,务必在任何过滤逻辑之前完成去重和排序,再生成 id 映射表。这样即使一张图里有 DOTA 官方标注中不常见的类别,也会因为列表缺失而在转换时直接排除,而不是乱序。

5.4 现象:eval 时同一个目标被重复输出多个框

输入一张旋转框密集的图,比如机场停机坪,NMS 之后目标周围仍然保留了五六个框。旋转框 NMS 和水平框 NMS 的 IoU 计算方式不同,旋转框重叠面积需要多边形求交,很容易被实现简化后算错。常见解决方法是降低 NMS 阈值。水平框常用 nms_iou=0.45,旋转框可以降到 0.3 一试;如果还不行,检查 NMS 的输入坐标顺序是否和解码输出一致。某些分支把角度定义成弧度但 NMS 里当角度用,也会导致相邻框之间 IoU 被低估,从而保留大量重复框。

5.5 现象:train 能跑,但 val 时直接报 shape mismatch 崩溃

这类问题一般出在验证集图片没有目标,对应 txt 为空文件。转换脚本对空文件写入一个空串,dataset 在加载时按行分割得到空数组,后续解析 len(points) 无法对齐固定长度,报维度错误。解决方法是转换脚本里加一个判断,如果 out_lines 为空,不写文件或写一个特殊的只含负数的“无目标”文件,并在 data loader 中跳过该样本。另一个隐藏原因是图片尺寸非正方形,验证时按 batch 堆叠要求统一尺寸,建议用 letterbox 或直接把 img 设成 640x640 并保证所有图片大于 640 后再切中心区域,而不是直接 resize,否则旋转框的坐标和角度在缩放后全部失真。

6. 验证旋转框 demo 的最终效果:mAP、可视化与角度统一

6.1 用 val.py 看正确的 rotated mAP

训练完成后,用验证脚本评估模型,命令和普通 YOLOv5 一致:

python val.py --data data/dota.yaml --weights runs/train/exp/weights/best.pt --img 640

输出里会打印 mAP50 和 mAP50-95。需要注意,这两个指标是基于旋转框 IoU 计算的,即两个四边形的交集面积除以并集面积,而不是水平框的矩形 IoU。因此同一模型用普通 YOLOv5 的 eval 代码跑出的 mAP 是没有意义的,必须确保代码里调用了旋转框匹配逻辑。对比基线时,也应同时对比普通水平框模型和 OBB 模型在相同数据上的 mAP,才能说明旋转框带来的增益。

6.2 可视化预测结果,先看框再看指标

指标之外,一张直观的可视化图能快速定位问题。常见推理脚本输出 cx, cy, w, h, angle,需要转换成四个顶点才能画出旋转矩形。下面是一段把中心格式转成顶点的片段:

import math import cv2 def convert_to_vertices(box, angle_deg): cx, cy, w, h = box cos_a = math.cos(math.radians(angle_deg)) sin_a = math.sin(math.radians(angle_deg)) dx, dy = w / 2, h / 2 pts = [ [-dx, -dy], [dx, -dy], [dx, dy], [-dx, dy] ] verts = [] for px, py in pts: x = cx + px * cos_a - py * sin_a y = cy + px * sin_a + py * cos_a verts.append((int(x), int(y))) return verts

这段代码把中心点格式按角度旋转得到四个顶点,然后就可以用 cv2.polylines 绘制。如果发现顶点顺序在可视化时交叉错乱,重新确认角度正负方向和旋转公式,而不是强行继续训练。可视化时用原始分辨率图片,不要用训练时的 letterbox 图,否则拖出来的框会有偏移。

6.3 导出部署模型前,统一角度和坐标格式

写 demo 是一回事,真正部署是另一回事。导出 ONNX 或 TensorRT 前,需要确认模型输出的角度定义与部署端 NMS 期望的一致。比如训练时角度范围是 [-90°, 0),导出后若部署端默认 [0°, 180°),推理结果会整体误差 90°,表现为细长目标全部转成短边对齐。解决方式是在导出脚本里增加一个角度映射层,把网络输出统一到部署端格式;或者直接修改部署端后处理,和训练保持一致。另一个坑是坐标是否归一化、是否在 0~1 之间。推理完要乘回原图尺寸,角度不能乘宽高比,这些细节在 demo 阶段看不出来,真上线就暴露。

第一次跑通旋转框 demo 时,我犯的最傻的错误是把 DOTA 标签里的类别顺序写反了,mAP 连挂三天,后来用可视化批量画了一遍才意识到,当时只觉得“这模型怎么这么玄学”。之后我给自己定了个规矩:任何新数据集先随机抽 5 张可视化标签,再抽 5 张可视化预测,最后才看指标。顺序反过来,你会被 mAP 骗到怀疑人生。旋转框训练并不是比水平框难多少,而是多了一个需要全链路对齐的角度变量,角度对齐了,剩下的调参经验基本可以平移。希望这篇笔记能帮你在旋转框上少走一段弯路。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询