**摘要:**无人机俯拍的路面汽车密集、朝向任意,水平框容易把相邻车辆和背景一起框进来,旋转框(OBB)更贴合。YOLO26-OBB 可切换为端到端输出,省掉 NMS,并去掉 DFL 的额外开销,适合边缘设备。本文从 OBB 表示、数据采集与标注、微调训练,讲到 ONNX/BModel 转换,并给出土星云 SE110S-WA32、WB16、WC8 三款设备的部署方法与实测性能对照。
一、为什么路面汽车检测需要旋转框
无人机在 80~150 米高度俯拍时,一辆 4.5 米长的轿车在 4K 原图里约 60~120 像素,切成 1024 切片后只剩 20~30 像素。车头朝向、停车排布、道路走向都不固定,水平框会把相邻车位、车道线和阴影一起框进来;两辆车挨得近时,水平框大面积重叠,NMS 一压就容易漏掉一辆。旋转框多一个角度参数,框可以贴着车身转,密集停车场景下的定位和计数都更稳。
OBB 还带来下游可用性:车牌识别、跟踪、违停判断都需要车头朝向和车身长边;(cx, cy, w, h, θ) 投到地图或车道坐标系时误差更小。代价是标注更慢,角度有周期性和方形目标歧义,训练和后处理要多写几行代码。
本文的路线是:用 DOTA 预训练模型跑通 OBB 链路,再用自采数据微调成业务类别;训练在 x86 + GPU 上完成,设备只负责推理。
二、YOLO26-OBB 的基础知识
2.1 OBB 怎么表示
OBB 常见表示是 (cx, cy, w, h, θ):中心点、长边、短边、角度。YOLO26-OBB 训练时把 w 规范为长边,θ 是从 +x 轴到长边方向的夹角,顺时针为正,范围 [-45°, 135°),并且旋转 180° 视为同一个框。标注文件则常用 8 个点 x1 y1 ... x4 y4,训练前再转成 xywhr。
车是长条目标,长边短边一互换,角度差 90°;方形车长宽接近,角度一动角点顺序就跳。工程上统一做"长边规范 + 角度对 π 取模",后处理再还原四角;否则同一辆车相邻帧角度来回跳,跟踪器会当成两个目标。
2.2 YOLO26 改了什么
官方名称是 YOLO26,社区也写作 YOLOv26,本文两者混用。它是 Ultralytics 的新一代统一实时模型,检测、分割、姿态、分类和 OBB 共用一套框架。对边缘部署最有价值的改动有四个:去掉 DFL 后检测头更简单;one-to-one 端到端头让推理可以不跑 NMS;Progressive Loss 把监督移向推理头,STAL 改善小目标标签覆盖;MuSGD 混合优化器收敛更稳。
OBB 分支还针对角度做了专门优化:角度损失对长宽比接近 1 的方形目标更稳,解码时处理了角度边界不连续的问题。官方给出的 yolo26s-obb 指标是 9.8M 参数(融合后)、56.7 GFLOPs,DOTA test 上 mAP50 80.9、mAP50-95 54.8。注意这是 15 类 DOTA 的整体指标,不等于"路面汽车"的业务精度;小汽车这一类通常是最难的一项。
2.3 端到端头与损失
传统 YOLO 的 OBB 头输出 (N, 4+nc+1),先做置信度过滤,再跑旋转 NMS。YOLO26 的 one-to-one 头直接输出最多 300 个候选,每行是 cx, cy, w, h, conf, cls, θ;它用一对一标签分配训练,推理时不再依赖 NMS 去重,后处理只做阈值过滤、角度规范化和坐标反算。对边缘设备来说,这省掉了一次 NMS,也省掉了 nms_thresh 这个难调的参数。
但"无 NMS"不等于"没有后处理"。密集停车场里,300 个候选仍可能包含同一辆车的多个近似框,业务上通常还要按类别做 top-k。损失侧,OBB 用 ProbIoU 做回归,再加角度损失和分类损失;DFL 去掉后输出通道更规整,导出和编译都更省事。

