简介基于YOLOv5的旋转目标检测系统完整源码与预训练模型面向计算机视觉开发者、算法工程师及目标检测方向初学者适用于车牌识别、文字检测、细胞分析等任意角度目标的定位任务。压缩包共177个文件、整体约103.66MB以Python脚本66个py、YAML配置35个yaml、C/CUDA加速算子cpp、cu、hpp和模型权重pt为主同时包含代码说明文档、样例图片与Jupyter示例覆盖从数据预处理、模型训练到推理部署的完整流程。源码目录yolov5_obb-master中实现了基于OBB的旋转框损失函数、NMS后处理含poly_nms等旋转框算子、评估工具以及Dockerfile部署配置可直接用于复现实验或替换自有数据集进行微调。已有382人学习下载无论是了解YOLOv5旋转目标检测原理还是在此基础上做工程化扩展都能获得可运行的参考实现。1. 基于YOLOv5的旋转目标检测先救输出的角度头标准YOLOv5在COCO上的水平框检测我跑过很多项目真正动手做旋转目标检测时发现网络主干和Neck都不用大改最核心的差异在检测头的输出张量、损失计算和后处理NMS。所谓“基于YOLOv5的旋转目标检测系统源码模型”本质就是把角度回归接进YOLOv5的输出层让网络既能给出目标位置和类别也能给出长方形朝向适合遥感图像里的飞机、仓库里的货架、文档扫描件里的文字块这类任意朝向目标检测。对于已经会用官方YOLOv5训练水平框、又想补上朝向信息的人来说正确的路径是理解旋转框表示、改好角度头、对齐数据格式再谈调模型。2. 在YOLOv5网络结构上改输出头角度编码、损失与旋转NMSYOLOv5网络结构里Head输出特征图每个网格预测一组参数水平框版本是 (x, y, w, h, obj, class)旋转目标检测要把它变成 (x, y, w, h, theta, obj, class)。只改输出头就能跑不代表只加一个输出通道就行角度编码、损失函数和NMS都得换否则训练出来的角度头完全不收敛。2.1 输出张量结构调整从5nc到6ncYOLOv5有三种尺度的特征图原始Detect头里每层卷积输出通道是self.no * self.na其中self.no nc 5。旋转目标检测的常见做法是在中心点、宽高后面追加一个角度值把self.no改成nc 6# models/yolo.py 中 RotatedDetect 的初始化片段 class RotatedDetect(nn.Module): def __init__(self, nc80, ch()): # ch 是三个检测层输入通道 super().__init__() self.nc nc self.no nc 6 # x,y,w,h,theta,obj class self.nl len(ch) # 输出尺度数 3 self.na 3 # 每个尺度 3 个锚框 self.m nn.ModuleList( nn.Conv2d(c, self.no * self.na, 1) for c in ch) def forward(self, p): z [] for i in range(self.nl): # p[i] 形状: (b, no*na, ny, nx) bs, _, ny, nx p[i].shape p[i] p[i].view(bs, self.na, self.no, ny, nx) p[i] p[i].permute(0, 1, 3, 4, 2).contiguous() # 得到 (bs, na, ny, nx, 6nc) return p这段代码的作用是把特征图重排成网格维度让后续 build_targets 可以直接按网格位置索引 (x, y, w, h, theta)。注意这里 theta 一般统一按弧度预测但有的源码会把 theta 编码成 cos2θ 和 sin2θ因为 θ 在 [-π/2, π/2) 区间有边界跳变直接回归一个角度值Smooth L1 损失在角度接近 ±90 度时会出现明显震荡。我在实际项目里更倾向回归单个 θ 值配合角度归一化函数限制范围因为输出维度少一个小模型也更容易收敛。有一种观点认为旋转目标检测需要更换骨干网络其实不是。YOLOv5 里的 Focus、C3、PANet 在特征提取上与目标朝向无关遥感场景里的多尺度小目标更依赖特征金字塔本身。很多效果不错的旋转检测系统源码里只改了 Detect 类和后处理逻辑backbone 保持原样。如果为了旋转目标特意换成 Transformer 主干参数和训练成本都会上升而且不解决参数表示问题。2.2 角度范围与旋转框参数θ的表达必须前后一致旋转框在工程上经常写成 (x, y, w, h, θ)但 θ 的定义各家不完全一样。DOTA 官方标注输出的是四个角点 (x1,y1)...(x4,y4)需要先计算中心点、宽高和角度部分标注工具把 θ 定义为长边与水平正方向的夹角取值 [-90°, 90°)另一部分定义为从水平轴逆时针旋转到目标短边的角度取值 [0°, 180°)。两种表示下同一个矩形即使中心点、宽高相同画出来朝向也会不同。拿到一个“源码模型”压缩包第一件事不是训练而是看它的数据转换脚本里用的角度范围再用一个小脚本验证角度正方向。表示方式参数数量优点缺点常见损失函数中心点宽高角度5输出维度小、回归稳定角度边界跳变Smooth L1中心点宽高cos2θ,sin2θ6无边界跳变输出多一个通道L1 或 MSE四个角点8标注数据可直接使用顶点相关性强、小目标不稳GIoU 或 KLD如果用第二种编码方式网络输出的是 cos2θ 和 sin2θ不再直接输出 θ。NMS 阶段要把它们还原成旋转矩形转换公式是theta 0.5 * atan2(sin2θ, cos2θ)并且在绘制和评估前再次归一化。否则即使模型收敛后处理阶段也会因单位不一致画出错误方向的框。2.3 旋转IoU与旋转NMS手工实现时的3个坑旋转目标检测的后处理不能用普通水平NMS因为水平矩形IoU会忽略角度差异容易在斜排目标上产生大量重复框。常见做法是用旋转框的IoU多边形裁剪或近似运算替换原来的 bbox_iou并额外计算角度距离。这里有三个容易踩坑的地方普通NMS在cocoapi/opencv里直接丢掉角度信息同一个旋转框错位后仍然按水平IoU计算导致低分误删。旋转NMS的IoU阈值需要调小。长宽比越大的目标角度误差对其IoU影响越大官方水平框阈值 0.5 通常偏松我一般从 0.3 开始试。角度归一化周期是 π 而不是 2π。两个相交框角度一个是 89°另一个是 -91°实际几乎重合但若直接比较角度差值会得到 180° 的差异。提示不要用水平框的 IoU 阈值直接套旋转NMS。如果模型角度回归还没收敛旋转NMS输出会频繁出现同一物体两个重叠框此时先看角度分布而不是先调NMS阈值。训练时的总损失一般为cls_loss obj_loss box_loss angle_loss。水平框 YOLOv5 的 box_loss 用 CIoU但 CIoU 无法直接作用于旋转框。常见替代方案是使用 Gaussian Wasserstein DistanceGWD或者概率IoU也有的实现退化为在中心点上用普通回归、只把角度作为额外 term。判断一个源码包是否真正“旋转”就看它的 box_loss 是不是真的能回传梯度到角度分支。很多自称旋转的模型画框时加个角度但 loss 根本没有角度项这类源码只能做演示。3. 用YOLOv5训练自己的旋转数据集数据格式、增强和超参数拿到旋转目标检测源码和预训练模型后通常要先跑通 pre-trained 模型再用自己的数据集微调。旋转目标检测的数据格式比水平框多一个角度如果预处理时角度定义和模型不一致训练 loss 会一直下降指标却很差。这一章按数据清洗、增强、超参三条线来梳理常见坑。3.1 把DOTA格式转成yolov5旋转框格式遥感领域常用 DOTA 格式每行是八个坐标点加类别名。YOLOv5 旋转系统的常见训练格式是class x_center y_center width height angle转换时要用角点计算中心点、宽高和角度。下面是一个最简转换逻辑# tools/dota2yolo_obb.py 的要点 import numpy as np def dota_line_to_obb(line): parts line.strip().split() coords list(map(float, parts[:8])) cls parts[8] pts np.array(coords, dtypenp.float64).reshape(4, 2) # 通过对角顶点取中点得到中心点 cx (pts[0][0] pts[2][0]) / 2.0 cy (pts[0][1] pts[2][1]) / 2.0 # 以第0和第1个点作为一条边计算长边与水平方向夹角 dx pts[1][0] - pts[0][0] dy pts[1][1] - pts[0][1] w np.linalg.norm([dx, dy]) h np.linalg.norm(pts[2] - pts[1]) theta np.arctan2(dy, dx) # 角度范围限制到 [-pi/2, pi/2) if theta np.pi / 2: theta - np.pi if theta -np.pi / 2: theta np.pi return cls, cx, cy, w, h, theta注意宽和高的顺序不同标注工具产生的角点顺序不一样。如果长边被算成 h但模型定义的是 w那么训练时等量宽高会互换。一个自查办法是把转换后的 (cx, cy, w, h, theta) 画在图上对照原标注看矩形长边是否跟随 theta 方向。DOTA 数据里还存在 difficult 标志很多时候源码包默认把它当成背景丢弃如果目标本身是小飞机或小船舶丢弃难例会让训练样本明显变少这种情况下我倾向保留难例只是把损失权重降低。3.2 Mosaic、翻转增强与角度一致性YOLOv5 的 Mosaic 增强在旋转检测里坑最多。Mosaic 拼接时如果对整图做了旋转和缩放原标注的中心点、角度都必须同步变换。很多源码包会在 load_mosaic 里禁用随机旋转只保留平移和缩放因为旋转增强还要插值生成的对齐线框会有误差。水平翻转仍然是常用手段但要做角度修正# 在 load_mosaic 的拼接循环中对水平翻转做角度更新 if random.random() 0.5: # 水平翻转 img img[:, ::-1] labels[:, 1] 1.0 - labels[:, 1] # cx 翻转 labels[:, 5] -labels[:, 5] # theta 取负 labels[:, 5] np.where(labels[:, 5] -np.pi/2, labels[:, 5] np.pi, labels[:, 5])这段代码里的labels[:, 5]是角度具体下标以你的实际标签表为准。关键原则是任何改变 x 或 y 坐标的变换都必须同步更新角度。竖直翻转同样要取负90° 或 270° 旋转则要对角度做加减 π/2 并再次归一化。如果源码包在增强部分没有处理角度训练时网络会看到同一个目标朝向标注混乱最终模型只得学会预测水平框角度头变成摆设。3.3 YOLOv5超参数里需要调整的3个项YOLOv5官方hyp.scratch.yaml是为水平框调好的旋转检测里很多参数依然适用但下面三项值得特别关注参数默认值旋转任务建议影响anchor_t4.02.5~3.0正样本分配数量长条形目标更容易成为负样本box_loss0.050.05~0.1中心点和宽高回归angle_loss无0.1~0.2角度回归需手动加进总损失第一个是 anchor_t默认 4.0 表示锚框和目标宽高比差距超过 4 倍就不分配正样本。旋转目标经常是长条形船舶的长宽比可以达到 6:1这个阈值会直接把很多目标判成背景。我一般调到 2.5~3.0但过低会让 anchor 数量显著增多训练变慢。第二个是 box_loss 权重如果角度分支和 box 分支一起用同一个 loss 会互相干扰建议单独设 angle_loss并让它的权重小于 box_loss。第三个是输入尺寸遥感小目标建议用 1024 或 1280而不是默认 640因为角度头对边缘轮廓更敏感分辨率不足时短边像素太少。提示如果源码包里的轮次损失函数没有angle_loss这一项光改 hyps 文件不会生效需要像第2章那样在损失函数里对 theta 分支单独计算 loss 并backward()。4. 源码模型落地训练命令、推理脚本与部署排错源码包里的模型文件通常是runs/train/exp/weights/best.pt和last.pt前者是验证集最优后者是训练结束时状态。我一般先加载best.pt跑一张标注过的验证图确认输出的旋转框坐标能正确绘制再开始全流程。这一章聚焦训练命令、推理输出和部署时的角度单位问题。4.1 最小可复现的训练命令假设数据集目录是dataset/ images/ train/ val/ labels/ train/ val/ dataset.yaml训练命令与官方 YOLOv5 基本一致python train.py \ --data dataset/dataset.yaml \ --weights yolov5s.pt \ --img 1024 \ --batch 16 \ --epochs 100 \ --hyp data/hyps/hyp.scratch.yaml \ --device 0这里的--weights yolov5s.pt可以是官方水平框预训练权重也可以换成源码包附带的旋转检测模型。--img 1024对遥感小目标更友好但显存占用会变大先在 8GB 卡上把--batch 4跑起来确认 dataloader 正常后再加大 batch。训练开始后不要只看 loss 下降要观察每个 epoch 结束时验证集上的角度误差。如果前 10 个 epoch 角度误差仍然在 60 度以上回到第3章检查角度定义是否一致。4.2 推理阶段的旋转框输出与后处理rotated 版 detect.py 的核心区别在 NMS 前后的角度分量分离# detect.py 中推理后处理的关键步骤 pred non_max_suppression_rotated(pred, conf_thres0.25, iou_thres0.3) # pred 的每条记录形状为 (n, 7) # x, y, w, h, theta, confidence, class if len(pred): boxes_xywhr pred[:, :5] conf pred[:, 5] cls pred[:, 6] # 保存成 DOTA 风格或旋转框可视化格式 for box in boxes_xywhr: x, y, w, h, theta box # 输出时如果需要角度制再在这里转换 theta_deg theta * 180 / np.pi这是最容易被忽略的地方pred[:, :5]里的 theta 单位取决于训练代码。有的源码在损失函数里把角度转成了弧度有的在输出层直接乘以 180/π。如果用错误单位画框会看到所有预测框的角度整体偏移一个固定量且偏移量和目标本身方向无关。建议推理前先打印一帧预测的 theta 原始值再用已知朝向的测试图对比。4.3 部署时角度归一化的三个常见错误把模型导出到 TensorRT、ONNX 或 OpenVINO 时最容易翻车的是角度单位不统一。三个高频错误将弧度当成角度直接传给 Web 前端前端画矩形时所有框旋转约 57.3 倍。角度接近 ±90° 时不取余导致同一目标因边界跳变画到完全相反的方向。C 后处理和 Python 推理使用不同角度范围同一个输入在两边输出框不一致。解决办法是固定一个归一化函数在训练标注、预测输出和后处理三处复用def normalize_angle(theta): # 弧度单位范围: [-pi/2, pi/2) theta theta % np.pi if theta np.pi / 2: theta - np.pi elif theta -np.pi / 2: theta np.pi return theta注意角度归一化周期是 π 而不是 2π因为旋转矩形旋转 π 后等价。部署前用同一组测试图片对 Python 和 C 输出做比对如果差值小于 1e-5基本可以排除单位问题。另一个建议是把归一化函数直接 compile 进推理引擎前的后处理模块不要在前后端各写一份逻辑。5. 验证角度回归效果的三个可视化技巧角度回归是否真正学到位不能只看 mAP。我经常遇到模型在水平目标上 mAP 很高、在斜目标上失灵这时候可视化比调参更高效。用验证集上的预测结果画旋转框再结合角度误差直方图可以快速定位问题。5.1 直接用OpenCV把旋转框画出来import cv2 import numpy as np def draw_rotated_box(img, box_xywhr, color(0, 255, 0), thickness2): x, y, w, h, theta box_xywhr cos_t, sin_t np.cos(theta), np.sin(theta) half_w, half_h w / 2, h / 2 corners np.array([ [-half_w, -half_h], [half_w, -half_h], [half_w, half_h], [-half_w, half_h] ]) rot np.array([[cos_t, -sin_t], [sin_t, cos_t]]) box_pts corners rot.T np.array([x, y]) box_pts box_pts.astype(np.int32) cv2.polylines(img, [box_pts], True, color, thickness)这个脚本能同时验证前5个值的含义、宽高顺序和 theta 单位。找一张包含明显旋转目标的图片用这版代码画 ground truth 和预测框如果长边方向与逻辑不符优先怀疑角度定义正方向或 w/h 互换。5.2 用角度误差直方图替换loss曲线训练结束后从验证集统计每个目标的预测 θ 与标注 θ 的差值angle_err (pred_theta - gt_theta) % np.pi angle_err np.where(angle_err np.pi/2, angle_err - np.pi, angle_err)绘制直方图后如果分布是以 0 为中心的高斯峰说明角度头正常。如果出现双峰峰值分别在 0 附近和 ±90 度附近通常是宽高顺序发生了交换。如果整体偏移一个固定角度则是角度定义不一致。这种验证方式比只看 loss 更直接因为 loss 会同时惩罚分类和位置误差难以区分角度问题。5.3 合成一个旋转NMS测试样例为了单独验证旋转NMS是否正确我做了一张合成图两个相同大小的矩形中心相同一个角度 10°另一个角度 -10°。旋转NMS应该保留置信度较高的那个两个都不该被额外删除。如果输出全空说明旋转IoU实现里角度距离处理有误。还可以在这个用例里加一条规则当角度差小于 3° 时直接按完全重合处理避免旋转框浮点计算带来的数值抖动。这个技巧能帮你把 NMS 的 bug 从整个检测系统中隔离出来不用反复跑训练集验证。本文还有配套的精品资源点击获取
