☰
YOLOv5旋转框检测OBB训练Demo全解析:从数据准备到避坑实战
2026/10/15 12:31:11 网站建设 项目流程

简介:YOLOv5 OBB旋转框训练DEMO是一套面向目标检测开发者和进阶学习者的实操示例,针对遥感影像、航拍视频中的倾斜目标(如车辆、树木、建筑物)检测需求,在YOLOv5基础上引入旋转边界框机制,弥补常规轴对齐框的不足。资源完整覆盖了OBB训练的数据预处理、配置调整、损失计算、训练验证与推理部署环节,适合希望理解旋转框原理并快速跑通流程的读者。压缩包共583个文件,大小445.58MB,包含166个XML标注文件、95个TXT标签、93张PNG图像、71个Python脚本、38个YAML配置以及若干TensorRT/ONNX相关文件,目录结构清晰,兼顾数据、代码与训练产物。目前已有1818人学习下载。通过该资源,读者可对照实验记录和脚本直观了解OBB数据集的构建方法、旋转框损失函数的实现细节,并借助现成的训练、预测、导出脚本迁移到自有数据;其中事件日志和推理代码也为排查训练问题、优化模型精度提供了实际参考。

1. YOLOv5 OBB旋转框训练Demo:先搞懂它解决什么问题

水平框检测在遇到细长目标时经常翻车,比如遥感影像里的建筑物、工厂传送带上的长条工件、货架上的倾斜标签。你用普通YOLOv5去框一个斜着放的螺丝刀,检测框要包住整个目标,结果框里一大半是背景,两个挨得近的目标还会互相污染特征。这时候就要上旋转框检测(OBB,Oriented Bounding Box),让检测框跟着目标角度一起转。这份YOLOv5 OBB旋转框训练Demo,就是一个能直接跑起来的旋转框目标检测项目,基于YOLOv5的框架扩展了角度回归分支,支持带角度标签的训练、验证和推理。适合做遥感目标检测、工业质检、场景文字检测的开发者,想在自己数据集上快速落地旋转框方案,又不想从头写检测器的人。

拿到这份资源,你要理解的不只是“跑通训练命令”,而是数据集标注格式、模型输出头、损失函数和后处理这几处相对水平框都改在哪。下面按我实际拆解这个项目的顺序,一层层讲清楚。

2. 资源包结构与环境准备:先看清项目骨架再动手

2.1 目录结构与关键文件解析

解压这个Demo后,第一件事不是急着装环境,而是先过一遍项目骨架。常见的YOLOv5 OBB分支项目会在原始YOLOv5基础上增加或修改这几个关键路径。

  • models/yolov5s-obb.yaml:模型结构配置,定义旋转框检测头的输出维度。水平框每个anchor输出5+nc个值(x,y,w,h,obj+classes),旋转框版本会在此基础上增加角度分支,一般是再加一个或两个输出值。
  • utils/loss.py:损失函数。这里会多出角度损失项的计算逻辑,常见做法是使用Smooth L1或KLD(Kullback-Leibler Divergence)损失来处理角度回归。
  • utils/metrics.py:评估指标。从水平IoU换成旋转IoU计算,mAP的统计方式跟着变。
  • utils/datasets.py或utils/augmentations.py:数据加载与增强逻辑。Mosaic、随机仿射变换等增强操作会针对旋转框做适配,尤其在随机旋转时要同步更新标签的角度值。
  • detect.py / val.py / train.py:入口脚本。训练时通过参数指定OBB模式,推理输出会多出角度字段,画框函数也要改成绘制旋转矩形。

我第一次打开这类Demo时犯过一个错,直接用pip装好requirements.txt就开跑,结果报“ModuleNotFoundError: utils.plots”之类的错。原因就是路径没加对,项目里很多模块是通过相对路径互相import的,必须从项目根目录执行命令。

2.2 Python环境安装与依赖核对

环境依赖方面,这类OBB Demo通常会要求在PyTorch环境下运行。我一般建议先创建独立的conda环境,避免和已有项目打架。

conda create -n yolov5-obb python=3.8 conda activate yolov5-obb cd /path/to/yolov5-obb pip install -r requirements.txt

逻辑说明:这条命令创建一个干净的Python 3.8环境,然后进入项目目录安装依赖。requirements.txt里一般包含torch、torchvision、opencv-python、numpy、pyyaml、tqdm、matplotlib等基础包。PyTorch版本建议和你的显卡驱动匹配,如果是30系及以上显卡,装CUDA 11.x配套的torch版本即可。