图 1 OBB 的几何表示与 YOLO26-OBB 推理链路
三、应用场景
城市交通流量与路口排队。 无人机悬停或沿航线飞行,统计车道占有率、排队长度、车流方向。旋转框能区分相邻车道的车,也能在拥堵时把斜向停靠的车辆分开。
违停与应急车道占用。 固定摄像头有盲区,无人机可以补盲。检测到车辆后结合车道线、禁停区和 GPS/IMU 判断是否压线、是否占道;边缘端只上传结构化结果和告警图。
园区、港口与物流园。 这些场景车辆密集、车型多、堆场不规则,OBB 对卡车、挂车、叉车的贴合度明显好于水平框,适合做车位占用、装卸区排队和异常停留检测。
事故与道路巡检。 事故现场车辆朝向混乱,旋转框有助于判断碰撞角度和占道范围;巡检场景可以把检测结果叠加到正射影像或 GIS 上,形成路段级的车辆密度图。
这些场景的共同点是:画面俯视、目标小、车辆朝向任意、带宽和供电有限。把模型放在 SE110S 这类边缘设备上,现场只传告警和统计值,比全部回传云端更现实。无人机只是数据源,真正难的是数据闭环和现场调参。
四、数据收集与整理
4.1 采集
建议飞行高度 80~150 米,云台俯角 60~90 度,4K 或 1080p、25~30fps,快门不低于 1/500 秒。航线覆盖顺光、逆光、阴影和不同季节,同一区域至少飞 3 个时段,避免模型把光照当成类别特征。视频按 1fps 抽帧,相邻帧用 pHash 去重,否则训练集会被近似帧"灌水"。
公开数据可以先跑通流程。DOTA-v1.0 有 2,806 张航拍图、18.8 万个实例、15 类,其中 small vehicle 和 large vehicle 可以直接映射成小汽车和货车/客车;UCAS-AOD、HRSC2016 也能补充车辆和船舶场景。但 DOTA 只允许研究用途,商业项目要自采、自标,不能直接把 DOTA 权重拿去上线。
4.2 清洗与切片
先删模糊、过曝、云影严重和重复的图,再按 1024×1024 切片,重叠 128~200 像素。切片时同步裁剪标签:被切掉超过 30%~40% 的目标直接丢弃,边缘只露一部分的框打 truncated 标记,避免模型学到"半个车也是车"。小于 8~10 像素的目标建议删掉,强行保留会拉低小目标 AP。
类别上不要一上来分十几类。业务初期用 car、truck、bus 三类就够,工程车、危化品车可以先用 truck 兜底,等数据够了再拆。类别越细,标注一致性越差,量化后越容易混。
4.3 标注
工具可以用 X-AnyLabeling、roLabelImg 或 Label Studio,统一输出 8 点多边形或 4 点旋转框。标注规范要写清楚:框贴车身、长边沿车头方向、包含后视镜但不包含阴影、遮挡超过一半标 difficult、截断车按可见部分标。每批数据抽 10% 交叉复核,重点看长宽比接近 1 的方形车和密集车位。
标完先看角度分布。如果 90% 的车都在 0 度附近,说明数据太"正",换真实航拍图会不适应;某个角度区间明显缺失就补采或做旋转增强。角度直方图比总样本数更能说明数据是否健康。
4.4 划分与增强
划分必须按架次、区域、日期做,不能把同一段视频的相邻切片随机分到训练集和验证集,否则模型是在背答案。推荐 70%/15%/15%,测试集留一个没飞过的区域或另一天的数据,用来观察跨场景泛化。
增强以几何变换为主:随机旋转 180°、水平/垂直翻转、缩放 0.5~1.5、Mosaic、HSV 抖动。OBB 标签要跟着图像一起旋转,别只转图不转框。不要做非等比拉伸,车会变形成"面包";也不要随意大角度裁剪,容易把长车切成两段。Mosaic 最后 10 个 epoch 关掉,让模型在真实分布上收尾。

