1. 项目概述:从YOLOv8到YOLO26的OBB任务跃迁
最近在做一个无人机航拍船舶识别的项目,客户的需求不仅仅是框出船在哪里,还要求精确地标出船头的朝向和船身的长轴方向,这对于后续的轨迹预测和避碰分析至关重要。传统的水平框检测显然无法满足这种“带角度”的检测需求,这就引出了目标检测中的一个细分领域:旋转目标检测。而OBB正是其核心的数据标注格式。
OBB全称Oriented Bounding Box,即定向边界框。与常规的矩形框用(x_center, y_center, width, height)表示不同,OBB通常用(x_center, y_center, width, height, angle)五个参数来定义一个带旋转角度的矩形。这个angle就是关键,它让检测框能够紧密贴合具有明显方向性的目标,比如航拍中的车辆、船舶、飞机等,极大地减少了背景冗余,提升了后续分析的精度。
为什么选择YOLO26来做这件事?YOLO系列从v5开始就以其极致的工程效率和友好的用户界面著称,到了v8更是集分类、检测、分割于一身。而“YOLO26”这个称呼,更多是社区对Ultralytics公司YOLO系列在2024年迭代版本的一种习惯性称呼,它并非一个官方版本号,你可以将其理解为基于YOLOv8架构,并融合了最新论文思想和工程优化的一个强大分支或最新实践。对于旋转框检测,YOLO26(或者说YOLOv8-OBB)提供了开箱即用的支持,其代码库清晰,文档相对完善,并且继承了YOLO系列训练简单、部署便捷的优良传统,对于我们这种需要快速验证和落地的工程项目来说,是再合适不过的选择了。
这个项目将完整走通一个旋转目标检测的Pipeline:从无人机采集的原始图像开始,进行OBB数据标注,接着准备符合YOLO26格式的数据集,然后修改模型配置以适配OBB任务,启动训练并监控指标,最后对训练好的模型进行验证和推理。我会以无人机航拍船舶为具体案例,把其中的原理、坑点和实操细节掰开揉碎了讲清楚。
2. 核心原理与数据准备:深入理解OBB与数据集构建
2.1 旋转框的数学表达与损失函数演进
要训练模型,首先得明白我们要让模型学习什么。OBB的表示方法主要有两种,这两种方法也直接影响了损失函数的设计。
第一种是OpenCV定义法,也是YOLO26默认采用的。它用(x_center, y_center, width, height, angle)表示。这里的angle是旋转角度,但其定义需要特别注意。在OpenCV的坐标系里,角度通常是指矩形框的长边(width)相对于水平轴(x轴)的夹角,以逆时针方向为正。但具体到不同的实现库(如DOTA_devkit, mmrotate),角度的范围(是[0, 90°)还是[0, 180°))和起始边(是width还是height)可能有细微差别。YOLO26内部会进行处理,以保证训练的一致性。这种表示法直观,但有一个问题:当width和height接近时,或者角度绕180°变化时,同一个物理框可能有两种数值表示,这会给模型回归带来歧义。
第二种是四点表示法,即用矩形四个顶点的坐标(x1, y1, x2, y2, x3, y3, x4, y4)来表示。这种表示没有歧义,但回归参数从5个变成了8个,增加了模型的学习难度,且顶点顺序需要严格一致(通常是顺时针或逆时针)。
YOLO26的OBB损失函数是在传统YOLO损失基础上的扩展。其回归损失通常包含三个部分:
- 中心点损失:计算预测框与真实框中心点的距离,常用CIoU Loss的变体,使得中心点回归得更准。
- 尺寸损失:计算
width和height的差异,通常使用平滑L1损失或与GIoU思想结合的损失。 - 角度损失:这是OBB独有的。最简单的可以用平滑L1损失直接回归角度值。但更优的方法是使用周期性损失函数,比如
Smooth L1 lossonsin(angle)和cos(angle)。因为角度具有周期性(0°和360°等价),直接回归角度值在边界处(如预测1°和真实359°)会产生巨大的损失,而通过回归角度的正弦和余弦值可以完美解决这个问题。YOLO26的代码中通常已经实现了这种更鲁棒的角度的损失计算。
2.2 无人机航拍数据集特性与标注实战
无人机航拍图像有其鲜明的特点,这些特点直接影响了我们数据准备和模型训练的策略:
- 视角独特:俯瞰视角,目标(船舶)的形态、尺度、方向多变。
- 尺度差异巨大:近处的船可能占据图像大半,远处的船则只有几十个像素。
- 背景复杂:水面波纹、光照反射、岛屿、桥梁等干扰多。
- 目标密集:港口场景下船舶可能停靠密集,存在大量遮挡。
对于我们的船舶案例,标注工具首选Roboflow或CVAT。Roboflow的在线平台对OBB标注支持友好,且能一键导出多种格式(包括YOLO OBB格式)。CVAT功能强大,适合本地部署和复杂任务。
标注流程的关键细节:
- 创建项目时务必选择“旋转框”或“Oriented Bounding Box”标注类型。
- 标注时,先确定船体的“长边”。通常将船头到船尾的方向定义为长方形的长边(width),船身的宽度为短边(height)。这样,角度就能明确表示船头的朝向。
- 保持角度的一致性。定义好顺时针为正还是逆时针为正后,整个数据集必须统一。通常,让船头指向右侧时角度为0°,然后逆时针旋转角度增加,这是一个常见的约定。
- 对于被部分遮挡的船舶,尽量标注可见部分,并估算完整轮廓。如果遮挡严重,难以可靠估计,则应舍弃该样本,避免引入噪声。
注意:无人机图像可能存在广角畸变。如果畸变明显,建议先进行镜头校正,再用于标注和训练,否则会影响角度和几何形状的准确性。
2.3 YOLO OBB数据格式详解与脚本准备
YOLO格式的OBB标注文件是一个.txt文件,与图像同名,每一行代表一个目标。每一行的格式为:
class_id xc yc w h angleclass_id: 类别索引(从0开始)。如果只有“船”一类,这里就是0。xc, yc: 旋转框中心点的归一化坐标(除以图像宽度和高度)。w, h: 旋转框的宽度和高度的归一化值(除以图像宽度和高度)。注意:这里的w和h是旋转框自身的宽和高,不是水平外接矩形的。angle: 旋转角度,单位为弧度,范围通常在[-π/2, 0)或[0, π/2),具体取决于实现。YOLO26通常要求角度在[-π/2, 0)范围内,即-90°到0°。这是为了规范表示,避免歧义。
一个具体的例子:假设图像尺寸为1920x1080,图中有一艘船,其旋转框中心在(960, 540),框自身长边(船身方向)为200像素,短边为50像素,船头指向右上方,与水平轴夹角为-30°(顺时针30°)。
- 首先将角度转换为弧度:
-30° * π / 180 ≈ -0.5236 rad。 - 计算归一化值:
xc = 960/1920 = 0.5,yc = 540/1080 = 0.5,w = 200/1920 ≈ 0.1042,h = 50/1080 ≈ 0.0463。 - 标注行即为:
0 0.5 0.5 0.1042 0.0463 -0.5236
数据集目录结构应如下所示:
obb_dataset/ ├── train/ │ ├── images/ # 存放训练图片 .jpg │ └── labels/ # 存放对应的OBB标签 .txt ├── val/ # 验证集,结构同train │ ├── images/ │ └── labels/ └── data.yaml # 数据集配置文件data.yaml文件是核心,内容示例:
path: /home/user/obb_dataset # 数据集根目录 train: train/images # 训练集图像路径,相对path val: val/images # 验证集图像路径,相对path # 类别信息 names: 0: ship我们通常需要编写一个格式转换脚本,将标注工具(如Roboflow导出的JSON或CVAT的XML)转换成上述YOLO OBB格式。这里提供一个Python脚本的大致思路:
import json import os from pathlib import Path import math def convert_roboflow_json_to_yolo_obb(json_path, output_label_dir, img_width, img_height): """ 将Roboflow导出的OBB JSON转换为YOLO OBB格式。 假设Roboflow JSON中每个目标有`points`字段,表示四个角点[x1,y1,x2,y2,x3,y3,x4,y4]。 """ with open(json_path, 'r') as f: data = json.load(f) for item in data: image_filename = item['image'] label_filename = Path(image_filename).stem + '.txt' label_path = os.path.join(output_label_dir, label_filename) with open(label_path, 'w') as lbl_f: for obj in item.get('objects', []): class_name = obj['class'] points = obj['points'] # 四点坐标 # 1. 将四点坐标转换为 (xc, yc, w, h, angle) # 这里需要实现一个四点转旋转矩形的函数,例如使用OpenCV的minAreaRect # rect = cv2.minAreaRect(np.array(points).reshape(4,2)) # (xc_pixel, yc_pixel), (w_pixel, h_pixel), angle_deg = rect # 注意:cv2.minAreaRect返回的angle有特定范围,可能需要转换到YOLO所需范围。 # 2. 归一化 xc_norm = xc_pixel / img_width yc_norm = yc_pixel / img_height w_norm = w_pixel / img_width h_norm = h_pixel / img_height angle_rad = math.radians(angle_deg_converted) # 转换为弧度 # 3. 写入文件 (假设class_id为0) lbl_f.write(f'0 {xc_norm:.6f} {yc_norm:.6f} {w_norm:.6f} {h_norm:.6f} {angle_rad:.6f}\n') # 调用函数处理所有标注实操心得:在转换格式后,务必用可视化脚本检查一遍。写一个简单的脚本,读取图片和对应的
.txt标签,将旋转框画回图像上,确保角度和位置是正确的。这是避免后续训练出现诡异问题的关键一步。
3. YOLO26环境配置与模型结构解析
3.1 从零开始搭建训练环境
训练YOLO26 OBB模型,我推荐使用Python 3.8-3.10和PyTorch 1.12+的版本组合,稳定性最有保障。下面是一套经过验证的环境配置命令:
# 1. 创建并激活虚拟环境(强推,避免包冲突) conda create -n yolo26_obb python=3.9 -y conda activate yolo26_obb # 2. 安装PyTorch(以CUDA 11.8为例,请根据你的显卡驱动去PyTorch官网选择对应命令) pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118 # 3. 克隆Ultralytics YOLO仓库(这里我们使用支持OBB的v8分支) git clone https://github.com/ultralytics/ultralytics.git cd ultralytics pip install -e . # 以可编辑模式安装,方便修改代码 # 4. 安装其他可能需要的依赖 pip install opencv-python pillow matplotlib seaborn pandas pyyaml tqdm pip install albumentations # 用于数据增强验证安装是否成功:
import torch print(torch.__version__, torch.cuda.is_available()) # 应显示版本号和True from ultralytics import YOLO print(YOLO) # 应能成功导入避坑指南:
- CUDA版本匹配:
torch版本必须与你的CUDA驱动版本兼容。用nvidia-smi查看驱动支持的CUDA最高版本,然后去PyTorch官网找对应命令。- Ultralytics版本:确保克隆的是最新代码。OBB功能在较新的版本中才完善。可以直接
pip install ultralytics安装最新版,但为了代码级的自定义,从GitHub克隆更灵活。- 权限问题:在Linux系统下,如果遇到文件操作权限问题,在pip install时可以加上
--user标志,或者使用虚拟环境。
3.2 YOLO26-OBB模型结构框图与改进点解析
YOLO26(基于YOLOv8)的OBB模型结构,其主干网络与检测头部分与标准YOLOv8一脉相承,但为了输出旋转框参数,在检测头部分进行了关键适配。
我们可以将其结构分解为以下几个核心模块:
[输入图像 640x640x3] | v [Backbone: CSPDarknet] | (提取多层次特征图) v [Neck: PANet + FPN] | (特征融合,得到 P3, P4, P5) v [OBB Detection Head] | |--- [Cls Conv] -> 类别预测 (per anchor) |--- [Reg Conv] -> 回归预测 (per anchor) | | | |-- [xy] 中心点偏移量 (2) | |-- [wh] 宽高 (2) | |-- [angle] 角度参数 (通常为2,即sin和cos) (2) | |-- [obj] 目标置信度 (1) | v [输出] OBB参数 (xywh + angle) + 类别概率 + 置信度1. Backbone(主干网络):依旧是CSPDarknet,负责从输入图像中提取丰富的特征。它的跨阶段部分连接结构能有效缓解梯度消失,加强特征传播。
2. Neck(颈部):采用PANet(Path Aggregation Network)与FPN(Feature Pyramid Network)结合的结构。它将深层语义强的特征和浅层位置准的特征进行多尺度融合,生成P3(下采样8倍)、P4(16倍)、P5(32倍)三种不同尺度的特征图,分别用于检测小、中、大目标。这对于无人机航拍中尺度变化剧烈的船舶目标至关重要。
3. OBB Detection Head(检测头):这是适配OBB任务的核心。与普通检测头输出(x, y, w, h, obj, cls...)不同,OBB检测头需要输出角度信息。
- 角度表示:在特征图的通道上,模型通常不直接回归角度弧度值
angle,而是回归sin(angle)和cos(angle)两个值。这样做的好处如前所述,是解决了角度的周期性问题,使得损失函数在0°和360°边界处连续可导。 - 损失计算:在损失函数中,回归损失部分会包含对
sin/cos的约束。同时,在计算旋转框IoU(RIoU)时,也需要使用这五个参数(x, y, w, h, angle)来构造旋转矩形,进行精确的交并比计算。YOLO26的代码库中已经集成了RotatedIoU的计算,作为评估和损失的一部分。
4. 针对无人机场景的潜在改进点:
- 注意力机制:在Backbone或Neck中引入SimAM、EMA等无参或轻量级注意力模块,可以让模型更关注水面上的船舶目标,抑制波浪反光等噪声。
- 小目标检测层:无人机高空拍摄,小目标众多。可以借鉴YOLOv5-P6或YOLOv8-P2的思想,添加一个更浅层、更高分辨率的检测头(例如下采样4倍的特征图P2),专门用于捕捉像素级的小船舶。
- RepVGG风格重参数化:在训练时使用多分支结构增加模型容量,推理时融合成单路VGG式结构,在不增加推理耗时的情况下提升精度。
4. 训练策略、参数调优与完整实战代码
4.1 数据增强策略与配置文件修改
无人机数据有其特殊性,针对性的数据增强能显著提升模型鲁棒性。YOLO26支持通过*.yaml配置文件定义增强。
创建一个obb_ship_aug.yaml,可以在默认基础上修改:
# 继承默认配置,并修改增强参数 augment: true hsv_h: 0.015 # 色调抖动,模拟不同光照 hsv_s: 0.7 # 饱和度抖动,增强色彩鲁棒性 hsv_v: 0.4 # 明度抖动 degrees: 10.0 # 随机旋转角度范围,对OBB很重要!不能太大,否则角度标签会混乱。 translate: 0.1 scale: 0.5 # 缩放,模拟不同距离 shear: 2.0 # 剪切变换 perspective: 0.0001 # 透视变换,模拟视角轻微变化 flipud: 0.01 # 上下翻转概率,航拍图像上下翻转有意义 fliplr: 0.5 # 左右翻转概率,对OBB要小心!需要同步翻转角度标签。YOLO26的OBB代码应已处理。 mosaic: 1.0 # Mosaic增强比例,对小目标数据集非常有效 mixup: 0.1 # MixUp增强比例 copy_paste: 0.1 # 复制粘贴增强,对密集场景有帮助关键点:
degrees(随机旋转)对于水平框检测是利器,但对于OBB任务需要谨慎。因为图像旋转后,框的角度也需要相应旋转。YOLO26的OBB数据加载器应该能正确处理这种几何变换。fliplr(水平翻转)同理,翻转后框的角度会变为-angle。务必确认你使用的代码版本正确实现了这些变换。
接下来是模型配置文件。我们通常不需要从头写,而是修改现有的yolov8-obb.yaml(如果官方提供)或类似文件。核心是修改nc(类别数):
# yolov8n-obb-ship.yaml nc: 1 # 类别数,我们只有'ship' scales: # 模型缩放系数,这里以nano为例 depth_multiple: 0.33 width_multiple: 0.25 backbone: # ... 主干网络结构定义 head: # ... 检测头结构定义,注意输出通道数应与 (nc + 5) 匹配?对于OBB,实际上是 nc + 6 (xywh + sinθ + cosθ + obj)实际上,YOLO26的OBB模型定义可能已经内置,我们更多是通过参数来指定。最关键的步骤是准备正确的data.yaml。
4.2 训练参数深度解析与启动脚本
训练命令是核心。下面是一个详细的训练脚本示例train_obb.py,其中包含了关键参数的解释:
from ultralytics import YOLO import os def main(): # 1. 加载一个预训练模型(强烈推荐) # 使用在COCO等大型数据集上预训练的权重,能加速收敛并提升精度。 # 即使预训练模型是水平框检测,其主干网络的特征提取能力也是可迁移的。 model = YOLO('yolov8n.pt') # 先加载一个标准检测模型 # 2. 训练配置 results = model.train( # 数据配置 data='./obb_dataset/data.yaml', # 指向你的数据集配置文件 # 模型配置 (如果要使用特定的OBB模型结构文件) # cfg='./models/yolov8n-obb.yaml', # 训练超参数 epochs=100, # 迭代轮次,根据数据集大小调整,通常100-300 patience=30, # 早停耐心值,如果验证集指标连续30轮不提升则停止 batch=16, # 批次大小,取决于GPU内存。RTX 4090可尝试32。 imgsz=640, # 输入图像尺寸。可以尝试更大的尺寸如896以检测小目标,但会显著增加显存和耗时。 workers=8, # 数据加载线程数,建议设为CPU核心数左右 device='0', # 使用GPU 0。如果是多卡,可以写 '0,1' 或 'cpu' # 优化器与学习率 optimizer='AdamW', # 可选 'SGD', 'Adam', 'AdamW'。AdamW通常收敛更快。 lr0=0.001, # 初始学习率 lrf=0.01, # 最终学习率因子 (lr0 * lrf) momentum=0.937, # SGD动量 weight_decay=0.0005, # 权重衰减,防止过拟合 # 数据增强 (部分可通过augment参数控制,更细粒度需用augmentation yaml) augment=True, hsv_h=0.015, hsv_s=0.7, hsv_v=0.4, degrees=10.0, flipud=0.01, fliplr=0.5, # 项目与日志 name='yolo26_obb_ship_v1', # 实验名称,用于创建保存目录 project='runs/obb_train', # 项目根目录 exist_ok=True, # 允许覆盖同名实验 save=True, save_period=10, # 每10个epoch保存一次检查点 val=True, # 每个epoch后验证 plots=True, # 训练结束后生成指标图表 # OBB相关关键参数 (如果YOLO26版本支持) mode='obb', # 指定任务模式为OBB!这是最关键的一步。 # rotate=90, # 有些实现可能需要这个参数来启用旋转框训练 verbose=True, # 打印详细日志 ) print("训练完成!最佳模型保存在:", results.best) if __name__ == '__main__': main()关键参数解读与调优建议:
imgsz: 无人机图像通常分辨率很高(如4K)。直接下采样到640会丢失大量小目标细节。如果显存允许,尝试增大到896甚至1024,这对小目标检测精度提升明显。可以采用渐进式调整,先用640训练一个基准,再用更大尺寸微调。batch: 在显存允许范围内尽可能调大。更大的Batch Size能使梯度估计更稳定,可能允许使用更大的学习率。如果遇到OOM(内存不足),可以尝试梯度累积(YOLO训练命令可能通过accumulate参数支持),模拟大批次效果。lr0: 学习率是灵魂。对于微调(使用预训练权重),1e-3是个不错的起点。如果是从头训练,可以尝试更小的值如5e-4。使用学习率热身(warmup)和余弦退火(cosine)调度策略通常是默认且有效的。patience: 早停参数。如果验证集损失或mAP在patience个epoch内没有改善,训练将停止,并恢复到最后的最佳模型。防止过拟合的利器。mode='obb':这是开启旋转框训练模式的开关。务必确认你使用的Ultralytics版本支持这个参数。如果不支持,可能需要寻找专门支持OBB的分支或修改源代码。
4.3 训练过程监控与指标解读
启动训练后,控制台会输出日志,同时会在runs/obb_train/yolo26_obb_ship_v1目录下生成一系列文件:
weights/: 存放最佳模型best.pt和最后模型last.pt。args.yaml: 本次训练的所有参数备份。results.csv: 每个epoch的详细指标记录。confusion_matrix.png: 混淆矩阵。results.png: 综合指标曲线图,这是最重要的监控图表。
需要重点关注的指标:
- 损失曲线:
train/box_loss: 训练集回归损失(包含中心点、宽高、角度)。train/cls_loss: 训练集分类损失。train/dfl_loss: 分布焦点损失(如果使用)。val/box_loss,val/cls_loss: 验证集上的对应损失。- 正常情况:训练损失稳步下降,验证损失先降后趋于平稳或缓慢上升。如果验证损失很早就开始上升,说明过拟合了,需要加强数据增强、减少模型复杂度或增加正则化。
- 性能指标:
metrics/mAP50(B): 在IoU阈值为0.5下的平均精度(mAP),这是主要评估指标。metrics/mAP50-95(B): IoU阈值从0.5到0.95(步长0.05)的平均mAP,更严格的指标。- 对于OBB,可能还会有
metrics/mAP50(R)或metrics/mAP50-95(R),其中的(R)代表使用旋转框IoU(RIoU)进行计算,这才是衡量OBB模型性能的核心指标。务必确认你的评估使用的是RIoU。
- 学习率曲线:可以看到学习率按照预定的调度策略(如余弦退火)变化。
监控心得:不要只看最后的mAP。训练初期,观察损失是否快速下降;中期,关注验证集损失是否平稳;后期,对比
mAP50和mAP50-95。如果mAP50高但mAP50-95很低,说明模型对框的位置精度要求不高,可能框不够紧致,需要检查角度回归是否准确,或者RIoU计算是否正确。
5. 模型验证、推理部署与常见问题排查
5.1 模型验证与性能评估脚本
训练完成后,我们需要在独立的测试集上评估模型的真实性能。YOLO26提供了方便的验证接口。
from ultralytics import YOLO import cv2 def validate_and_visualize(): # 1. 加载训练好的最佳模型 model = YOLO('./runs/obb_train/yolo26_obb_ship_v1/weights/best.pt') # 2. 在验证集上进行评估,获取详细指标 metrics = model.val( data='./obb_dataset/data.yaml', split='val', # 使用验证集 batch=16, imgsz=640, conf=0.001, # 评估时使用的置信度阈值,设低些以召回所有可能目标 iou=0.6, # NMS用的IoU阈值 device='0', plots=True, # 生成评估图表,如PR曲线 save_json=True, # 保存评估结果为JSON文件,便于分析 # 对于OBB,确保任务模式正确 task='obb' ) # 打印关键指标 print(f"mAP50-95 (OBB): {metrics.box.map}") # 注意属性名可能随版本变化 print(f"mAP50 (OBB): {metrics.box.map50}") print(f"Precision: {metrics.box.precision}") print(f"Recall: {metrics.box.recall}") # 3. 单张图片推理与可视化 img_path = './obb_dataset/val/images/sample_001.jpg' results = model(img_path, imgsz=640, conf=0.25, iou=0.45) # 获取第一个结果(单张图片) result = results[0] # 绘制带旋转框的检测结果 plotted_img = result.plot(conf=True, labels=True, boxes=True) # 注意:标准plot方法可能只画水平框 # 对于OBB,我们需要自定义绘制或使用result.obb # 检查结果中是否有OBB属性 if hasattr(result, 'obb'): obb_info = result.obb print("检测到的旋转框信息:", obb_info) # obb_info 可能包含 xywhr (x_center, y_center, width, height, angle_radians) # 需要自己用OpenCV的cv2.boxPoints()和cv2.drawContours()来绘制旋转矩形 # 保存或显示结果 cv2.imwrite('result_with_obb.jpg', plotted_img) # cv2.imshow('OBB Detection', plotted_img) # cv2.waitKey(0) if __name__ == '__main__': validate_and_visualize()自定义OBB结果可视化函数: 由于标准的result.plot()可能不支持旋转框,我们需要自己绘制:
import cv2 import numpy as np def draw_obb_on_image(image, obb_tensor, color=(0, 255, 0), thickness=2): """ 在图像上绘制旋转框。 obb_tensor: [n, 5] 或 [n, 6] 的Tensor,每行 (xc, yc, w, h, angle_rad, [conf])。 坐标是归一化的。 """ img_h, img_w = image.shape[:2] for box in obb_tensor: if len(box) == 6: xc_n, yc_n, w_n, h_n, angle_rad, conf = box else: xc_n, yc_n, w_n, h_n, angle_rad = box conf = None # 将归一化坐标转换为像素坐标 xc = int(xc_n * img_w) yc = int(yc_n * img_h) w = int(w_n * img_w) h = int(h_n * img_h) angle_deg = angle_rad * 180 / np.pi # 弧度转角度 # 计算旋转矩形的四个顶点 rect = ((xc, yc), (w, h), angle_deg) box_points = cv2.boxPoints(rect) # 返回4个点 box_points = np.int0(box_points) # 绘制旋转矩形 cv2.drawContours(image, [box_points], 0, color, thickness) # 可选:绘制角度方向(例如从中心指向长边方向) # 计算方向向量 major_axis_length = max(w, h) / 2 dx = major_axis_length * np.cos(angle_rad) dy = major_axis_length * np.sin(angle_rad) end_point = (int(xc + dx), int(yc - dy)) # 注意图像y轴向下 cv2.arrowedLine(image, (xc, yc), end_point, (255, 0, 0), 2) # 可选:显示置信度 if conf is not None: label = f'{conf:.2f}' cv2.putText(image, label, (int(box_points[0][0]), int(box_points[0][1])-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 1) return image5.2 模型导出与部署考量
训练好的.pt模型是PyTorch格式,用于部署时需要转换成相应格式。
1. 导出为ONNX:
yolo export model=./runs/obb_train/yolo26_obb_ship_v1/weights/best.pt format=onnx imgsz=640 simplify=Truesimplify=True: 应用ONNX Simplifier优化计算图,可能减少节点。- 导出后,务必用
Netron工具打开生成的.onnx文件,检查输入输出节点是否正确,特别是输出维度是否包含角度信息(例如[batch, num_anchors, nc+6])。
2. 部署到不同平台:
- TensorRT (NVIDIA GPU):这是追求极致性能的首选。使用
trtexec或Python的torch2trt、onnx-tensorrt工具将ONNX转换为TensorRT引擎。需要特别注意插件支持,YOLO中的某些操作(如GridSample、Einsum)可能需要特定版本的TensorRT或自定义插件。 - OpenVINO (Intel CPU/GPU):对于Intel硬件部署,OpenVINO是优化利器。使用OpenVINO的Model Optimizer将ONNX转换为IR格式,再利用Runtime推理。
- RKNN (Rockchip NPU):对于嵌入式设备如RK3588,需要将ONNX转换为RKNN格式。瑞芯微提供了RKNN Toolkit2工具链。转换过程中可能需要对模型结构做适配(如修改某些不支持的算子)。
- CoreML (Apple Silicon):对于iOS/macOS部署,可导出为CoreML格式。Ultralytics可能支持直接导出,或通过ONNX再转CoreML。
部署核心挑战:旋转框的后处理。模型原始输出是密集的预测张量,需要经过非极大值抑制(NMS)来筛选最终框。对于水平框,NMS使用IoU;对于旋转框,必须使用旋转框IoU(RIoU)。许多部署框架(如TensorRT)的标准NMS插件不支持RIoU计算。解决方案通常有两种:1)将RIoU NMS作为自定义插件实现;2)在模型导出前,将后处理(包括解码和NMS)封装进ONNX图中(称为
end2end导出)。YOLO26的导出功能可能支持后者,但这会增加图的复杂性并降低移植性。更常见的做法是在推理代码中自己实现RIoU NMS。
5.3 常见问题、排查技巧与实战心得
在训练和部署YOLO26 OBB模型的全流程中,我踩过不少坑,这里总结一份速查表:
| 问题现象 | 可能原因 | 排查与解决方案 |
|---|---|---|
| 训练损失不下降或为NaN | 1. 学习率lr0设置过高。2. 数据标注格式错误,特别是角度值超出预期范围(如不是弧度制)。 3. 数据中存在损坏的图片或标签文件。 4. 梯度爆炸。 | 1. 将lr0降低一个数量级(如从1e-3降到1e-4)重试。2.使用可视化脚本检查标签,确保角度值合理(通常在 [-π/2, 0)或[0, π/2))。3. 运行数据加载检查脚本,确保所有文件能正常读取。 4. 添加梯度裁剪( gradient_clip_val参数)。 |
| 验证集mAP很低,但训练集损失正常 | 1. 严重过拟合。 2. 验证集和训练集分布差异大。 3. 验证时评估指标不是RIoU。 | 1. 增强数据增强(mosaic, mixup, copy_paste),增加权重衰减weight_decay,使用早停patience。2. 检查数据划分是否随机、均匀。确保验证集有代表性。 3.确认验证评估使用的是旋转框IoU。检查 model.val()的参数或源码。 |
| 模型预测的框都是水平的(角度为0) | 1. 训练时未正确启用OBB模式(缺少mode='obb'参数)。2. 损失函数中角度部分的权重太低或未生效。 3. 数据集中所有目标角度都接近0(标注错误)。 | 1.这是最常见的原因!确认训练命令中包含了OBB模式参数。 2. 检查模型配置文件,确保检测头输出通道数包含角度参数(sin/cos)。 3. 可视化训练集标签,检查角度分布。 |
| 小目标检测效果差 | 1. 输入图像尺寸imgsz太小。2. 数据集中小目标样本不足。 3. 默认锚框(Anchor)尺寸不匹配。 | 1.增大imgsz(如从640到896)。2. 对小目标进行过采样,或使用Mosaic增强(能天然增加小目标上下文)。 3. 在数据集上重新聚类生成锚框尺寸(YOLO通常自适应,但可手动验证)。 |
| 导出的ONNX/TensorRT模型推理结果错误 | 1. 导出时输入/输出节点不正确。 2. 后处理(解码、NMS)未正确集成或实现。 3. 框架间算子不兼容或精度差异。 | 1. 用Netron可视化ONNX,对比PyTorch推理的输入输出形状和数值(可用torch.onnx.export的input_names,output_names参数)。2.重点:确认旋转框的解码和RIoU NMS在目标平台上正确实现。考虑使用 end2end方式导出,或将后处理单独实现。3. 尝试在导出时设置 opset_version,或简化模型结构。 |
| 角度预测存在180度歧义 | 这是OBB固有的表示歧义问题。同一个矩形,用(w, h, θ)和(h, w, θ-90°)表示是等价的。 | 1. 在训练前对标签进行规范化,强制规定长边为width,角度限定在特定范围(如[-π/2, 0))。2. 在损失函数中加入对 w > h的约束(宽高比损失)。3. 在后处理中,对预测结果进行同样的规范化。 |
最后的个人体会:成功训练一个鲁棒的YOLO26 OBB模型,数据质量占七成,训练调参占三成。在船舶检测这个案例里,花费最多时间的不是写代码,而是清洗数据、修正标注、设计合适的数据增强流程。尤其是角度标注的一致性,需要反复检查。另外,不要迷信默认参数,无人机场景下的最优imgsz、degrees增强幅度都需要通过实验来摸索。模型部署是另一个战场,特别是RIoU NMS的实现,需要你对目标平台的计算特性有深入了解。先从PyTorch原生的推理脚本跑通整个流程,确保算法逻辑正确,再逐步攻坚各个部署平台的适配问题,这样会稳很多。