参数说明:python=3.8不是必须的,3.9、3.10也能跑,但有些项目依赖的老版本pycocotools在3.10上编译容易出问题,用3.8最省事。

装完依赖后,我先跑一个简单命令验证torch和CUDA是否正常工作:

python -c "import torch; print(torch.__version__, torch.cuda.is_available())"

逻辑说明:如果输出里cuda.is_available()是False,说明torch装成了CPU版本或者驱动没对上。遇到这种情况,直接去PyTorch官网选对应CUDA版本的安装命令重新装,不要在这个问题上耗时间。

依赖装好后,建议先跑一次官方提供的demo数据训练一遍,验证环境和代码流程没问题,再上自己的数据集。这个Demo里通常会带一个小的示例数据集,下载解压后放在datasets目录下,数据结构大概是:

datasets/ demo_obb/ images/train/xxx.jpg images/val/xxx.jpg labels/train/xxx.txt labels/val/xxx.txt

标签文件格式是旋转框的核心,下一章详细说。

3. 数据集准备:从四点标注到可训练的旋转框

3.1 旋转框标注格式:两种主流表示法

旋转框标注最常用的是两种格式:一种是OpenCV风格的cx cy w h angle,其中angle是角度值,单位是度,表示框相对水平方向的旋转角度;另一种是四角点坐标格式x1 y1 x2 y2 x3 y3 x4 y4,直接记录旋转矩形的四个顶点坐标,按顺时针或逆时针顺序排列。YOLOv5 OBB分支在数据加载阶段一般会把格式统一成某种内部表示,常见的是把四角点转换成中心点加宽高加角度的表示,或者反过来。

这个Demo里,标签文件的组织方式我见过两种变体:一种是标签每行格式class_id x1 y1 x2 y2 x3 y3 x4 y4,坐标值做了归一化;另一种是class_id cx cy w h angle,这种格式下角度范围通常是0到180或-90到90,取决于作者实现。你在用之前,一定要去看dataset.py里怎么解析标签,否则标注格式对不上,训练出来的模型会“看起来很努力但效果完全不对”。

如果你手头标注工具导出的格式不是项目需要的,可以写一个转换脚本。下面这个例子演示了如何把四角点格式转换成cx cy w h angle:

# convert_quad_to_cxywh_angle.py import numpy as np def quad_to_cxywh_angle(points): """ 将四角点坐标转为 (cx, cy, w, h, angle) points: 8个数值 [x1, y1, x2, y2, x3, y3, x4, y4] 返回: [cx, cy, w, h, angle],角度单位为度 """ pts = np.array(points, dtype=np.float32).reshape(4, 2) # 计算四条边的长度,取平均得到宽和高 d1 = np.linalg.norm(pts[1] - pts[0]) d2 = np.linalg.norm(pts[2] - pts[1]) w = (d1 + d2) / 2 h = (d1 + d2) / 2 # 中心点:取四个顶点平均值 cx = pts[:, 0].mean() cy = pts[:, 1].mean() # 角度:用第一条边的方向角,弧度转角度 angle = np.arctan2(pts[1][1] - pts[0][1], pts[1][0] - pts[0][0]) angle_deg = angle * 180 / np.pi return [cx, cy, w, h, angle_deg] # 示例:一个旋转30度的矩形 quad = [100, 100, 186, 150, 166, 236, 80, 186] cx, cy, w, h, angle = quad_to_cxywh_angle(quad) print(f"cx={cx:.2f}, cy={cy:.2f}, w={w:.2f}, h={h:.2f}, angle={angle:.2f}")

逻辑说明:这个脚本的核心是几何换算。四角点坐标直接来自标注工具,通常是有序的顺时针或逆时针四点。宽高通过取两条对边距离的平均来抗噪声,角度用第一条边的方向角,这样只要顶点顺序稳定,角度计算就不会出现跳变。

参数说明:角度范围很关键。如果后续训练时项目把角度限制在0到180度,那这里算出来的负角度就要加180归一化。不同的OBB实现对这个参数的处理逻辑不同,有的用180表示半周期,有的用90表示四分之一周期,你转换脚本里写死之前,先确认目标项目的角度编码方式。

3.2 数据yaml配置与标签目录组织