图 2 从数据整理到边缘部署的链路
五、训练
5.1 起点与配置
从 yolo26s-obb.pt 开始微调,比从零训练稳。输入固定 1024×1024,batch 8~16(看显存),epoch 100~150,优化器用 MuSGD,初始学习率 0.01,余弦退火,warmup 3 个 epoch,close_mosaic=10,patience=20。如果数据少于 5,000 张,先冻结主干前 10 层训 10 个 epoch,再全量解冻,学习率降到 0.001。
|--------|---------------------------|
| 项目 | 建议值 |
| 预训练 | yolo26s-obb.pt(DOTA 15 类) |
| 输入 | 1024×1024,letterbox |
| 类别 | car / truck / bus |
| batch | 8~16 |
| epoch | 100~150 |
| 优化器 | MuSGD,lr0=0.01 |
| 增强 | 旋转 180°、翻转、缩放、Mosaic |
| 量化 | INT8 优先,FP16 兜底 |
5.2 训练命令
yolo obb train model=yolo26s-obb.pt data=road_car_obb.yaml \
epochs=120 imgsz=1024 batch=12 device=0 \
optimizer=MuSGD lr0=0.01 cos_lr=True close_mosaic=10 patience=20
训练完先看 results.png、混淆矩阵和验证集可视化。如果 car 和 truck 大量互混,优先检查标注里的长宽比和类别定义,而不是急着换大模型。
5.3 评估看什么
主指标是 mAP50 和 mAP50-95,但业务上更该看小汽车召回率、每类 AP 和角度误差。DOTA 评测用 conf=0.001 生成 json,再跑 DOTA_devkit;部署阈值则用 0.25~0.4,两者不是一回事。开源例程在 DOTA val 上的 15 类整体 mAP50 是 0.610(FP32/FP16)、0.600(INT8),只说明移植对齐,不代表你的路段精度。
小目标是最容易掉点的地方。1024 输入下,20 像素的车已经只剩几个特征点;如果业务里远处车多,优先上 1280 输入、P2 小目标头,或者在 WA32 上做 2×2 切片。换更大模型带来的收益,往往不如提高输入分辨率和补小目标样本。
六、模型转换
6.1 从 PyTorch 到 ONNX
导出时固定 batch=1、固定 1024 输入,opset 用 13,并关闭 NMS,保留端到端 7 维输出:
from ultralytics import YOLO
m = YOLO("runs/obb/road_car/weights/best.pt")
m.export(format="onnx", opset=13, imgsz=1024, simplify=True, nms=False)
导出后用 onnxruntime 跑同一张图,和 PyTorch 的框、置信度、角度逐项对比。重点看输出是不是 1,300,7、角度有没有整体偏移、letterbox 的 padding 有没有算进去;任何一项不一致,到设备上都会放大成"框偏几像素"或"角度反了"。
6.2 编译 BModel
BModel 在 x86 Linux 主机上用 TPU-MLIR 编译,流程是 ONNX → MLIR → 校准/量化 → BModel。下面以 WA32(BM1684X)为例:
model_transform.py --model_name yolo26s_obb \
--model_def yolo26s-obb.onnx \
--input_shapes \[1,3,1024,1024] \
--mean 0,0,0 --scale 0.0039216,0.0039216,0.0039216 \
--keep_aspect_ratio --pixel_format rgb \
--mlir yolo26s_obb.mlir
run_calibration.py yolo26s_obb.mlir \
--dataset cali_1024 --input_num 200 -o yolo26s_obb_cali
model_deploy.py --mlir yolo26s_obb.mlir \
--quantize INT8 --chip bm1684x \
--calibration_table yolo26s_obb_cali \
--model yolo26s_int8_1b.bmodel
WB16 把 --chip 换成 bm1688,再加 --num_core 2 编双核模型;WC8 换成 cv186x,例程只提供 FP32/FP16,所以用 --quantize F16。--keep_aspect_ratio 必须和训练侧的 letterbox 一致,mean/scale 也要和训练一致;这两处错了,INT8 会掉得很明显。
6.3 校准与精度对齐
校准集不要只用 4 张图跑通。建议 100~300 张 1024 切片,覆盖晴天/阴天/逆光、空路/拥堵、远车/近车,并保留 10%~20% 的难负样本(阴影、车道线、广告牌、空车位)。校准图要按设备端同样的 letterbox 和 RGB 顺序生成,不能直接拿原始 4K 图。
量化后先跑 bmrt_test 看理论时延,再用同一批图对比 FP32/FP16/INT8 的 mAP 和角度误差。整体 mAP 掉 1~2 个点可以接受,但小汽车 AP 掉超过 3 个点就要查校准集;仍不达标就退回 FP16,或者用混合精度表保护角度分支。不要为了省几毫秒,把召回率让出去。
七、三款设备部署与性能
7.1 规格与平台映射
三款 SE110S 的算力、内存和解码能力不同,模型要分开编译:
|-------------|----------|---------------|--------|--------------|------------|
| 设备 | 计算平台 | 整机算力 | 内存 | 视频解码 | 推荐模型 |
| SE110S-WA32 | BM1684X | 32 TOPS@INT8 | 16GB | 32 路 1080p25 | INT8 1b |
| SE110S-WB16 | BM1688 | 16 TOPS@INT8 | 8GB | 16 路智能分析 | INT8 2core |
| SE110S-WC8 | CV186X | 7.2 TOPS@INT8 | 8GB | 8 路智能分析 | FP16 1b |
WA32 余量最大,适合多路汇聚、4K 切片和检测+跟踪;WB16 的 16 TOPS/8GB 更适合 2~4 路;WC8 功耗低,适合单路或 2 路末端。这里的"路数"是估算,实际要按分辨率、帧率和业务进程压测。
7.2 部署链路
推荐链路:RTSP/本地视频 → 硬件解码 → letterbox 到 1024 → BMCV/SAIL 推理 → 置信度过滤、角度规范化、坐标反算 → 跟踪/去重 → 结构化上报。Python 例程适合验证,生产建议用 C++ + BMCV,把解码、缩放、颜色转换放在硬件链路上,减少拷贝。
python3 yolo26_opencv.py --input datasets/test \
--bmodel models/BM1684X/yolo26s_int8_1b.bmodel \
--dev_id 0 --conf_thresh 0.25
后处理按 1,300,7 解析:前四列 cx,cy,w,h,第五列置信度,第六列类别,最后一列角度。先按置信度过滤,再把 w<h 的框交换宽高、角度加 90°、对 π 取模,最后减 padding、除以缩放比映射回原图。若导出 one-to-many 头,要补旋转 NMS。
7.3 性能测试结果
下表把官方 yolo26_opencv.py 的 mAP 与 bmcv.soc 的端到端时延放在一起对照;测试输入 1024×1024、batch=1,精度为 DOTA val 15 类整体 mAP50:
|-------------|------------|-----------|-------------|-----------|-------------|
| 设备 | 模型 | mAP50 | bmrt 时延 | 端到端时延 | 端到端 FPS |
| SE110S-WA32 | INT8 1b | 0.600 | 10.54 ms | 42.2 ms | 23.7 |
| SE110S-WB16 | INT8 2core | 0.600 | 24.27 ms | 83.2 ms | 12.0 |
| SE110S-WC8 | FP16 1b | 0.610 | 89.57 ms | 148.1 ms | 6.8 |
WA32 理论推理 10.54ms,端到端 42.2ms,差距主要来自解码和预处理;WB16 从 24.27ms 涨到 83.2ms,WC8 从 89.57ms 涨到 148.1ms,原因相同。多路场景想拉 FPS,先优化解码/缩放链路,比换模型有效。
精度上,INT8 比 FP32 整体只掉 1 个点左右,WA32 和 WB16 都能接受;WC8 例程没有 INT8,FP16 的 0.610 与 FP32 持平,但 89.57ms 决定了它更适合单路。WA32 的 FP16 理论 17.24ms、端到端约 48ms,仍有 20 FPS 左右;WB16 的 FP16 双核理论 51.63ms、端到端约 111ms,只有 9 FPS,精度优先时才用。
按每路 5 FPS、留 20% 余量估算,WA32 可带 4 路左右,WB16 约 2 路,WC8 约 1 路。这个数字不是硬上限,切片、跟踪和上报都会吃 CPU;现场要按"最坏一帧"的延迟来定,而不是只看平均值。

