资讯动态

YOLOv5 OBB旋转框训练实战:角度定义、DOTA滑窗与NMS评测

发布时间:2026/10/27 13:06:02 来源:尧图企业网站定制
简介面向需要处理倾斜目标检测的开发者与算法学习者这份YOLOv5 OBB旋转框训练demo在原生YOLOv5基础上引入带方向角的旋转框标注与训练流程可用于遥感影像、车辆、树木等非矩形目标的定位实践适合具备一定深度学习基础、希望从水平框迁移到OBB的中级读者。压缩包共583个文件约445.58MB包含71个Python脚本、38个YAML配置、166个XML标注、95个TXT标签及93张PNG与23张JPG示例图另有预训练权重、ONNX模型、CUDA算子与部署脚本等覆盖数据处理、参数配置、训练评估与模型导出的完整链路。已有1822人学习下载说明该示例在旋转框任务上具备一定参考价值。读者可据此复现OBB标注转换、旋转框损失计算、验证可视化与推理部署并对照目录结构梳理工程组织与排错思路对理解方向敏感型目标检测的工程实现颇有帮助。1. 从一张飞机图说起为什么水平框在这里是错的跑道边的飞机用水平框框出来框里一半是跑道、一半是旁边的机库密集停放的车辆用水平框一框几个框叠在一起NMS 一压剩下的框和真实目标对不上。航拍和遥感这一类俯视图里目标的朝向分布极其分散水平框HBB的表达能力一开始就不够用。这个yolov5 obb 旋转框训练 demo就是干这件事的在 YOLOv5 的检测头上增加一个角度分支把输出从(cx, cy, w, h)扩展成(cx, cy, w, h, θ)再配套 DOTA 格式的数据流水线、旋转框的 NMS 和后处理形成一个能训练、能推理、能出结果的完整源码包。它适合两类人一类是手里已经有 DOTA 或类似八点标注数据、想快速跑通一条旋转框 baseline 的另一类是做过水平框检测、想搞清楚角度回归到底难在哪的。下面按我拆包复现的顺序写从角度定义一路写到评测合并。2. 角度怎么定三种旋转框表示、一个转换函数和两次自检旋转框比水平框多出来的那个 θ是所有坑的源头。一个矩形在几何上只有一种形状但写成(cx, cy, w, h, θ)却有无数种等价形式θ 加 180 度是同一个框θ 加 90 度同时把 w 和 h 交换也是同一个框。只要标注、数据加载、损失计算、后处理这四个环节里有两个用了不同的约定训练就会变成在拟合一个周期函数loss 曲线看着在降mAP 死活上不去。2.1 OpenCV 定义、le90 与 le135 为什么会打架先把三种常见约定摆出来。OpenCV 的cv2.minAreaRect返回的(w, h)顺序是不保证的它可能把短边放在 w 上θ 的范围也会随版本变化le90 把长边固定为 wθ 落在[-90, 0)le135 同样固定长边为 w但 θ 落在[-45, 135)。这三个定义单独用都没问题混用就出事。定义θ 范围w / h 约定典型来源OpenCVoc随版本浮动常见 [0, 180)不保证 w ≥ hcv2.minAreaRect直接返回le90[-90, 0)w 为长边部分旋转检测库le135[-45, 135)w 为长边部分标注工具与 OBB 实现更隐蔽的一层是标注工具。有些旋转框标注工具在 XML 里存的是弧度导出成 txt 时才转成度如果转换脚本直接float()读进来当度用1.57 度的角度看起来合理实际是 90 度。这种错不会报异常只会让模型学到一个恒定的偏置。这个 demo 走的是 OpenCV 定义这条路统一把长边放到 w 上θ 归一化到[0, 180)。选它的理由是转换链路最短minAreaRect一步就能从八点标注算出旋转框不需要额外的分支判断。代价是 θ 在 0 和 180 附近存在边界跳变这一点后面在角度损失那节还会提到。我一般会先把这个约定写进项目的 README改代码的人在动 angle 相关逻辑前必须先看一眼。2.2 从八点到五点poly2rbox 的写法与归一化口径DOTA 格式的标注是八个数x1 y1 x2 y2 x3 y3 x4 y4 类别名 difficult四个角点按顺时针或逆时针排列。要喂给 YOLO得先算最小外接矩形。下面这段是整个数据链路里最不能出错的一段import cv2 import numpy as np def poly2rbox_oc(poly): 八点标注 - OpenCV 定义旋转框 poly: [x1,y1,x2,y2,x3,y3,x4,y4] 返回: (cx, cy, w, h, theta)theta 单位为度范围 [0, 180) pts np.asarray(poly, dtypenp.float32).reshape(4, 2) (cx, cy), (w, h), theta cv2.minAreaRect(pts) # minAreaRect 不保证 w h统一把长边放到 w 上角度同步 90 if w h: w, h h, w theta 90.0 theta theta % 180.0 # 归一化到 [0, 180) return cx, cy, w, h, theta def rbox2poly(cx, cy, w, h, theta): 旋转框 - 八点后处理可视化时要用 t np.deg2rad(theta) dx, dy w / 2.0, h / 2.0 corners np.array([[-dx, -dy], [dx, -dy], [dx, dy], [-dx, dy]], dtypenp.float32) R np.array([[np.cos(t), -np.sin(t)], [np.sin(t), np.cos(t)]], dtypenp.float32) return (corners R.T np.array([cx, cy], dtypenp.float32)).reshape(-1)poly2rbox_oc里if w h那个分支是关键交换 w 和 h 的同时必须给 θ 加 90 度否则框会转向90 度形状对但朝向错。theta % 180.0负责兜住minAreaRect在个别版本里返回的负角度。rbox2poly则是反向操作先以中心为原点算四个角再旋转平移回去注意这里用的是corners R.T转置写反了框会镜像。归一化口径要在这一层就定死。常见做法是中心点和宽高都除以图幅尺寸角度除以 180全部落到[0, 1)区间。角度除以 180 的好处是输出分支可以直接用 sigmoid不用处理负值坏处是 0 和 1 是同一个物理角度边界处依然会跳。这是个取舍不是银弹。2.3 用一趟往返转换自检标注转换逻辑写完别急着开训先花十分钟做往返校验。原理很简单如果poly → rbox → poly → rbox两轮的结果一致说明转换是无损的如果不一致说明有分支没走对。def check_roundtrip(poly, tol1e-3): 两次转换结果必须一致角度按 180 度周期比较 cx, cy, w, h, th poly2rbox_oc(poly) cx2, cy2, w2, h2, th2 poly2rbox_oc(rbox2poly(cx, cy, w, h, th)) assert abs(w - w2) tol and abs(h - h2) tol, 边长来回变了检查 w/h 交换分支 assert abs(cx - cx2) tol and abs(cy - cy2) tol, 中心点漂了 diff abs(((th - th2 90.0) % 180.0) - 90.0) assert diff tol, f角度不自洽: {th} vs {th2} # 顺手扫一遍训练集的标注把异常样本挑出来 bad 0 for line in open(/data/DOTA/train/labelTxt/0001.txt, encodingutf-8): parts line.strip().split() if len(parts) 10: continue try: check_roundtrip(list(map(float, parts[:8]))) except AssertionError as e: bad 1 print(parts[8], e) print(异常样本数:, bad)跑完之后如果异常样本数不是 0八成是碰到了三个问题里的一个八点顺序不是凸多边形顺序minAreaRect会算出一个错的框、某个点的坐标是-1之类的占位符、或者标注里有极扁的长条w 或 h 接近 0数值上不稳定。这三种我都会直接从训练集里剔掉宁可少几百个样本也别让模型去学一个自相矛盾的目标。提示往返校验对角度的容差不要设成 0浮点运算在 0 和 180 的边界处一定会给你一点误差按 180 周期比较才是对的做法。3. 数据流水线把 8000 像素的大图切成 1024 的滑窗并转成五点标注DOTA 的原图动辄几千甚至上万像素直接缩到 1024 训练一艘船会变成一个像素点。所以这条流水线的标准动作是先把大图滑窗切成 1024 的小片再在小片上训练。切图这一步看起来只是裁一裁实际上split_size、gap、thresh三个参数直接决定了模型能学到什么。3.1 环境先对齐torch、CUDA 和编译扩展这个 demo 不是纯 Python 能跑完的旋转框的 NMS 和部分框 IoU 计算依赖编译扩展所以环境必须一次装对。我一般按这个顺序来# 1. 先确认驱动和 CUDA 运行时版本后面所有版本号都要跟这个对齐 nvidia-smi # 2. 装 torch 时显式指定 cuda 版本不要让它自己猜 pip install torch1.13.1cu117 torchvision0.14.1cu117 \ --extra-index-url https://download.pytorch.org/whl/cu117 # 3. 编译旋转框扩展前先告诉编译器要为目标卡的算力编译 # 30 系消费卡是 8.6A100 是 8.0T4 是 7.5按实际改 export TORCH_CUDA_ARCH_LIST8.6 cd utils/nms_rotated python setup.py build_ext --inplace cd ../.. # 4. 验证扩展能 import且能对两个旋转框算出 IoU python -c from utils.nms_rotated import nms_rotated; print(ok)第三条最容易翻车。TORCH_CUDA_ARCH_LIST不设的话编译器会把一堆老架构都编一遍编译时间翻好几倍而且在部分环境下会直接因为算力参数不识别而中断。另外 gcc 版本别太高我遇到过 gcc 11 编 torch 1.13 的扩展报一堆模板错误降到 gcc 9 就好了。这条属于玄学范畴但确实有规律扩展的编译器和编译 torch 时的编译器差太多就容易出问题。3.2 滑窗切图split_size、gap、thresh 三个参数怎么定切图用 DOTA_devkit 那套就行核心是splitbaseimport sys sys.path.append(./DOTA_devkit) from DOTA_devkit.ImgSplit_multi_process import splitbase if __name__ __main__: basepath /data/DOTA/train/ # 原始大图 images/ labelTxt/ outpath /data/DOTA/train_split/ # 切完的输出目录 split splitbase(basepath, outpath, split_size1024, # 单个切片的边长 gap200, # 相邻切片的重叠像素 thresh0.7, # 目标被裁后剩余面积占比的保留阈值 num_process8) # 多进程数按核数给 split.splitdata(1) # rate1 表示不做采样全量切三个参数的取值逻辑分别是这样split_size跟训练分辨率绑死。设成 1024 就用--imgsz 1024训设成 800 就用 800两边不一致的话坐标缩放会引入额外的插值误差小目标首当其冲。我不建议为了省显存把split_size调到 512那样一辆车在图上就十几个像素再多的角度分支也救不回来。gap是重叠区目的是别让目标正好卡在切片缝上被切成两半。经验值是gap ≥ 最大目标尺寸的一半。机场跑道这种超长目标切图之后必然还是断的那就在数据集层面单独处理别指望靠调 gap 解决。我一般用 200512 的切片配 100。thresh控制被裁目标的取舍。一个目标被切片边缘裁掉之后如果剩余面积占原面积的比例低于这个值就直接丢掉不标高于这个值就把裁剩的那部分单独作为一个目标保留下来。设得高比如 0.9会丢掉大量边缘样本设得低比如 0.3会留下一堆半截目标让模型困惑。0.7 是个比较稳的中间值。切完之后文件名会带上偏移量形如P0001__1024__0___0.png标注文件同名放在labelTxt/下坐标已经转换成相对于切片的局部坐标这一步 devkit 帮你做了不用自己减。3.3 转成五点标注并生成数据集配置切片的标注还是八点格式要转成cls cx cy w h theta的归一化五点import os from tqdm import tqdm CLASSES [plane, ship, storage-tank, baseball-diamond, tennis-court, basketball-court, ground-track-field, harbor, bridge, large-vehicle, small-vehicle, helicopter, roundabout, soccer-ball-field, swimming-pool] def convert_one(txt_path, img_w, img_h, img_h, out_path): python def convert_one(txt_path, img_w, img_h, out_path): lines [] with open(txt_path, r, encodingutf-8) as f: for raw in f: parts raw.strip().split() if len(parts) 10: # 少于 8 个坐标 类别 difficult 的行直接跳过 continue poly list(map(float, parts[:8])) cls_name parts[8] if cls_name not in CLASSES: # 类别表外的目标含 difficult 之外的杂类丢弃 continue cx, cy, w, h, th poly2rbox_oc(poly) # 中心点与宽高按图幅归一化角度按 180 归一化全部落到 [0,1) lines.append({} {:.6f} {:.6f} {:.6f} {:.6f} {:.6f}.format( CLASSES.index(cls_name), cx / img_w, cy / img_h, w / img_w, h / img_h, th / 180.0)) with open(out_path, w, encodingutf-8) as f: f.write(\n.join(lines))归一化这一步有个细节宽高分别除以img_w和img_h不要两个都除以边长。切片是正方形的时候两者相等看不出问题一旦换成非正方形的输入尺寸框就会整体变形而这个错误在正方形测试里永远不会暴露。角度除以 180 之后回归目标落在[0, 1)输出分支配 sigmoid这是自洽的一套。数据集配置文件长这样# data/dota_split.yaml train: /data/DOTA/train_split/images val: /data/DOTA/val_split/images nc: 15 names: [plane, ship, storage-tank, baseball-diamond, tennis-court, basketball-court, ground-track-field, harbor, bridge, large-vehicle, small-vehicle, helicopter, roundabout, soccer-ball-field, swimming-pool]注意train和val指向的是图片目录标注目录由代码按约定去找同级的labels/具体路径规则看 demo 里datasets.py怎么拼的不同分支写法不一样别照着别的仓库的习惯套。建完之后抽三张图跑一下可视化把预测前的 ground truth 框画出来确认框的形状和朝向跟原图对得上再看 anchor。产物路径用途切片图train_split/images/*.png训练输入五点标注train_split/labels/*.txt训练标签数据集配置data/dota_split.yaml路径与类别原始大图/data/DOTA/train/images最终评测前的结果合并4. 改头与开训OBB 头多出的那一维、hyp 里的 angle 权重和日志判读4.1 模型结构输出通道从na*(5nc)变成na*(6nc)YOLOv5 的检测头每个 anchor 输出5 nc维四个框参数、一个置信度、nc 个类别概率。加了旋转框之后变成6 nc多出来的那一维就是角度。改动的位置集中在三处models/yolo.py里 head 的输出通道数、Detect层里 decode 时的框解析逻辑、以及损失函数里角度项的构造。配置文里通常要显式打开一个开关形状类似# models/yolov5s_obb.yaml 的 head 部分 head: [[-1, 1, Conv, [512, 1, 1]], [-1, 1, nn.Conv2d, [na * (nc 6), 1, 1]], # 注意是 nc 6不是 nc 5 ]na是每个特征层的 anchor 数量默认 3。如果 demo 为了提升角度分辨率改成了 4 组 anchor这里的na和 anchor 配置文件必须同步改漏改一个就会在 decode 的时候维度对不上报的错还很绕形如 reshape 失败第一次遇到能查半天。加载预训练权重时日志里会打印Transferred xxx/xxx items数字不是全量是对的——主干和 neck 能对上head 的最后一层因为通道数变了会被跳过。这是预期行为不用管。4.2 hyp 参数angle 权重、degrees 增强和 mosaic超参文件是训练能不能收敛的第二道关。关键几项# data/hyps/hyp.rotate.yaml 关键项 lr0: 0.01 # 初始学习率OBB 头是随机初始化的别一上来就 0.1 lrf: 0.2 # 余弦退火终点比例 box: 0.05 # 框回归损失权重 cls: 0.5 # 分类损失权重 obj: 1.0 # 置信度损失权重 angle: 0.5 # 角度损失权重本文档新增项 degrees: 0.0 # 旋转增强角度上限OBB 任务里通常关掉 mosaic: 0.5 # mosaic 增强概率 flipud: 0.0 # 上下翻转航拍图翻转后朝向分布会变谨慎开 fliplr: 0.5 # 左右翻转OBB 下要同步镜像角度 scale: 0.5 # 缩放增强幅度angle这个权重是最需要调的。设太小0.05模型会偷懒把所有框的角度预测成 0因为这样框回归的 loss 反而更低设太大2.0角度分支会主导梯度框的位置和大小精度掉下去训练后期 loss 抖得厉害。0.5 附近是个常见的起点区间我一般从 0.3 开始看角度分支收敛情况再往上推。degrees关掉是血泪经验。旋转增强在 OBB 任务里有两个麻烦一是图像仿射变换的时候八点多边形必须跟着做同样的变换任何一个分支漏掉标签就飞到画面外了二是角度回归目标在大角度处会跨过 0/180 边界sigmoid输出在这个边界上是个断崖loss 会跳。所以先把degrees: 0.0跑通一轮拿到 baseline再考虑 5 到 10 度的小幅度增强。flipud建议关掉。航拍图的朝向分布和拍摄角度强相关上下翻转会造出真实场景里不存在的朝向组合。fliplr可以开但代码里必须把角度 θ 映射成180 - θ这个映射没写的话标签就是错的而且错得很隐蔽——框的形状对朝向镜像了。参数常用取值调大之后会发生什么angle0.3 ~ 0.8角度更准但位置精度可能下滑后期 loss 抖动加剧degrees0.0 ~ 10.0泛化略好但需要标签同步变换边界角度处 loss 不稳mosaic0.3 ~ 0.5小目标召回提升长条形目标被拼断的概率上升scale0.3 ~ 0.7尺度鲁棒性提升小目标进一步变小box0.03 ~ 0.06定位更准但训练前期收敛慢4.3 启动训练与日志判读启动命令大致如下参数名在不同分支里可能差一两个字用--help对一下python train.py \ --cfg models/yolov5s_obb.yaml \ --weights weights/yolov5s.pt \ --data data/dota_split.yaml \ --hyp data/hyps/hyp.rotate.yaml \ --epochs 120 \ --batch-size 8 \ --imgsz 1024 \ --workers 8 \ --device 0 \ --name dota_s_1024--imgsz 1024必须和切图时的split_size一致--batch-size 8是 1024 分辨率下 24G 显存能塞下的典型值显存不够优先降 batch 而不是降分辨率--weights给的是 ImageNet 或 COCO 预训练的主干head 部分会自动跳过。训起来之后盯四个数box_loss、angle_loss、cls_loss、obj_loss。健康的曲线是前三个在 20 个 epoch 内明显下降angle_loss降得比box_loss慢这是正常的——角度的梯度信号本来就弱。如果angle_loss从第一个 epoch 就贴在 0.69 附近不动0.69 约等于ln2对应二分类瞎猜的熵说明角度分支根本没拿到梯度回去查数据集里 θ 那一列是不是全 0或者归一化除错了分母。另一个要看的指标是验证集上的 mAP。旋转框任务的 mAP 上升比水平框慢前 30 个 epoch 可能一直贴着 0这不一定是有问题可能只是角度还没学出来。但如果在切图后的验证集上画出来的预测框形状明显不对框比目标大一圈、或者朝向整体偏 90 度那就别等了直接停下来查数据链路。5. 避坑与排查五类「能跑但不对」的现象怎么定位训练脚本不报错、loss 也在降、但结果就是不能用——这类问题最消耗时间。下面五条是我在这个 demo 上实际踩过的按定位顺序排。5.1 angle_loss 不动预测框全部水平现象训练日志里angle_loss从第一个 epoch 开始就贴着 0.69 上下微动几十个 epoch 过去还是这个位置。推理出来的框全都是水平的形状和位置反而挺准。原因角度分支拿不到有效梯度。最可能是数据集里 θ 那一列全 0 或者全 0.5也就是归一化的时候角度没写上或者除错了。次可能是poly2rbox_oc里那个if w h分支把角度吃掉了导致所有样本的角度都落在极窄的一段区间里模型学一个常数就够了。解决随机抽一百行训练标注把 θ 还原成角度打印出来看分布正常应该是几乎铺满[0, 180)的。如果分布集中在一小段回到转换脚本单步调试拿一个已知角度的框喂进去看输出对不对。另外检查models/yolo.py里的 decode 逻辑确认角度那一维的下标没有被框参数挤掉。5.2 验证集 mAP 接近 0但可视化看着挺准现象训练完在验证集上算 mAP结果不到 0.01可是把预测结果画到图上框位置和朝向肉眼看着都对。原因评测流程错了跟模型无关。旋转框的评测要求先把各切片的预测结果映射回原图坐标并合并再做评测直接在切片上算 mAP坐标尺度对不上全判错。另一个常见原因是类别 id 的映射顺序和评测脚本里的类别顺序不一致第 0 类和第 3 类互换了算出来自然一片零。解决先单图验证拿一张切片跑推理把rbox2poly转出来的四点画到原图上跟自己眼睛对一遍。这一步过了再检查结果合并脚本有没有正确使用文件名里的偏移量把局部坐标加回去。类别顺序就打印一下data.yaml里的 names 列表和评测脚本里的类别列表逐项对齐。5.3 打开 mosaic 之后框位置整体偏移现象mosaic: 0.0的时候训练正常改成 0.5 之后前几个 epoch 的box_loss暴涨训一段时间能恢复一点但精度明显不如关掉 mosaic。原因mosaic 会把四张图拼成一张同时做随机缩放和裁剪标准 YOLOv5 里这一步只变换了 HBB 的四个坐标。旋转框的八点或者五点在拼接时必须逐点做同样的仿射变换中心点好算角度和边长要重新从变换后的四点里用minAreaRect反算。很多分支在改 OBB 的时候忘了改 mosaic 那段标签还是按 HBB 的方式缩放框就整体偏了。解决把 mosaic 那段代码单独抽出来写个单测——构造两个已知的旋转矩形做一次确定性的平移缩放把变换后反算出来的框和手工计算的期望值对比。这一步做完基本能定位到是漏了平移还是漏了缩放。5.4 编译旋转框扩展时直接报错现象python setup.py build_ext --inplace跑到一半报一堆模板实例化错误或者编完了 import 时报undefined symbol。原因编译器版本和 torch 的 CUDA 版本不匹配或者TORCH_CUDA_ARCH_LIST没设导致编译器试图为不支持的算力架构生成代码。解决先nvcc --version和python -c import torch; print(torch.version.cuda)对一下两个版本主线要一致。设好TORCH_CUDA_ARCH_LIST30 系卡是 8.6A100 是 8.0T4 是 7.5。gcc 版本降到 9 或 10。三个都对齐之后清掉build/目录重新编别在旧的中间产物上接着编。5.5 小目标几乎全丢现象大目标飞机、跑道的召回不错小车、小船这类小目标基本检测不到。原因三种可能。切图时gap太小小目标正好卡在切片缝上被切掉anchor用的还是 COCO 的默认值跟 DOTA 的尺度分布差了一个数量级或者thresh设得太高被裁的小目标因为剩余面积比例不够被整批丢弃。解决把gap提到 200 以上或者按最大目标尺寸的一半来定。用autoanchor在切好的数据集上重聚类一遍 anchor把结果固化到配置里。thresh降到 0.5 试试看召回有没有改善。这三步按顺序做一次只动一个不然改完不知道是哪个起了作用。6. 验收与进阶单图可视化、rotate NMS 调参与合并评测拿到训完的权重先别急着跑全量评测用单图可视化做第一道验收。写一个十几行的脚本把原图、ground truth 八点多边形、预测框四点分别用不同颜色画上去重点看三件事框的朝向是否吻合、长边短边有没有互换、密集目标的框有没有被 NMS 错误抑制。这三条里前两条能对上说明角度回归和数据链路是通的第三条要看nms_thr。旋转框的 NMS 跟水平框不一样两个框如果中心很近但朝向不同IoU 可能很小反而是两个真实目标。DOTA 上常见的nms_thr在 0.05 到 0.1 之间比水平框的 0.45 低得多。这个值调高了密集的车辆和船只会被压成一片调低了同一个目标会被重复检出好几遍。我一般固定 0.1 起步看可视化结果再上下微调。环节参数常见取值影响推理conf_thres0.05 ~ 0.25高了漏检小目标低了送入 NMS 的框爆量NMSnms_thr0.05 ~ 0.15高了密集目标互相抑制结果合并gap等于切图时的 gap不一致会导致跨切片的同一目标合不上评测IoU 阈值0.5跟 DOTA 官方口径保持一致结果合并这一步最容易被跳过。切片上预测出来的坐标是局部的必须用文件名里的__1024__0___0这类偏移量加回去恢复成全图坐标再用 devkit 的合并脚本按 IoU 把跨越切片边界的重复框合成一个。合并时的 IoU 阈值建议跟切图的gap挂钩重叠区越小这个值要越低。做完这一步再去算 mAP数字才可信。进阶方向有两块值得试一是角度损失的改法直接回归 θ 在 0/180 边界处天然有断崖换成把 θ 编码成 sin/cos 两个分量回归或者用环形平滑标签边界问题能缓解不少代价是代码要改好几处二是多尺度 TTA在 1024 之外再加一组 1280 的预测按 IoU 融合密集小目标的召回通常会涨一两个点但推理耗时翻倍得看业务能接受多少延迟。从我这边几次复现的经验看旋转框任务里「能跑通」和「出得来结果」之间隔着的从来不是模型结构而是角度定义、坐标归一化、结果合并这三件枯燥的事。从那以后我每次拿到一个新的 OBB 工程都会先花半小时把poly2rbox和rbox2poly单独拎出来跑一遍往返校验再抽三张图把 ground truth 画出来对一眼这两个动作做在开训之前能省掉后面至少一天的排查。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