数据yaml文件在YOLOv5体系中扮演“地图”的角色,OBB版本会在此基础上额外增加角度相关的配置项。下面是一个典型的OBB数据配置:

# data_obb.yaml train: datasets/demo_obb/images/train val: datasets/demo_obb/images/val nc: 1 names: ['object'] # 角度相关配置 angle: 180

逻辑说明:train和val指向图片目录,labels目录与图片目录同级只是把images换成labels,这是YOLOv5默认的标签路径推导规则。nc和names是类别数及名称。angle配置角度范围,取180表示模型预测0到180度的旋转角,这个值直接决定角度回归分支的输出设计和损失计算方式。

参数说明:angle: 180适合大部分场景,但如果你的数据里目标角度天然有对称性,比如所有工件都是水平的或垂直的,可以改成angle: 90降低角度回归难度。新手先别动这个参数,用默认值跑通再说。

目录组织上,最容易出错的是图片和标签的命名对应关系。YOLOv5要求图片a.jpg对应的标签是a.txt,文件名前缀必须完全一致,否则训练时会报“No labels found in ...”。另外标签文件必须是普通文本,不能是UTF-8带BOM,否则解析第一行标签时会读出一个隐藏字符,导致坐标解析错位。

我习惯在训练前先写一个检查脚本,统计每张图片对应的标签文件是否存在、标签坐标有没有越界:

# check_labels.py import os from pathlib import Path img_dir = Path("datasets/demo_obb/images/train") label_dir = Path("datasets/demo_obb/labels/train") missing = 0 for img in img_dir.iterdir(): label_path = label_dir / (img.stem + ".txt") if not label_path.exists(): missing += 1 print(f"缺少标签: {img.name}") else: # 检查标签坐标范围 for line in label_path.read_text().strip().splitlines(): parts = line.split() coords = list(map(float, parts[1:])) if any(c < 0 or c > 1 for c in coords): print(f"坐标越界: {label_path.name} -> {line}") print(f"检查完成,缺失标签数: {missing}")

逻辑说明:这个小脚本对所有训练图片逐一检查标签文件是否存在,并检查归一化坐标是否超出0到1的范围,可以有效拦截两种最基础的数据问题。

4. 训练与推理:核心命令和参数细节

4.1 训练命令与OBB关键超参

数据准备好、检查脚本跑完没有异常,就可以开始训练了。这个Demo的基础训练命令和原版YOLOv5非常接近:

python train.py \ --data data_obb.yaml \ --weights yolov5s.pt \ --img 640 \ --batch-size 16 \ --epochs 100 \ --device 0 \ --project runs/train_obb

逻辑说明:--weights可以选择官方预训练权重作为初始权重,迁移学习能显著加速收敛。如果不指定,模型会从头开始训练,收敛慢且效果通常差一些。--device 0指定使用第一张GPU卡。--project指定输出目录,训练过程中的权重和日志会保存到这里。

参数说明:--img 640表示输入图像缩放尺寸,如果你的目标比较小,可以设成896或1280,显存不够就减小batch-size或者用--cache参数把图片加载进内存。这个Demo如果支持多尺度训练,还会有一个--multi-scale的选项,每10个batch随机改变输入尺寸,能提升模型对尺度变化的鲁棒性。

角度相关的训练参数一般在hyp.yaml文件里配置,OBB版本会多出角度损失权重这一项。常见的角度损失权重设置在0.5到2.0之间,权重太大会让模型在早期训练时把大量精力花在角度回归上,导致位置和分类学不好;权重太小则角度输出不准确,推理时旋转框看着歪歪扭扭。

# hyp_obb.yaml 关键片段 lr0: 0.01 lrf: 0.2 angle_loss: 0.8

逻辑说明:lr0是初始学习率,lrf是最终学习率系数,训练结束时学习率会衰减为lr0*lrf。angle_loss是角度损失相对位置损失的权重,0.8表示角度损失在总损失中占八成的位置损失权重。

4.2 推理与结果可视化:读懂旋转框输出

训练完成后,推理命令和原版YOLOv5相似:

python detect.py \ --weights runs/train_obb/exp/weights/best.pt \ --source data/images/ \ --conf-thres 0.25 \ --iou-thres 0.45 \ --device 0

逻辑说明:--source可以指向图片目录、单张图片或视频文件。推理完的结果保存在runs/detect/exp下,旋转框会以带角度的矩形画在图上。

