1. 为什么突然要写旋转框检测
先说个实际场景。去年接了个工业质检的项目,传送带上的电子元件方向不固定,用普通水平框标注,一个框里经常框进两三个零件,模型精度怎么调都卡在78%左右。后来换了旋转框,同样的数据量,mAP直接飙到93%。这个差距让我意识到,在遥感、文档扫描、工业检测这些对方向敏感的场景里,旋转框不是锦上添花,而是刚需。
所谓旋转框检测(Oriented Bounding Box,简称OBB),就是在普通矩形框的基础上多了一个角度参数。普通检测输出的是 (x, y, w, h) 四个值,旋转框输出的是 (x, y, w, h, angle) 五个值。别小看多出来的这个角度,它让模型能够用最小外接矩形去贴合目标,而不是用轴对齐的矩形去硬套。
这个教程把 YOLOv5、YOLOv8、YOLOv10 三个系列的旋转框检测方案全部串起来讲一遍。适合谁看?想用旋转框解决实际问题的算法工程师、正在做遥感或工业视觉相关课题的研究生、以及那些已经被普通YOLO调出经验、想在OBB方向再跨一步的朋友。我会把数据标注、格式转换、环境配置、模型训练、推理部署、踩坑记录全流程捋一遍,照着做基本能复现。
2. 三个版本的技术选型与我的取舍逻辑
2.1 YOLOv5 的旋转框:改动最多的老将
YOLOv5 官方仓库本身是不支持 OBB 的,得靠第三方实现或者自己改。现在网上能找到的方案,主流是参考早期某遥感检测项目对 YOLOv5 的改造思路。我记得当时的实现方式是替换模型的 head 部分,把原本的检测头改成带角度回归的检测头,并且对损失函数做了定制。角度损失用的是修正后的环形平滑标签(Circular Smooth Label,CSL)处理方式,把角度预测当作分类问题来做,这样比直接回归角度要好训练一些,因为角度有周期性,0度和180度实际是同一个方向,如果直接回归数值,模型很难理解这种平滑性。
我个人的建议是,除非你的部署环境锁定在 YOLOv5,或者你有很强的模型改造能力,否则不太推荐从 YOLOv5 开始搞旋转框。YOLOv5 的 OBB 方案需要你改动的地方太多,而且第三方实现的代码质量参差不齐,有些甚至还有 hidden bug。我们当时做技术选型的时候,花了一周时间研究 YOLOv5 的 OBB 改造方案,说实话,能跑通,但效率不高。
2.2 YOLOv8 的旋转框:官方支持的省心之选
YOLOv8 是官方原生支持 OBB 的版本。从 v8 早期版本开始,官方仓库就提供了 yolov8n-obb.pt、yolov8s-obb.pt 这些权重文件,训练和推理都做了完整的封装,这就是最大的优势——你不用自己接 head、写 loss,官方已经帮你做完了。
YOLOv8 的 OBB 实现在架构上有一个关键点:它在回归 head 中额外增加了一个 angle 分支,并且使用 ProbIoU(Probability Intersection over Union)损失来计算旋转框之间的差异。这个损失的优点是比较稳定,能处理角度周期性带来的一些问题。推理阶段,输出的格式是 (cx, cy, w, h, angle),在可视化的时候需要按角度把框画出来。
官方支持带来的另一个好处是生态好。Ultralytics 仓库的文档对 OBB 有很多说明,训练命令、数据集格式、评估指标都有现成的。我后来大部分生产环境下的旋转框项目,基本都是用 YOLOv8 来做的,因为省心,不容易出幺蛾子。
2.3 YOLOv10 的旋转框:无NMS架构的新尝试
YOLOv10 出来的时候主打的是无 NMS 推理。它的核心卖点是通过消除 NMS 来加速推理。但问题在于,YOLOv10 官方仓库同样没有原生支持 OBB。你要是想用 YOLOv10 做旋转框,得用第三方分支或者基于官方代码去改,这就比较麻烦。
我调研过一些开源社区的 YOLOv10 OBB 分支实现,基本原理是把官方模型中用于水平框的检测头扩展到五个参数输出,同时要处理无 NMS 架构下旋转框的匹配策略,这个改动比 YOLOv5 还复杂,因为 YOLOv10 的标签分配方式和传统YOLO不一样。如果你真的想在 YOLOv10 上做旋转框,我建议先等第三方实现成熟一些,或者干脆用 YOLOv8 稳定出结果。技术选型永远要服务于项目需求,不是越新的版本就越好。
2.4 三者的对比总结
| 维度 | YOLOv5 OBB | YOLOv8 OBB | YOLOv10 OBB |
|---|---|---|---|
| 官方支持 | 不支持 | 原生支持 | 不支持 |
| 改动成本 | 高(需改head和loss) | 低(开箱即用) | 高(需改匹配策略) |
| 损失函数 | CSL或自定义 | ProbIoU | 待第三方方案 |
| 训练难度 | 中 | 低 | 高 |
| 推理速度 | 中 | 中 | 快(理论) |
| 生态完善度 | 低 | 高 | 低 |
| 适合场景 | 被迫锁定v5的老项目 | 大多数新项目 | 研究尝试 |
一句话总结我的选型建议:别跟框架谈恋爱,选官方支持得最好的。YOLOv8 在这三个里面是最适合做旋转框的,不是因为别的版本差,而是因为你把精力省下来可以去调数据、调参数,而不是耗在改代码上。
3. 数据标注与格式转换:旋转框落地最容易翻车的一环
3.1 标注工具的选择
旋转框检测需要标注四个角点坐标(或者中心点+宽高+角度),普通标注工具没法直接用。我尝试过几个主流方案:
- roLabelImg:老牌的旋转框标注工具,基于 PyQt 开发,支持 VOC 格式的旋转框标注,导出的是 xml 文件,里面用 rotated_rect 表示目标。这个工具胜在轻量,缺点是界面比较旧,批量操作能力弱。
- X-AnyLabeling:功能比较全面,支持各种模型辅助标注,旋转框标注也支持。界面比 roLabelImg 现代不少,还带自动标注功能,适合标注量特别大的场景。
- LabelImg 的旋转框扩展版:后期维护不太活跃,不太推荐了。
我个人用得最顺手的是 X-AnyLabeling。理由很简单,旋转框标注本身就是个耗时活,如果工具能配合一个初步的模型做预标注,你只需要手动调整框的角度和大小,效率会大幅提升。
3.2 DOTA格式转YOLO-OBB格式
YOLOv8 官方支持的 OBB 数据格式是 YOLO-OBB,每行表示一个目标,格式为:
class_id x1 y1 x2 y2 x3 y3 x4 y4这里 (x1, y1) 到 (x4, y4) 是旋转矩形的四个角点坐标,需要归一化到 0~1,且四点按顺时针顺序排列。
遥感领域最常用的数据集格式是 DOTA,它的标注格式是:
x1 y1 x2 y2 x3 y3 x4 y4 class_name difficult看起来很像,但其实有个大坑:DOTA 中的坐标是绝对像素坐标,不做归一化,而 YOLO-OBB 需要归一化;另外 DOTA 的四个点虽然是按顺序给出的,但不能保证是顺时针,需要做一次排序处理。
我当时写了个转换脚本,核心逻辑如下:
import numpy as np from pathlib import Path def dota_to_yolo_obb(dota_label_path, img_width, img_height, class_mapping): """ 将DOTA格式标签转换为YOLO-OBB格式标签 """ results = [] with open(dota_label_path, 'r', encoding='utf-8') as f: for line in f.readlines(): parts = line.strip().split() if len(parts) < 9: continue # 前8个坐标 coords = list(map(float, parts[:8])) class_name = parts[8] if class_name not in class_mapping: continue class_id = class_mapping[class_name] points = np.array(coords).reshape(4, 2) # 确保四个点按顺时针顺序排列 # 计算多边形面积来判断方向 area = 0 for i in range(4): x1, y1 = points[i] x2, y2 = points[(i + 1) % 4] area += x1 * y2 - x2 * y1 if area < 0: points = points[::-1] # 逆时针则反转 # 坐标归一化 points[:, 0] = points[:, 0] / img_width points[:, 1] = points[:, 1] / img_height normalized = points.flatten() line_out = f"{class_id} " + " ".join(f"{v:.6f}" for v in normalized) results.append(line_out) return results这里面积判断顺时针的方法,用的是鞋带公式(Shoelace Formula),第一次写的时候没有处理这个细节,训练时 loss 一直不降,后来排查发现部分标注框的方向是乱的。这个坑一定要记得避免。
3.3 训练集验证集划分的注意事项
在划分旋转框数据集时,跟普通检测有一个显著不同:你需要确认同一张图上的对象旋转方向分布不过于集中。比如遥感图像里的飞机,如果训练集的飞机方向大多朝北,测试集里朝东的飞机模型大概率检测不出来,因为角度这个参数的训练需要充分覆盖各个方向。更好的做法是按场景划分而不是简单随机划分,把同一个地区的影像尽量放到同一个集合里,防止数据泄漏。
3.4 标注质量的检查方法
标注完数据后,千万别急着训练。我建议写一个快速可视化脚本,把标注框画在图像上检查四点顺序是否正确、框是否贴合目标。很多时候标注工具导出时单位不一致(有的用像素、有的用比例),肉眼看到框飘在图上才能发现这些问题。
我常用的检查方式是随机抽样 5% 的图片做可视化,如果发现超过 1% 的标注有问题,就返回去重新标注。这个标准可以自己掌握,但对旋转框来说,漏标一个角点整个框就废了,多花点时间检查很值得。
4. 环境准备与训练配置:让模型真正跑起来
4.1 环境安装
如果你是第一次用 Ultralytics 仓库做 OBB 训练,环境安装其实非常直接,不需要装一堆乱七八糟的依赖。我自己常用的环境配置是 Python 3.9 以上 + PyTorch 2.x + CUDA 11.8 或更高。
pip install ultralytics就这么一行,没有多余的东西。Ultralytics 会自动帮你把依赖处理好,包括 torch、torchvision、opencv 这些基础设施。如果你需要用 GPU 加速,先确认你的 CUDA 版本跟 pytorch 匹配,这个不用我说,大家应该都懂。
4.2 数据集结构组织
训练前需要把数据集组织成 YOLOv8 期望的结构,以下是我的标准模板:
dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── data.yaml注意这里有个细节,Ultralytics 的 OBB 训练中,图片的默认尺寸通常是 1024 或者 640。遥感图像一般尺寸比较大(比如 4096x4096),你需要用rect=True参数或者提前做裁剪,否则直接训练大概率显存不够。
data.yaml 的写法示例:
train: dataset/images/train val: dataset/images/val nc: 4 names: ['plane', 'ship', 'storage-tank', 'vehicle']有一个关键点需要注意,当你的图像标签中有某个类别的样本数量特别少时,建议在 data.yaml 中增加一个权重字段,或者在训练参数中设置 class weights,不然少数类别的 AP 会很难看。
4.3 YOLOv8-OBB训练实操
准备好数据后,训练命令非常简洁:
yolo obb train data=dataset/data.yaml model=yolov8n-obb.pt epochs=100 imgsz=1024 batch=16 device=0这里model=yolov8n-obb.pt表示用官方预训练权重做迁移学习。如果你是从头训练,可以改成yolov8n.yaml结构文件。对大多数场景,我都建议用预训练权重,旋转框任务的数据量通常比水平框任务小很多,迁移学习的收益非常明显。
关于 batch size,如果你的显存不够大(小于16G),建议减小 batch 而不是减小图像尺寸。旋转框对图像的细节比较敏感,缩得太小容易丢失角度信息。我一般维持在 imgsz=1024 起步,显存实在不够再降到 640。
4.4 YOLOv5-OBB训练回顾
这里也简单说一下 YOLOv5 的 OBB 训练流程,万一有朋友确实需要。第三方实现的代码一般结构是:
python train.py --data data.yaml --cfg models/yolov5s-obb.yaml --weights '' --epochs 100YOLOv5 的 OBB 实现需要额外处理角度周期的损失,所以训练时你会发现 loss 曲线不像水平框那么平滑,这是正常的。另外在验证阶段,你需要从模型输出中提取五个通道(x,y,w,h,angle),再转成四角点格式来算 mAP,过程比 YOLOv8 繁琐不少。
4.5 YOLOv10-OBB的现状与避坑
从我目前看到的情况来说,YOLOv10-OBB 还没有一个像官方 YOLOv8 那样完善的开箱即用实现。如果你在网上搜到了一些分支,请务必确认几个关键点:
- 是否支持从 YOLOv10 预训练权重进行迁移学习
- loss 函数中是否正确定义了角度分量的反向传播
- 推理阶段是否妥善处理了无 NMS 架构与旋转框的兼容性
如果这几个点没有一个让人满意的答复,我建议暂时别用。技术选型是为了出结果,不是为了炫技。
5. 训练过程中的关键参数与优化策略
5.1 学习率与优化器选择
YOLOv8 默认使用 SGD 还是 Adam 取决于你的配置,但我个人在旋转框任务上更倾向于 AdamW。原因是角度回归这个分支,初期收敛比较慢,AdamW 的适应性学习率能更平稳地处理这种多任务之间的尺度差异。
如果你用 SGD,建议初始学习率设置在 0.01 左右,配合 warmup 策略。如果 loss 在早期剧烈震荡,就把学习率调低一个数量级。
5.2 数据增强避坑
Ultralytics 的默认数据增强中,有旋转增强、翻转增强等手段。但对 OBB 任务来说,这里有个矛盾点:目标本身已经用旋转框标注了,你再做任意角度的旋转增强,标注可以跟着旋转,这个没问题;但是翻转增强(比如水平翻转和垂直翻转)会打乱角度的方向定义。
比如一个目标框标注角度是 30 度,水平翻转后,这个框实际对应的角度应该变成 150 度,如果你的数据增强 pipeline 没有同步更新标注角度,模型就会学到错误的知识。
YOLOv8 内部对于 OBB 任务会自动处理这些细节,但如果你用了额外的第三方增强库,就必须自己注意这个坑。我踩过一次,训练集做水平翻转增强后 mAP 掉了个 3 个百分点,查了好久才发现是角度跟着翻转后没有重新计算。
5.3 类别不均衡的处理
旋转框检测在遥感场景中类别不均衡问题格外突出,比如一张遥感图可能有 500 个飞机框,但只有 5 个油罐。普通 YOLO 训练时少数类基本被淹没。
我有两个解决思路:
- 第一个是在线难例挖掘,但这个在 Ultralytics 里不好直接配置,实现成本高。
- 第二个更简单有效:对少数类做过采样,就是把包含少数类的原始图像(裁剪后)重复出现在训练集中。这种方式能直接增加少数类在 batch 中的出现频率,实测效果不错。
5.4 角度周期的特殊处理
不要以为 YOLOv8 用 ProbIoU 就万事大吉了,角度周期问题依然值得注意。当你的回归目标接近 0 度或 180 度时,loss 容易出现抖动,因为 179 度和 -179 度之间的真实差异其实只有 2 度,但数值差异看起来是 358 度。
YOLOv8 的 ProbIoU 损失本身对这种周期性有较好的容错,但我还是建议在训练时开启角度相关的数据增强——在任意旋转增强时,角度这个参数要跟着图像旋转做同步换算。用 Ultralytics 官方脚本时它会自动处理,但如果你改过数据集加载逻辑,这点就需要确认。
6. 推理、可视化和部署:最后一公里的实战细节
6.1 模型导出与推理
训练完成后,用 YOLOv8 做 OBB 推理非常直接:
from ultralytics import YOLO model = YOLO('runs/obb/train/weights/best.pt') results = model.predict('test_image.png') for result in results: boxes = result.obb # 旋转框结果 print(boxes.conf, boxes.cls, boxes.xyxyxyxy) # 四角点这里的结果boxes.xyxyxyxy是四个角点的坐标,format 是 (N, 4, 2),可以直接拿来画图。
导出 ONNX 时注意:
yolo export model=best.pt format=onnx opset=12旋转框模型导出后,ONNX 输出层中有一个输出头是角度预测结果。如果你用 TensorRT 部署,需要额外确认 OBB 的输出解析层是否支持。有些版本的 TensorRT 对 OBB 的 op 支持有 bug,推理结果会出现角度偏差,测试时务必与 PyTorch 的推理结果做对比。
6.2 可视化技巧
画旋转框跟画普通框不太一样,需要用到 OpenCV 的旋转矩形绘制。我的做法是:
import cv2 import numpy as np def draw_obb(image, points, color=(0, 255, 0), thickness=2): """ points: (4, 2) 顺序角点 """ pts = points.astype(np.int32) cv2.polylines(image, [pts], isClosed=True, color=color, thickness=thickness)这里核心是用cv2.polylines而不是cv2.rectangle,后者画不出带角度的矩形。可视化时颜色可以根据类别从预设调色板取,透明度别调太高,不然重叠目标看不出边界。
6.3 部署时对输出的后处理
部署阶段,旋转框的置信度过滤和 NMS 策略也需要单独处理。YOLOv8 在 PyTorch 推理时已经集成了 NMS,但导出到 ONNX 或 TensorRT 后,你需要在后处理代码里自己实现旋转框 NMS。普通 NMS 用的 IoU 计算是轴对齐矩形的,旋转框必须用旋转 IoU 来计算。
旋转 IoU 的计算比较麻烦,需要解多边形交集面积。常见的方案是调用 OpenCV 的cv2.rotatedRectangleIntersection函数,它可以直接返回两个旋转矩形的交集面积,无需自己手写几何算法。
import cv2 import numpy as np def rotated_iou(box1, box2): """ box: (cx, cy, w, h, angle) angle 为度 """ rect1 = ((box1[0], box1[1]), (box1[2], box1[3]), box1[4]) rect2 = ((box2[0], box2[1]), (box2[2], box2[3]), box2[4]) intersection_area, _ = cv2.rotatedRectangleIntersection(rect1, rect2) if intersection_area is None: return 0.0 area1 = box1[2] * box1[3] area2 = box2[2] * box2[3] inter = cv2.contourArea(intersection_area) union = area1 + area2 - inter return inter / union if union > 0 else 0.0实测下来用这种方式做旋转框 NMS,速度虽然比普通 NMS 慢一些(因为要算多边形交集),但对于大多数场景来说性能是足够的。
7. 实战案例:遥感图像船舶检测全流程复现
7.1 问题定义与数据说明
不如拿一个完整的案例来复盘。假设我们要检测某区域的船舶目标,数据来源是公开遥感影像。船舶在图像中各个方向都有,用水平框标注时经常一个框套多艘船,这是旋转框发挥优势的典型场景。
数据规模:总共 2000 张图像,尺寸 1024x1024。原始标注用的是一套私有格式(x_center, y_center, width, height, angle),跟YOLO-OBB的四角点格式不一样,需要先转换成四角点格式。
7.2 数据转换与组织
转换的核心逻辑比较好理解:给定中心点、宽高和角度,通过二维旋转矩阵求出四个角点,再归一化。这里有个经验,转换的时候如果原标注里 angle 单位是弧度,千万别忘了转成度,反之亦然。我们当时有 200 张图的标注单位搞错了,转出来的框全都歪了,浪费了一天时间。
数据组织完成后,随机抽样 20 张图可视化检查,确保所有框的正确性。这一步是必须的,不要偷懒。
7.3 训练与结果对比
我分别用 YOLOv8n-OBB 和 YOLOv8s-OBB 做了对比实验。数据量不大,n 模型和 s 模型的绝对精度差别不算大,但 s 模型在少量困难样本上的召回率明显更好。最终选择了 YOLOv8s-OBB。
训练结果:
- 训练 100 个 epoch,imgsz=1024,batch=8,单卡 A100
- mAP50: 0.91
- mAP50-95: 0.68
- 单张推理时间(TensorRT FP16):17ms
对比同数据下水平框 YOLOv8s 的结果(mAP50: 0.82),旋转框带来了近 9 个百分点的提升。对于船舶这种长条形目标来说,这个提升幅度在预期之内,因为水平框的 IoU 计算天然吃亏,同一个目标框进来一堆背景区域,标签分配时含噪。
7.4 部署时的精度保持
部署环节遇到过一个典型问题:PyTorch 测试精度 0.91,TensorRT FP16 推理精度掉到 0.87,排查下来发现原因有两个方面:
- 一方面是 NMS 实现不一致。PyTorch 推理用的 YOLOv8 内建 NMS,对 OBB 的置信度过滤阈值有特殊处理,而 TensorRT 后处理里我用的自己写的旋转 IoU 函数,阈值没有对齐。
- 另一方面是 FP16 精度损失在角度回归通道上放大了,角度预测本身就比较敏感,低精度推理下容易出现微小偏差。
解决方案是把角度通道单独保持 FP32,其他通道用 FP16。这个在 TensorRT 里可以通过 per-channel 精度的方式配置,不是特别麻烦,但需要花点时间去调。
8. 常见问题与排查技巧速查表
| 现象 | 可能原因 | 排查思路 | 解决方案 |
|---|---|---|---|
| Loss 不降或震荡 | 标注框四点顺序混乱 | 可视化检查标注框 | 在数据转换时用鞋带公式统一顺时针 |
| mAP 很高但可视化效果差 | 角度正值与负值共存,画框时未处理周期 | 检查输出角度范围(-90~90还是0~180) | 画框前统一角度到同一表示空间 |
| 训练时内存溢出 | 图像过大 + batch过大 | 查看训练日志中的实际输入尺寸 | 降低 imgsz 或 batch,优先降低 batch |
| 推理时少检或漏检 | 旋转框 NMS 实现不当 | 对比 Java/Python 的 NMS 输出 | 用 cv2.rotatedRectangleIntersection 替换自写 IoU |
| 小目标检测效果差 | 图像缩放过度导致细节丢失 | 检查小目标在缩放后的像素尺寸 | 使用 imgsz=1280 并配合 tiling 推理策略 |
| 角度误差集中在0/90/180 | 数据增强中翻转导致标注未同步更新 | 对比增强前后的标注可视化 | 关闭翻转增强或手动修正角度换算 |
8.1 关于标注格式的免费提醒
DOTA 格式中目标点的顺序和方向问题,我前面已经提到过,这里再强调一下。不管数据来自哪个渠道,统一转换到四角点格式后,务必跑一遍顺时针排序脚本做全局检查,不要在训练中让模型去理解两种不同方向的框并存的情况。
8.2 如何诊断模型学到了错误的旋转语义
一个比较实用的诊断方法:训练完找一个包含 90 度方向目标的验证集,单独计算这个方向子集上的 AP。如果横向目标的 AP 高而纵向目标的 AP 低,大概率是数据增强打乱了角度语义。这种分段评估的方式能让问题的定位精准很多。
8.3 旋转框 mAP 计算方式
最后提一下旋转框 mAP 计算在 Ultralytics 中的差异。它用的是旋转 IoU 而不是水平 IoU,也就是我之前提到的cv2.rotatedRectangleIntersection或等价的算法。很多朋友拿旋转框模型的 mAP 去跟水平框模型比较,这是不合理的,因为两个 mAP 的 IoU 定义完全不同。旋转框的 IoU 更严格,所以数值通常低于同场景下水平框的 mAP,但实际检测效果更好。这一点在汇报成绩时需要特别解释清楚,不然业务方会误以为你的模型变差了。
9. 效率优化与扩展方向
如果你手头数据量比较大,可以试试把旋转框检测扩展到切图推理流程。遥感图像通常尺寸巨大,单张 1024x1024 推理显然不够用,你需要把大图切成若干 patch,分别推理后再把旋转框映射回原图坐标。这块需要注意的是切图重叠区域的处理——角落的目标可能会被切断,建议有 overlap 的切图方案,配合 NMS 去重。
另一个可以探索的方向是半自动标注。先用一个初步的旋转框模型给未标注数据打标签,然后人工修正错误框。这个方案在数据量上千张时有奇效,可以节省 60% 以上的标注时间。X-AnyLabeling 工具里可以直接加载 YOLOv8-OBB 模型做预标注,流程上跑通不难。
10. 聊聊我自己的经验总结
从普通水平框检测转向旋转框检测,本质上不是代码层面的变化,而是思维方式的转变。你需要开始考虑角度回归的周期性、标注角点顺序的规范性、旋转 IoU 计算的正确性,这些细节任何一个出了问题,模型的最终效果都会打折扣。
我在实际操作中的体会是,旋转框检测项目里最容易出问题的不是模型结构,而是数据链路。从原始标注到训练格式的转换、增强过程中的角度同步、推理时的框绘制和 NMS,每一环都容易出现"看起来对了但实际不对"的情况。所以每次换新数据集,我都会从头到尾过一遍数据可视化和单步推理测试,确认每一步的中间产物都是正确的,再开始大规模训练。
最后再分享一个小技巧:如果你在多个项目里做旋转框检测,建议沉淀一套统一的 OBB 数据工具包,包括格式转换、可视化、旋转 IoU 计算、NMS 后处理这些模块。不同的项目只是数据不同,这些工具是可以复用的。我第一次做的时候全是临时脚本,第二做才把工具抽出来,后边的项目直接从 2 周压缩到 3 天。别做重复造轮子的事。