
1. 为什么航拍场景必须抛弃水平框旋转目标的本质需求先把话说在前面如果你只是拿普通YOLO直接去检测无人机拍回来的图十有八九会在车辆、建筑物、船只这类目标上翻车。这不是YOLO本身不行而是航拍影像的目标形态和自然场景照片差异太大。俯视视角下路边停着的车、港口的集装箱、工地的塔吊它们的朝向几乎完全随机。普通YOLO输出的是水平矩形框也就是axis-aligned bounding box。这种框的天生缺陷在于它用四条边去包住一个斜着摆放的物体时框里必然混入大量背景同时还会把旁边距离近的目标一起框进去。之前我做过一个城市车辆统计的项目无人机在100米高度拍摄画面里车辆密集排列。用YOLOv8的水平框检测两辆并排斜停的车经常被识别成一个大框因为水平框无法区分两个朝向不同的目标。更麻烦的是框和框之间的IoU变得特别大NMS一压直接就把其中一辆车给滤掉了漏检率一度到了30%以上。旋转目标检测Oriented Object Detection也叫OBB检测就是为了解决这个问题。它输出的是一个带角度的矩形框通常用五个参数表示中心点坐标(cx, cy)、宽高(w, h)和角度θ。这样一来Bounding Box可以紧密贴合目标的朝向同区域内的目标数量识别能力大幅提升。有一点需要说清楚旋转框并不是在所有检测任务里都比水平框好。如果目标本身是正着拍的比如街道上的行人、正对镜头的猫猫狗狗水平框完全够用强行上旋转框反而会因为角度预测误差增加训练难度。判断标准很简单——目标的朝向是否任意分布、是否密集排布、你后续是否需要对目标做精细轮廓处理。满足其中任意两条就该考虑OBB了。从标注成本上来算账这件事也挺有意思。同样是标注一个斜着的建筑物用水平框你需要把框拉大去包住整个建筑旋转框则是沿着建筑的轮廓画一个最小的外接旋转矩形。前者虽然标注动作简单但框内部的背景区域大模型学到的是建筑背景的混合特征后者标注的时候要多转一下角度但干净的正样本让模型收敛更快、精度更高。我在实际项目里对比过用旋转框标注的数据量只要水平框的60%左右就能达到相近甚至更高的检测精度。2. 检测器选型YOLOv8-OBB与其它旋转检测方案的取舍选型这件事我的经验是先看你的落地场景再选工具而不是哪个框架火就用哪个。旋转目标检测方向上开源社区现在跑得最顺的方案主要有这么几条路线。2.1 Ultralytics YOLOv8-OBB最省心的入门路径Ultralytics官方在YOLOv8中直接集成了OBB支持训练命令和标注格式都有现成方案。这是目前上手门槛最低的一条路非常适合第一次做旋转目标检测的团队。YOLOv8-OBB的检测头在普通YOLOv8基础上增加了一个角度分支损失函数用了Probiou Loss这个后面专门说。它对DOTA格式和YOLO-OBB格式都做了适配训练脚本、验证脚本、导出脚本一条龙不用自己写一堆工具代码。实测下来YOLOv8-OBB在小目标密集场景下比水平框YOLOv8有质的提升尤其是在车辆、船舶这类规则几何形状的目标上精度提升非常明显。2.2 MMRotate研究者路线OpenMMLab旗下的MMRotate是目前学术领域最全的旋转目标检测工具箱里面集成了Oriented R-CNN、RoI Transformer、Rotated RetinaNet、RTMDet-R等一大堆算法。如果你想跑对比实验、看论文复现或者需要尝试S2ANet这类针对遥感影像优化的特殊模型MMRotate是首选。不过它的代价是用起来相对繁琐配置文件多依赖版本耦合问题多对新手不友好。我一般在快速验证一个新想法时会碰MMRotate做产品原型还是回YOLO系。2.3 YOLOv5-OBB系列社区维护的稳定旧版本在YOLOv8-OBB出来之前社区里大量项目是基于YOLOv5改造的OBB版本比如hukaixuan的YOLOv5-OBB还有各种魔改分支。它们能跑但有一个共性问题依赖的分支众多环境兼容性参差不齐很多分支的依赖库已经停更了。如果你不是有特殊的老项目维护需求不建议新项目从这套起步。下面这张表是我在选型时的对比思路分享出来给你参考方案上手难度训练速度精度上限生态完整度适合场景YOLOv8-OBB低快高极高落地项目、快速原型MMRotate高中极高高论文复现、算法对比YOLOv5-OBB中中中中老项目维护实际结论很简单除非你有明确的算法创新需求否则直接选YOLOv8-OBB作为起点。它让你把主要精力放在数据集、训练调参和后续的轮廓处理上而不是花在环境搭建和踩依赖坑上。3. 环境搭建与数据集标注最容易让人放弃的两个关卡这部分我得多花点篇幅因为多数人第一次做OBB项目就是在环境配置和标注阶段放弃的。这两关跨过去后面就顺了。3.1 Anaconda环境配置的坑与解法YOLOv8-OBB推荐使用Python 3.8到3.11之间的版本我测试下来3.9和3.10都比较稳。PyTorch版本建议2.0以上CUDA超过11.8都没有问题。这里有几个实测下来的注意点第一PyTorch不要用conda装直接用pip装官方源版本。conda的PyTorch经常和NVIDIA驱动、CUDA工具包之间有版本打架的问题pip装反而省心。执行下面的命令会自动根据当前CUDA环境选择合适版本。conda create -n yolo_obb python3.10 conda activate yolo_obb pip install ultralytics注意ultralytics包会同时安装torch和torchvision如果你的机器上已经有了特定版本的torch可以先单独装torch再装ultralytics避免它帮你把torch版本覆盖掉。第二如果你的显卡是AMD的情况会复杂一些。ultralytics官方支持CPU、NVIDIA GPU、Apple Silicon对AMD GPU没有原生支持。实测下来AMD GPU在Linux下可以通过ROCm跑一部分PyTorch版本但配置难度比较高。如果你手头只有AMD显卡建议先在CPU上跑通小数据集验证流程或者考虑用云GPU训练。用CPU训练小数据集时把batch size调小到4到8imgsz维持在640还是可以接受的就是慢一些。第三Windows和Linux的路径分隔符问题。标注工具输出的路径格式如果没处理好训练时会报图片找不到的错误。建议在数据集配置文件中用绝对路径或统一用正斜杠。3.2 旋转框标注从roLabelImg到X-AnyLabelingOBB数据集的标注比普通检测多一个角度维度这一步的质量直接决定模型上限。标注工具选得好效率差两三倍。早期的OBB标注基本都用roLabelImg它支持画旋转矩形输出的是旋转框的四个顶点坐标。但这个工具的问题在于标注体验一般撤销、缩放、标签管理都不算顺手而且依赖PyQt5装起来也有点别扭。我现在的标注主力是X-AnyLabeling它支持旋转框标注且交互更平滑缩放平移流畅还能自定义标签列表标注结果可以直接导出为YOLO-OBB格式。如果你需要标注大量数据强烈建议直接用X-AnyLabeling而不是roLabelImg。标注格式上需要区分两个体系DOTA格式每个目标用四点坐标表示即x1 y1 x2 y2 x3 y3 x4 y4 class_name四个点按顺时针排列。这个格式可读性好但转换到YOLO格式需要额外脚本。YOLO-OBB格式每行一个目标格式为class_id x_center y_center width height angle其中x_center、y_center、width、height都已经做了归一化除以图片宽高angle是归一化后的弧度值范围是[0, 1)。标注完成后把数据集目录按下面的结构组织好datasets/ ├── train/ │ ├── images/ │ │ ├── img_001.jpg │ │ └── ... │ └── labels/ │ ├── img_001.txt │ └── ... ├── val/ │ ├── images/ │ └── labels/ └── data.yamldata.yaml的内容很简单只需要指定路径和类别列表path: datasets/ train: train/images val: val/images names: 0: vehicle 1: ship 2: building3.3 航拍影像的切图预处理航拍影像的分辨率普遍很高一张4000x3000的图直接丢进YOLO训练下采样到640后小目标基本就消失了。这个问题在旋转目标检测中尤其突出因为航拍目标普遍是小目标。我的做法是训练前先把大图切成小块每块带一定重叠然后再送入训练。切图有两个关键参数切片大小和重叠率。切片大小我一般取640或1024太小了切块数量爆炸太大了显存吃不消且小目标问题依旧。重叠率建议在15%到25%之间。重叠的作用是避免目标恰好被切在边缘断开。切图时要同步处理标注文件——原始标注框坐标要减去切图起始点的偏移量超出切图边界的目标要裁剪掉完全不在切图内的直接丢弃。这部分逻辑要自己写脚本处理没有现成的YOLO工具。标准的切图脚本思路如下。import cv2 import numpy as np def crop_image_with_labels(image_path, label_path, crop_size640, overlap0.2): img cv2.imread(image_path) h, w img.shape[:2] labels [] with open(label_path, r) as f: for line in f: parts line.strip().split() cls int(parts[0]) cx, cy, bw, bh, angle map(float, parts[1:]) # 反归一化 cx, cy, bw, bh cx * w, cy * h, bw * w, bh * h labels.append([cls, cx, cy, bw, bh, angle]) step int(crop_size * (1 - overlap)) crops [] for y in range(0, h, step): for x in range(0, w, step): crop img[y:ycrop_size, x:xcrop_size] if crop.shape[0] crop_size or crop.shape[1] crop_size: continue crop_labels [] for cls, cx, cy, bw, bh, angle in labels: # 判断目标中心是否在切图内 if x cx x crop_size and y cy y crop_size: new_cx (cx - x) / crop_size new_cy (cy - y) / crop_size new_bw bw / crop_size new_bh bh / crop_size crop_labels.append([cls, new_cx, new_cy, new_bw, new_bh, angle]) crops.append((crop, crop_labels)) return crops这里有个细节值得注意判断目标是否保留不要用框的任一角点而要用目标中心点。因为旋转框本身的几何分布比较散只看角点容易把中心在切图内但角点超出边界的有效目标漏掉或者反过来引入中心在切图外的大面积目标。切图后的数据量和训练时间会成倍增加但这是航拍小目标精度提升最值得的一笔投入。我在车辆检测项目里切图后mAP50提升了约12个百分点效果非常显著。4. 训练过程的核心细节损失函数、超参数与置信度调优训练OBB和训练普通检测器有些相通之处但角度维度的加入带来了新的变数。这部分我把损失函数、训练参数和数据增强策略分开讲。4.1 旋转框损失函数的设计逻辑YOLOv8-OBB的损失函数由分类损失和回归损失两部分组成。分类损失仍然用BCE回归损失则从普通YOLOv8的CIoU换成了Probiou Loss。为什么不能用CIoU关键在于角度周期性问题。旋转框的角度是周期性变量0度和180度是同一个方向。如果直接用角度差值来计算损失梯度会在周期边界附近产生跳变导致训练不稳定。Probiou Loss通过把旋转矩形转换成二维高斯分布用高斯分布的Wasserstein距离来度量两个旋转框之间的差异天然解决了角度周期性问题同时也自然地考虑到中心点偏移、宽高比差距和角度差异。从训练现象来看Probiou Loss在前几个epoch收敛非常快但到了后期精度提升会放缓。这时不用急着调整学习率可以把epoch数拉长后半段的缓慢提升正是模型在精细对齐角度选择。4.2 一组实测效果不错的训练超参数以一个中等规模的数据集为例约8000张切图3个类别我的起点配置是这样model: yolov8n-obb.pt # 或者yolov8s-obb.pt epochs: 200 batch: 16 imgsz: 640 optimizer: AdamW lr0: 0.001 lrf: 0.01imgsz如果你用了切图策略640够用如果直接训练大图建议用1024但显存会吃紧。batch根据显存调整batch在合理范围内越大越稳定。显存不够时别死磕batch优先保证image size。lr0AdamW优化器下0.001是比较稳妥的起始学习率。SGD的话建议0.01起步。epochs旋转检测的收敛比水平检测慢一些200是一个底线值数据集复杂的话300也不嫌多。训练过程中主要盯三个指标mAP50、mAP50-95和验证集loss。mAP50看普通精度的达标情况mAP50-95反映模型对角度预测的精细度后者在OBB任务中尤其重要因为角度预测误差会直接影响后续轮廓提取的质量。4.3 置信度门限该怎么调推理阶段最容易被忽略的就是置信度门限。YOLO的默认置信度门限是0.25但实际部署时这个值不一定合适。置信度门限调整可以这样来思考如果你做的是车辆计数误检一辆车的代价大于漏检一辆车因为误检会虚增数据就把门限调高到0.4到0.5如果你是做目标搜索目标是尽量不遗漏任何可疑目标门限可以降到0.1到0.15。还需要配合NMS的IoU阈值一起调。OBB的NMS用的IoU计算比较复杂因为要考虑旋转矩形的交集面积。实际经验是目标密集的场景把IoU阈值从默认的0.5调低到0.3左右可以减少相近目标的互相抑制目标稀疏的场景可以保持默认。推理时的命令如下yolo obb predict modelbest.pt sourcetest_images/ conf0.25 iou0.54.4 数据增强的针对性设计YOLO自带的增强策略马赛克、翻转、色调变换等可以默认开但对航拍旋转目标场景有几个增强手段值得主动加。旋转增强。航拍目标的朝向本来就是任意的所以随机旋转是训练旋转检测器最直接、最有效的增强手段。YOLO-OBB在旋转增强时会同步更新旋转框的角度不用担心标签错位的问题。不过要注意旋转增强和切图策略的顺序——先切图再旋转否则容易引入大量黑边。水平垂直翻转。这个增强简单有效但需要注意翻转后角度标注的符号变化YOLO-OBB的标签更新逻辑已经处理好了这一点。马赛克增强的坑。马赛克增强能显著提升模型的泛化能力但它将四张图拼在一起小目标会进一步缩小对航拍小目标不太友好。建议把mosaic概率从默认的1.0调到0.5左右让模型有一定比例的目标保持原始尺寸。类别不平衡的处理。航拍场景常常有严重的类别不平衡比如车辆数量远超船只。我不想直接砍样本来做平衡那样浪费数据。更好的方案是合成场景数据——手动把少数类目标复制粘贴到空背景区域同时保留其旋转框标注相当于给模型增加了阅读稀疏类别样本的机会。这个方法不完美但在我经历的项目里往往比过采样和欠采样效果好。5. 从旋转框到精确轮廓掩膜提取与轮廓后处理链路严格说起来旋转框检测解决的是目标是什么、在哪、朝向哪这三个问题但精确轮廓这个词意味着要输出目标的外边界形状而不是一个简单的旋转矩形。这一步是把检测能力转化为工程可用结果的关键环节。5.1 三种从检测框到轮廓的路线对比我之前实践下来有3条路可以走适用场景各不相同。路线一模板匹配法。如果目标形状非常规整比如车辆、标准集装箱可以基于旋转框参数用模板匹配去细化边缘。这个方法不涉及额外模型纯粹靠几何计算速度极快但遇到形状复杂的目标时效果不好。路线二检测后接分割模型。在旋转框内做实例分割比如把Box变成Mask再提取轮廓。这个方法精度上限高但需要为每个目标类别准备分割标注标注成本翻倍。路线三旋转框直接扩展为多边形。用旋转框检测的结果在框内做边缘查找和几何约束拟合把旋转矩形扩展成贴合目标实际形状的多边形轮廓。YOLOv8-OBB的矩形框已经很贴合目标主体了框内做轮廓处理相对简单。考虑到成本和精度的平衡我的实践路线是路线三在它基础上再叠加一个轻量的语义分割或边缘检测作为辅助。整体流程跑下来在建筑物和车辆的轮廓提取上已经能达到比较满意的效果。5.2 轮廓提取的完整实现过程先说我用的方案做完旋转框检测后我把每个旋转框裁剪出来在这个裁剪子图里做局部二值化和边缘查找然后用多边形逼近算法把边缘压缩成轮廓点。下面是处理单个旋转框的核心代码逻辑。import cv2 import numpy as np from shapely.geometry import Polygon def extract_contour_from_obb(img, obb_params): img: 原始图像 obb_params: (cx, cy, w, h, angle)均为像素坐标/角度值 返回轮廓多边形点集 cx, cy, w, h, angle obb_params # 计算旋转矩形的四个顶点 rect ((cx, cy), (w, h), angle) box cv2.boxPoints(rect) # 返回四个顶点格式与cv2.polylines兼容 box np.intp(box) # 根据旋转框裁剪局部图像 min_x, min_y np.min(box[:, 0]), np.min(box[:, 1]) max_x, max_y np.max(box[:, 0]), np.max(box[:, 1]) margin 10 # 向外扩一点避免裁剪时切掉目标边缘 x1, y1 max(0, min_x - margin), max(0, min_y - margin) x2, y2 min(img.shape[1], max_x margin), min(img.shape[0], max_y margin) crop_img img[y1:y2, x1:x2] if crop_img.size 0: return None # 局部边缘检测 gray cv2.cvtColor(crop_img, cv2.COLOR_BGR2GRAY) # 高斯模糊去掉噪声保留大尺度边缘 blurred cv2.GaussianBlur(gray, (5, 5), 0) edges cv2.Canny(blurred, 50, 150) # 查找轮廓 contours, _ cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None # 选择面积最大的轮廓 main_contour max(contours, keycv2.contourArea) # 多边形逼近简化轮廓 epsilon 0.02 * cv2.arcLength(main_contour, True) approx cv2.approxPolyDP(main_contour, epsilon, True) # 轮廓点回映射到原图坐标 contour_pts approx.reshape(-1, 2) [x1, y1] return contour_pts.tolist()注意几个关键细节Canny的阈值选择很关键。航拍影像噪声多阈值设太低会提取出大量纹理边设太高又从会漏掉真实目标的边缘。我建议先在验证图上手动调几次找到一个适合你图像特点的阈值区间而不是每个项目都从默认值出发。多边形逼近的epsilon值。这个值控制着简化程度太大会丢失细节太小会让轮廓点数爆炸。0.02倍轮廓周长是一个比较稳的起点如果你后续还要做轮廓简化再调整。轮廓筛选策略。有时候检测框内会混入其他目标的边缘比如路边车辆上方正好有电线杆的影子。面积最大轮廓作为主目标只是第一层筛选后续还需要结合轮廓中心与旋转框中心的距离做二次过滤——轮廓中心距离旋转框中心太远的目标要考虑是否误检。5.3 轮廓数据的输出与地理映射如果只是项目展示把轮廓画在图上就够了。但航拍项目实际落地时往往还需要把这些轮廓信息导出为矢量数据后续做统计分析或地理信息系统使用。以GeoJSON为例每个目标的输出结构大概是这样的{ type: Feature, properties: { class: vehicle, confidence: 0.87 }, geometry: { type: Polygon, coordinates: [[ [120.1234, 30.5678], [120.1240, 30.5679], [120.1242, 30.5685], [120.1236, 30.5686], [120.1234, 30.5678] ]] } }如果检测结果要转换到地理坐标而不是像素坐标还需要一个坐标映射步骤。无人机航拍通常会记录GPS信息和相机姿态参数把这些参数结合相机内参做透视变换就可以把像素坐标映射到经纬度。这块涉及的依赖比较多一般要看你是用的是哪款飞控和相机我用的方案是先把像素坐标映射到局部平面坐标系比如ENU再从ENU转到经纬度。算出来的轮廓边缘误差大致在1到2个像素能接受。6. 推理部署与批量产出从模型文件到可用的业务结果训练完模型只是一个中间节点真正的工程化考验是如何把模型稳定地跑起来输出可用的业务结果。6.1 ONNX导出与模型压缩YOLOv8-OBB支持直接导出ONNX格式这一步最大的好处是摆脱PyTorch环境依赖部署端只需要ONNX Runtime或TensorRT就行。yolo obb export modelbest.pt formatonnx halfTrue dynamicTrue这里有几个导出参数值得解释一下halfTrue导出FP16精度的模型。精度损失在可接受范围内但推理速度几乎翻倍显存占用减半。dynamicTrue允许动态输入尺寸。航拍测试图尺寸不固定时比较有用但会稍微降低TensorRT优化效果。导出后建议用onnxruntime或onnx2trt测试一遍避免模型某些算子导出后精度异常。我遇到过的一个典型情况是模型导出ONNX后推理result和PyTorch不一致最后发现是angle分支的归一化操作在ONNX中精度丢失。解决方式是导出时关闭某些融合优化或者稍微调整angle分支的结构。6.2 批量推理脚本设计航拍影像的批量推理和单张图片推理不太一样因为每张大图可能产生几十到几百个目标。我推荐把推理逻辑封装成一个脚本输入是原始大图路径输出是带检测框的图像、带轮廓的图像、以及标准格式的标注文件。下面是批量推理的核心流程。import cv2 from ultralytics import YOLO model YOLO(best.pt) def process_image(image_path, conf_thres0.25, iou_thres0.5): img cv2.imread(image_path) # 切图推理 crops split_image(img, crop_size640, overlap0.2) all_boxes [] for crop, offset_x, offset_y in crops: results model.predict(crop, confconf_thres, iouiou_thres, imgsz640) obb results[0].obb # YOLOv8-OBB的检测结果 if obb is None: continue # 把crop上的坐标偏移回原图 boxes obb.xyxyxyxy.cpu().numpy() [offset_x, offset_y, offset_x, offset_y, offset_x, offset_y, offset_x, offset_y] confs obb.conf.cpu().numpy() clss obb.cls.cpu().numpy() for box, conf, cls in zip(boxes, confs, clss): all_boxes.append({box: box, conf: conf, cls: int(cls)}) # 对拼接结果做全局NMS跨切图去重 filtered global_nms(all_boxes, iou_thres) return filtered切图推理的NMS跨图去重是必须的。因为重叠的切图会在边缘产生重复检测如果不去重两辆车之间的车辆可能被检测两次。全局NMS的处理方式不算复杂把所有切图检测框映射回原图坐标后用标准的NMS逻辑过滤一次即可。效果上比直接在crop上做NMS更好因为它能看到目标的全局上下文。6.3 部署到边缘设备的方案取舍如果你最终要部署到本地边缘设备有几个技术路线可以选。CPU部署用ONNX Runtime直接跑FP16模型速度取决于CPU的算力。实测i7-12700上yolov8n-obb处理640x640的图大约需要50到80毫秒能支撑实时处理单个视频流但多路视频或者大图切图推理会吃力。NVIDIA GPU部署TensorRT是最优解把ONNX模型转换后推理速度可以提升3到5倍。yolov8s-obb在RTX 3060上处理单张640x640大约在5到10毫秒已经非常够用。AMD显卡部署如果只能用AMD显卡PyTorch的ROCm分支在Linux下能跑通但TensorRT没有AMD版本ONNX Runtime的ROCm EP也还在完善中。我的实测体验是能用但不建议在正式部署环节用AMD显卡跑OBB模型除非你有专门的时间去调优。端侧FPGA部署FPGA跑YOLO的案例越来越多但OBB模型的angle分支和Probiou Loss算子不一定都被FPGA工具链支持需要先验证算子兼容性。个人建议先跑通CPU/GPU方案FPGA留到后续做量产优化时再评估。7. 实测中常见的误检漏检与排查思路好用的模型都是调出来的。实测过程一定会遇到各种问题这里把我踩过的坑和排查思路整理出来你可以对照着自己的现象去定位。7.1 密集目标的粘连问题车辆密集排列的场景里最常见的现象是两个并排车辆的检测框角度互相干扰NMS后把其中一辆车的框合并到了另一辆车中导致输出结果少了一个框。排查思路先用conf为0.1的低阈值跑一遍推理看底层的检测结果是否包含被抑制的目标。如果有说明是NMS的IoU阈值设得过高如果低置信度下两边预测的角度偏差依然较大说明模型本身对密集朝向的判别能力不够。解决办法有两种。一种是调低NMS的IoU阈值让重叠较多但角度差异明显的框都保留。YOLOv8的NMS对旋转框的角度差异有一定的敏感性把iou从0.5调低到0.3通常能改善。如果还不行就要考虑做数据增广增强——在训练时增加密集场景的样本比例或者在标注时对密集区域做更精细的框对齐。实测后者效果更持久。7.2 小目标漏检问题航拍影像中车辆小到只有10x20像素的情况很常见。这种情况下即使切图、调置信度漏检仍然难以完全避免。针对小目标漏检我的排查顺序是确认imgsz是否够大。把推理的imgsz从640提高到1024看漏检是否有改善。有改善说明模型对小目标特征提取能力不足需要更精细的feature map。确认切图策略是否合理。原图如果存在大量背景模型可能会把小目标当成噪点。把切图大小从640降到512让目标在切图中占比更大有时候会有奇效。如果前两步没用就需要从数据增强入手了。专门做一组小目标增强数据把原图中的目标缩小后再粘贴到新的背景图上手动增加小目标样本的多样性。7.3 角度预测绕圈问题有个有趣的现象同一类目标在训练集和测试集的朝向分布不一样时模型的角度输出会不稳定。比如训练集中车辆朝向基本是水平的测试时出现大量垂直朝向的车辆模型就可能在角度预测上绕圈把垂直朝向的车辆预测成水平加180度。排查时先在验证集上算每个类别的角度误差分布如果某个类别的角度误差方差特别大说明这个类别的角度先验有问题。解决思路是在训练数据中增加该类别在不同朝向的分布让模型学到朝向的泛化性而不是只记住训练集的统计规律。7.4 类别混淆的成因与对策航拍影像中小型车辆和大型车辆的视觉特征在某些场景下非常相似尤其是俯视角度下轿车和SUV的区别可以很小。这种类别混淆靠调超参数基本无解根本路径还是要在标注层面把类别的判定标准写清楚。比如车辆类和卡车类最后一个判据是长宽比还是面积大小在标注规范里就要明确否则不同标注人员的标准不统一模型学到的类别边界就模糊。如果类别混淆问题确实严重也可以考虑把疑似目标的检测框裁剪后送到一个额外的分类模型做二次分类。这个方案的工程落地成本不高但能有效解决航班数据标注标准不一致导致的模型分不清问题。8. 从检测到产出的经验沉淀几点实用建议项目做到尾声有些经验是通用的在这里一次性分享出来。关于旋转框检测的投入产出比我的总体判断是如果你的目标在航拍场景中确实是任意朝向的OBB的投入是值得的。但如果是规则场景的水平目标它的优势并不会有想象中那么大。先想清楚场景需求再决定技术路线的方向这个顺序不能反。关于数据质量标注数据的质量比模型结构本身更值得投入精力。我在多个项目里发现同一个模型结构用标注严格、边界清晰的数据集训练比用标注粗糙、框边界松散的数据集训练mAP提升幅度超过15个百分点。有时候模型精度上不去先回去检查训练数据是否存在标注框偏移、类别错标、朝向偏差这些问题往往比换模型结构更有效。有意思的是标注规范写得越细标注工具的交互设计越顺手数据质量的提升就越明显。关于置信度门限不要迷信默认值。不同场景的误检代价不同门限应根据实际部署需求做调优。我在项目交付时一般会在验证集上画一条precision-recall曲线然后把工作点交给客户去选客户说更看重准确率就把工作点往上调说更看重召回率就往下调。这个决策交给业务方而不是由技术方闭门决定可以减少很多后期沟通成本。关于轮廓提取的精度边界需要接受一个事实旋转框检测得到的轮廓精度是有限的。检测框本身就带有位置和角度的预测误差轮廓提取在这基础上只能做微调不可能做到像素级的完美贴合。如果你需要更精细的轮廓就得叠加真正的分割模型。在大多数业务场景里旋转框检测加水多边形轮廓的方案已经够用了不必在追求极致的路上消耗过多时间。最后一个实践技巧训练和推理的全流程尽量保持可复现。环境依赖版本、数据集版本、训练超参数、模型权重这些信息完整记录下来。项目后期你要复现精度、回溯bug、复现客户结果没有这些记录会非常痛苦。我在做旋转目标检测项目的过程中走过不少弯路从环境版本冲突到角度标注遗漏从切图脚本bug到NMS跨图重复每一步都踩出了经验。希望这篇实践记录能帮你少走一些弯路把更多精力放在真正有价值的调优和分析上。如果你也在航拍影像上做旋转目标检测欢迎带着具体问题来交流。