这里有一个值得注意的地方,水平框模型推理输出的xywh是x_center, y_center, w, h,而OBB模型在xywh基础上多出angle字段,detect.py在处理预测结果时会多一步角度读取。如果你在自己写的推理脚本里接这个模型,要知道输出Tensor的格式通常是这样:

[ batch_size, num_queries, 6 + num_classes ] # 其中6 = cx, cy, w, h, angle, obj_conf # 后面是各类别概率

从预测结果里取旋转框并画图的逻辑大致是:

# draw_obb.py import cv2 import numpy as np def draw_obb(image, cx, cy, w, h, angle_deg, color=(0, 255, 0)): """ 根据中心点、宽高和角度画旋转矩形 angle_deg是角度制的旋转角,cv2画旋转矩形时用负弧度制 """ angle_rad = -angle_deg * np.pi / 180.0 cos_v = np.cos(angle_rad) sin_v = np.sin(angle_rad) # 半宽半高 hw = w / 2.0 hh = h / 2.0 # 旋转矩形的四个顶点相对中心点的偏移 corners = np.array([ [-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh] ]) # 旋转矩阵 rotation = np.array([ [cos_v, -sin_v], [sin_v, cos_v] ]) rot_corners = corners @ rotation.T + np.array([cx, cy]) # 转为整数坐标并按多边形绘制 pts = rot_corners.astype(np.int32).reshape(-1, 1, 2) cv2.polylines(image, [pts], isClosed=True, color=color, thickness=2) return image

逻辑说明:旋转矩形本质上就是把四个角点先按宽高摆好,然后围绕中心点旋转指定角度。cv2.polylines直接画闭合多边形,不需要自己重新计算每条边的方程。这里的角度正负号取决于模型输出的角度定义,我拆过的这个Demo里角度是顺时针为正,所以画图时要取负号转成OpenCV的逆时针方向。

参数说明:w和h的定义也很讲究。有的实现里w是长边,h是短边,角度指向长边方向;有的实现则固定用第一个维度作为w。如果推理结果看起来有些框长边和短边反了,检查一下模型训练时标签里是长边在前还是短边在前,画图时对应调整即可。

5. 避坑手册:旋转框训练中遇到的五类典型问题

5.1 训练loss不下降或震荡:角度周期性问题在作祟

现象:前10个epoch的box loss在下降,但总loss波动剧烈,训练曲线像锯齿,验证集mAP长时间在低值徘徊。

原因:角度这个量的周期性和坐标不一样。当模型预测的角度值是179度,而标签角度是0度时,数值上差了179度,但实际旋转框几乎重合。多数损失函数不具备角度周期性感知,直接用Smooth L1算角度差,会让模型在角度边界附近反复横跳。

解决:优先检查项目是否支持角度范围90度模式,把angle从180改成90。这样角度编码区间从0到180压缩到0到90,配合数值归一化能有效减少边界问题。如果项目支持使用KLD损失替代Smooth L1做角度回归,切换过去通常能缓解震荡。

提示:KLD损失对角度差做了周期性处理,在角度接近边界时梯度更平滑,是旋转框检测里常用的角度回归损失方案。

5.2 训练时报“Assertiont ≥ 0 && t < nfailed”:坐标越界

现象:数据集加载到一半直接报错,异常发生在读取标签并计算grid索引时,个别样本的坐标出现负数或超过1的值。

原因:标签坐标没有归一化,或归一化时用了错误的图片尺寸。常见场景是标注工具导出的是绝对像素坐标,你直接放进labels目录没做除以图片宽高的操作。另一种情况是图片存在EXIF旋转信息,标注时看的图片和训练时读入的图片方向不一致,坐标跟着失效。

解决:写检查脚本先跑一遍(第3章的脚本就能拦截这个问题)。如果确认标签本身没问题但报错还在,检查图片读取路径下的原图尺寸与标注时是否一致,尤其注意标注时是否裁剪过图片。我通常会在转换脚本里把每个标签的坐标极值打印出来,一眼定位是不是归一化步骤出了问题。

5.3 模型在验证集mAP很高,但现场效果差:数据增强与角度分布不匹配

现象:在自己的测试集上mAP有0.85,拿到现场拍的照片上检测率直线下降,尤其角度接近45度或135度的目标几乎全漏。