图 3 三款 SE110S 的推理时延与吞吐
7.4 例程跑通与替换
先用官方例程验证模型和前后处理,再替换成自己的 bmodel。Python 例程在 DOTA P0014 上的输出如下,检测框、类别和置信度都能正常叠加,说明 1,300,7 的解析、letterbox 反算和角度还原是对的。

图 4 官方 Python 例程输出
图里的标签比较密,是 15 类 DOTA 模型的原样输出;业务模型改成 3 类、把阈值提到 0.35 后,画面会干净很多。先跑通单张图,再跑文件夹,最后接 RTSP,不要一上来就多路并发。
八、检测效果
下图是同一张 DOTA 测试图的模型推理可视化。橙色是小汽车,蓝色是货车/客车;密集停放的重卡排成斜列,旋转框仍然贴着车身,没有把相邻两辆框在一起;道路上的小汽车只有十几像素,也能单独分出框。

图 5 路面汽车 OBB 检测效果
这张图能看出 OBB 的价值,也能看出问题:右侧几辆白色货车对比度低,有漏检;被遮挡的车只露车头,框会偏小;远处小车的置信度偏低,阈值一高就容易漏。业务上别追求"一张图全对",要看整段视频的召回和跟踪稳定性;检测框送进 OBB 跟踪器后,用多帧关联补漏,比单帧调阈值有效。
九、几条工程经验
1. 先把"车"定义清楚。 car/truck/bus 的边界、截断车怎么标、工程车算不算 truck,都要写进规范;否则 mAP 再高,业务也说不清漏了什么。
2. 按架次和区域划分。 相邻帧、同一段航线不能跨训练集和测试集,否则指标虚高,现场一换区域就崩。
3. 小目标优先于大模型。 1024/1280 输入、P2 头、切片,通常比把 s 换成 m 更有效;先看小汽车 AP,再看整体 mAP。
4. 角度规范只写一份。 训练、导出、后处理共用同一个 regularize 函数,长边、模 π、方形目标的处理不能三处各写一套。
5. 校准集要像现场。 校准图覆盖光照、密度和难负样本;INT8 小汽车 AP 掉超过 3 个点,先查校准,再考虑 FP16。
6. 端到端瓶颈在预处理。 用 C++/BMCV、零拷贝、硬件解码,把 decode+preprocess 压下去;只盯 bmrt 的 10.54ms 会误判整机能力。
7. NMS-free 仍要设阈值。 端到端头只是省了 NMS,置信度阈值、top-k、类别内去重仍要按场景调。
8. 合规先行。 无人机飞行、数据采集、车牌和人脸处理都要符合当地要求;原始视频尽量本地脱敏,只上传结构化结果。
十、总结
无人机视角的路面汽车检测,难点不在"有没有模型",而在小目标、任意朝向、密集遮挡和边缘算力四件事同时出现。YOLO26-OBB 用旋转框解掉朝向和密集排列的问题,用端到端头省掉 NMS,用 DFL-free 和更规整的输出降低导出、量化、部署的复杂度。
工程落地可以按这个顺序:DOTA 预训练跑通链路 → 自采数据微调 3 类 → 1024 输入 + 切片补小目标 → INT8 量化并对比小汽车 AP → 在 WA32/WB16/WC8 上分别压测。WA32 适合多路和切片,WB16 适合 2~4 路,WC8 适合单路末端;INT8 端到端 23.7/12.0 FPS,WC8 FP16 6.8 FPS,先把单路跑稳,再谈多路。