原因:训练数据里的角度分布不均匀,大量目标集中在水平或垂直方向,45度和135度方向的样本很少。模型对样本稀疏的角度区域天然拟合不足。旋转框检测对角度分布比水平框更敏感,因为模型需要学到的不仅是“这里有个东西”,还包括“这个东西转了多大幅度”。

解决:检查训练集的角度分布,写个统计脚本画出角度直方图。如果某个区间样本很少,可以加大该角度区间的数据增强权重,比如在Mosaic之外的随机旋转增强中,把旋转角度范围从±15度扩大到±30度。如果项目里支持角度抖动(angle jitter)增强,打开它并设一个合理的值,比如20度。

5.4 推理时旋转框没有完全贴合目标:后处理里缺了一步角度归一化

现象:检测框大概位置正确,但整体旋转角度比实际目标偏移了几度到十几度,目标越长偏移越明显。

原因:模型在训练时角度是在特定区间编码的,推理时输出层的激活函数如果没做对应解码,输出的角度可能是未经处理的原始值。比如训练时用sigmoid把角度压缩到0到1再乘以180,推理时直接拿sigmoid输出乘以180但没减偏移值。

解决:翻一下models/yolov5s-obb.yaml里角度分支的最终激活函数定义,再对照decode逻辑。一般推理结果里角度字段要经过一次缩放和平移,公式是angle_pred = sigmoid(x) * angle_range - angle_offset,具体数值看代码实现。直接在detect.py里打印几组原始输出和解码后的角度值,对比标签就能看出解码是否正确。

5.5 显存OOM:旋转框分支让显存压力变大

现象:原版YOLOv5用batch-size 16跑得动,切到OBB版本同样配置直接报CUDA out of memory。

原因:OBB模型增加了一个角度输出通道,特征图上的输出维度变大,同时损失函数里旋转IoU的计算涉及更多矩阵运算,显存占用比同体积水平框模型高20%到40%。

解决:不要和原版参数较劲,先降batch-size到8或4跑通。如果必须保持batch-size,可以开梯度累积,对训练节奏影响较小。另一个实用做法是把--img从640降到544,输入尺寸小了显存会空出不少,mAP掉得有限。

6. 进阶验证技巧:合成旋转矩形测试角度预测准确性

模型训练完、推理流程跑通以后,建议你用合成数据做一个快速验证,把角度预测的准确性单独测出来。这个技巧能帮你把“检测框位置准不准”和“角度准不准”两个维度解耦,定位问题时特别有用。

写一个脚本,生成一批已知角度旋转矩形的图片,矩形中心位置随机但角度是预设值,比如每隔15度一张图:

# synthetic_test.py import cv2 import numpy as np def create_synthetic_rect(size=(640, 640), angle_deg=30): """生成一张含单个旋转矩形的合成图""" img = np.zeros((size[0], size[1], 3), dtype=np.uint8) img[:] = (128, 128, 128) cx, cy = 320, 320 w, h = 200, 60 angle_rad = -angle_deg * np.pi / 180.0 corners = np.array([ [-w/2, -h/2], [w/2, -h/2], [w/2, h/2], [-w/2, h/2] ]) rot = np.array([ [np.cos(angle_rad), -np.sin(angle_rad)], [np.sin(angle_rad), np.cos(angle_rad)] ]) rot_corners = corners @ rot.T + np.array([cx, cy]) pts = rot_corners.astype(np.int32).reshape(-1, 1, 2) cv2.fillPoly(img, [pts], (0, 255, 0)) return img # 生成一组固定中心点、不同角度的测试图 for angle in range(0, 180, 15): img = create_synthetic_rect(angle_deg=angle) cv2.imwrite(f"test_angle_{angle}.jpg", img)

然后用训练好的模型对这批合成图做推理,重点看预测的角度值与真实角度的误差。如果误差在5度以内,说明角度回归分支工作正常,推理时的解码逻辑也对;如果误差很大,优先检查模型推理输出的角度解码和画图时的角度符号约定。

合成数据没有真实场景的纹理和噪声,能测出模型的几何感知能力,但测不出泛化能力。所以它适合当冒烟测试用,在跑新数据集、改角度超参之后花十分钟验证一下角度分支没坏,然后再去精调别的参数。我从那以后每次换数据集,都强制走一遍“合成矩形验证角度、真实数据验证检测率”的流程,能在正式训练前拦截掉大部分标注格式和角度编码的问题。希望帮到你。

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

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

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

立即咨询