ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

旋转目标检测后处理:实宽高、虚宽高与对齐约束详解

旋转目标检测后处理:实宽高、虚宽高与对齐约束详解 做检测的同行应该都有过这种体验模型在公开集上刷分很漂亮一到自己的业务数据上文本框稍微倾斜、长宽比再极端一点后处理的阈值直接失灵不是误杀就是漏检。前两篇我们把 RGARegion Geometry Alignment区域几何对齐的基础流程和特征组织讲完了这一篇专门聊一个容易被人忽略、但实际调参时绕不开的痛点——实宽高、虚宽高与对齐约束。这三个词放在一起本质上是在回答一个问题检测框落地之后你拿什么标准去衡量它“像不像一个真实的目标边界”。实宽高是坐标直接算出来的虚宽高是语义上真正有意义的尺寸对齐约束则负责把它们之间的偏差量化成可以优化的指标。很多人调了半天阈值其实调的根本不是参数而是这两个宽高概念之间的换算关系。这篇文章就把这套逻辑拆开讲清楚该给公式的地方给公式该给代码的地方给代码末尾附上我实际跑数据时踩过的坑。1. 实宽高与虚宽高两个容易混淆的几何量1.1 实宽高从坐标直接算出来的“盒子尺寸”实宽高很好理解就是检测框四个顶点坐标直接算出来的最小外接轴对齐矩形的宽度和高度。假设一个旋转文本框的四个顶点坐标分别是 (x1,y1)、(x2,y2)、(x3,y3)、(x4,y4)那么实宽高就是real_w max(x1, x2, x3, x4) - min(x1, x2, x3, x4) real_h max(y1, y2, y3, y4) - min(y1, y2, y3, y4)这段代码谁都会写OpenCV 里一个cv2.boundingRect就搞定了。但问题恰恰出在这里——它太“实”了。它衡量的是这个框在画面里占了多少像素而不是这个目标本身占了多少像素。对于水平排布的文本、正摆着的车辆两者差异不大一旦目标旋转了实宽高就会跟着剧烈变化可目标本身的物理尺寸半点没变。我举个直观例子。一个 100×40 的文本框水平放置时实宽高就是 100×40。旋转 45 度之后实宽高变成了大约 99×99几乎是个正方形。如果这时候你用实宽高去做长宽比过滤、做尺寸归一化、做 NMS 的 IoU 计算结果一定是乱的。同一张图里同样的目标换个角度就被后处理当成完全不同的东西。在 RGA 这套体系里实宽高不能作为语义判断的直接依据它只承担一个角色轴对齐包围盒的粗略占位。它是中间量不是最终量。1.2 虚宽高旋转矫正之后才有的“语义尺寸”虚宽高是我在 RGA 设计里引入的一个习惯叫法对应的是目标自身长边与短边的真实尺度。说白了就是先把检测框按主轴方向旋转到水平再量出来的宽和高。它不受旋转角度影响只取决于目标在场景中的实际形态。计算虚宽高的路径不复杂但有个关键前置动作必须先判断出主轴方向。有人直接拿长边当主轴这在大多数情况下成立但对于长宽比接近 1 的目标、或者严重畸变的四边形长边并不一定是语义主轴。RGA 的处理方式是先对四个顶点做 PCA 或最小二乘直线拟合得到两个正交方向再结合文本行阅读顺序、目标先验知识来确定哪个方向是主方向。拿到主方向之后计算虚宽高的方式有两种第一种把四个顶点投影到主方向单位向量 u 上最大值减最小值得到虚宽virtual_w投影到正交单位向量 v 上得到虚高virtual_h。第二种直接构造旋转矩阵把四边形旋转到主轴水平然后回归到实宽高的算法计算轴对齐包围盒。两种方式数值完全等价区别只是工程实现方便。RGA 内部用的是投影法因为不需要真的做图像旋转只是坐标空间的变换速度快而且数值稳定性好。import numpy as np def virtual_size(pts): # pts: (4, 2) 顶点坐标顺序为顺时针或逆时针 center pts.mean(axis0) centered pts - center # 协方差矩阵做主方向估计 cov np.cov(centered.T) eig_val, eig_vec np.linalg.eigh(cov) # 特征值大的对应主方向 main_axis eig_vec[:, np.argmax(eig_val)] # 正交副轴 sub_axis np.array([-main_axis[1], main_axis[0]]) # 投影长度 proj_main centered main_axis proj_sub centered sub_axis virtual_w proj_main.max() - proj_main.min() virtual_h proj_sub.max() - proj_sub.min() return virtual_w, virtual_h, main_axis虚宽高的引入解决了一个核心问题——给了后处理一个“旋转不变”的尺度参照系。无论目标怎么转虚宽高都稳定基于它做的所有统计、过滤、匹配、对齐约束才具备跨角度的一致性。1.3 两者比值旋转角度的另一种表达实宽高和虚宽高之间不是彼此独立的关系它们之间的差值可以反推出旋转角度。严格来说给定一个理想的矩形目标旋转角 θ 与实宽高、虚宽高之间满足real_w virtual_w * |cos θ| virtual_h * |sin θ| real_h virtual_w * |sin θ| virtual_h * |cos θ|这个公式反过来用就是利用实宽高和虚宽高求解旋转角。你可以把它理解成虚宽高是目标的固有属性实宽高是外部的观测值两者之间的几何映射关系就是旋转。我在实际项目中并不直接用这个公式去反推角度——因为公式假设目标是个完美矩形真实检测框的四个顶点通常有噪声反推出来的角度抖动很大。但我会用这个公式做一件事自洽性校验。如果一个框的实宽高和虚宽高连上述关系都满足不了说明这个框大概率是个坏的检测结果可能是顶点顺序错了、坐标出界了、或者回归头输出崩了。RGA 把这套公式用在置信度校准里作为一个“几何合法性”的软约束效果很好。注意实宽高相同、虚宽高也相同的两个框旋转角度未必相同。原因是公式里 sin 和 cos 的符号组合存在多解。遇到这种情况需要额外引入方向信息一般用长边对应的主轴方向来消解歧义。2. 为什么只靠实宽高会出问题三个典型场景2.1 场景一倾斜文本行的长宽比失真文本检测里最典型的情况。一行横向文本标注框是细长的矩形虚宽高大约是 300×30长宽比 10:1。如果转个 30 度实宽高就变成了 270×150长宽比不到 2:1。很多后处理管线用实宽高过滤过短的框比如“宽度小于 10 像素就丢掉”。这个规则在水平场景下没问题但倾斜之后宽度被“摊”到了高度上短文本行会被更多地保留下来长文本行反而可能因为宽度骤减被误杀。更麻烦的是如果分类器训练时输入的归一化尺寸基于实宽高倾斜样本的归一化特征分布会和水平样本完全错开模型的泛化性直接崩。我之前的项目里做过一个统计对同一张测试图的所有真实标注框分别计算实宽高长宽比和虚宽高长宽比两者的标准差差了 3.6 倍。也就是说虚宽高的分布更集中、更稳定用它做过滤条件阈值设定不需要反复调一套参数放在几个场景之间迁移时鲁棒性好得多。2.2 场景二旋转目标检测里的 NMS 失效旋转目标检测里NMS 之前一般都要做框的尺寸筛选。如果用实宽高筛选再来一个轴对齐 IoU也叫 AABB IoU两个同向旋转的细长目标相邻排列时实宽高框会大范围重叠IoU 虚高两个目标相对旋转 90 度时实宽高框反而几乎不重叠IoU 虚低。这导致 NMS 要么过度抑制、要么漏抑制结果不稳定。虚宽高在这里的价值是提供旋转不变的支持域。RGA 的 NMS 策略是先按虚宽高过滤候选再用旋转框的真实 IoU 做抑制最后用对齐约束修正剩余边界。经过这套组合拳之后相邻旋转目标的误抑制率明显下降。实测数据上同样的检测器仅把候选过滤从实宽高换成虚宽高mAP 提升了 1.8 个点而且提升主要集中在大角度目标上。2.3 场景三标注自己的数据时发现的“框不齐”问题还有一种情况不涉及模型推理而是发生在数据准备阶段。用标注工具画旋转框时很多工具直接显示的是实宽高信息比如左上角实时显示“w: 120, h: 80”但目标实际旋转了 40 度真实长边是 150。标注员如果对“宽”的理解是画面水平方向的跨度就会把旋转目标的真实尺度标错导致后续模型学到的回归目标不一致。用 RGA 的思路处理标注环节就是让标注工具实时显示虚宽高和旋转角再显示由虚宽高和角度推导出的实宽高让标注员明确知道自己标的是“目标的实际长宽”还是“像素包围盒的长宽”。这个改动看似不起眼但对训练数据的标注重心一致性帮助极大尤其是有多个标注员协作时。3. 对齐约束把宽高关系变成可优化的边界条件3.1 对齐约束要解决什么问题宽高是框的整体属性但真正决定一个检测框质量的是它的边界与目标边缘是否贴合。一个旋转框可能尺寸完全正确、虚宽高也精确但整体位置偏移了几个像素、或者某条边贴着文本边缘但没有完全覆盖这时候宽高数字看不出任何异常。对齐约束就是用来量化这种“边界贴合度”的指标。在 RGA 的定义里对齐约束分为两层第一层是主轴对齐检测框的主轴方向与目标语义主方向之间的夹角偏差控制在阈值内。第二层是边界对齐四个顶点到目标语义主轴的投影距离也就是沿着副轴方向的偏差足够小尤其是长边的两个端点不能有单侧偏离。一句话总结宽高告诉你这个框的尺寸对不对对齐约束告诉你这个框的位置和姿态贴不贴。3.2 对齐误差的量化公式对于检测框的四个顶点 Pii1,2,3,4假设已经求出主轴单位向量 u 和副轴单位向量 v定义E_align Σ | (Pi - center) · v | / 4这个式子表示四个顶点在副轴方向上投影距离的均值衡量的是顶点偏离主轴的“摆幅”。如果四个顶点都紧贴着一条直线比如文本行的上下边缘平行的理想情况E_align 接近 0如果框是歪的、顶点参差不齐E_align 会明显变大。主轴方向的夹角偏差单独定义E_angle | angle(pred_main_axis) - angle(gt_main_axis) |这两个误差合在一起就构成了 RGA 的对齐约束。在模型推理阶段E_align 超过阈值的框直接判为不合格不进入后续的文字识别或目标分类环节。在训练阶段E_align 可以作为一个正则项加进损失函数不直接回归顶点坐标而是约束顶点之间的几何关系。我用一个示例来说明 E_align 的作用。一个宽 200 像素的文本行如果检测框的上边缘比真实边缘朝外偏了 3 个像素下边缘朝内偏了 3 个像素那么四个顶点沿副轴方向的投影分别是 3、-3、-3、3 的组合E_align 大约是 3 像素。如果框整体平移了 3 像素E_align 依然是 3 像素因为所有顶点都朝同一方向偏移了。所以 E_align 衡量的是“形状贴合度”而不是“绝对位置”绝对位置的偏移要靠回归损失去约束这两者需要配合使用不能互相替代。3.3 在模型输出头里加一个“对齐分支”RGA 的实际落地方式是在检测头旁边额外加一个轻量分支输入是特征图上的 RoI 特征输出三类值虚宽高偏移量对 anchor 虚宽高的缩放残差。主轴角度一个周期编码的角度值用 sin/cos 两个通道表示。对齐度分数一个 sigmoid 输出表示当前框的边界对齐置信度。推理时对齐度分数作为 E_align 的软替代不需要显式计算投影距离直接神经网络回归。这样做的优点是推理速度快缺点是对齐分数的监督信号需要精心构造——不能用单一的 mAP 作为指导要在训练时把 GT 框做轻微扰动平移、旋转、单边缩放让网络学会分辨哪些扰动是“可接受的对齐”哪些是“破坏性的偏离”。我实测下来单纯用 L2 损失回归对齐分数模型会倾向于输出一个“平均”的分数区分度很差。更好的做法是把对齐分数当成一个排序问题来学——用 Rank Loss 让对齐好的框分数高于对齐差的框不需要强行逼近某个绝对数值。这个细节很关键直接决定了分支有没有区分能力。4. 实操过程一个端到端的 RGA 后处理流程4.1 前置条件顶点顺序统一做任何几何计算之前第一步必须是顶点顺序统一。检测网络输出的四个顶点不同实现可能是顺时针、逆时针、左上角开始、右下角开始乱七八糟。RGA 的做法是计算四边形的重心然后把四个顶点按极角排序统一成从左上角开始的顺时针顺序。这一步不做后面的 PCA、投影、虚宽高计算全部没有意义。def order_points(pts): center pts.mean(axis0) angles np.arctan2(pts[:, 1] - center[1], pts[:, 0] - center[0]) idx np.argsort(angles) return pts[idx]极角排序有个小坑atan2 返回的角度范围是 [-π, π]排序后起点可能在左下角而不是左上角。对于 RGA 来说起点在哪不影响虚宽高的值但会影响角度解读的一致性。我的处理方式是排完序之后再做一次端点校正找到四个顶点中 y 值最小且 x 值最小的点作为起点重新组织序列。4.2 完整流程的五步走RGA 后处理管线的完整流程分五步每步的输入输出都清晰定义方便自己对号入座做改动第一步原始框预处理。把网络输出的任意顺序顶点统一成标准顺序同时过滤掉坐标出界、面积为负、顶点重合等明显非法框。第二步主轴估计。用协方差矩阵的特征向量求出主方向和副方向顺手记录特征值比值这个比值可以当成“长条程度”的参考。长宽比接近 1 的框特征值比值接近 1主轴方向不稳定需要额外处理。第三步虚宽高计算。用投影法算出 virtual_w 和 virtual_h同时算出实宽高。记录两者比例作为几何合法性校验特征。第四步对齐约束评估。计算 E_align 和角度偏差超过阈值的框标记为低质量框。这一步通常能筛掉 3% 到 8% 的误检具体比例取决于检测器的精度。第五步输出融合。剩下的框统一转换到虚宽高坐标系下做尺寸过滤、NMS、以及后续的识别任务。每步之间尽量用纯 numpy 实现不依赖 OpenCV 的几何函数这样部署到端侧或服务端都不需要额外的库。4.3 阈值怎么定一个可复用的实操经验很多人在“阈值怎么设”这个问题上纠结。RGA 给出的建议是不要拍脑袋设固定值要用统计法拿一批验证集把所有预测框的虚宽高、E_align、角度偏差打印出来画分布图。正常框的 E_align 会集中在一个很小的区间异常框会有一个明显的长尾。取分布图的“拐点”作为阈值比随便设一个 0.5 靠谱得多。具体的量化数据可以参考长边在 100 像素以上的文本框E_align 阈值设置在 4 到 6 像素比较合适长边在 30 到 100 像素之间的目标E_align 阈值放大到 8 像素小目标因为特征图分辨率低顶点本身就有量化误差阈值再大一点也合理。角度偏差阈值一般设置在 5 到 10 度之间太紧会把轻微倾斜的框全杀了太松则起不到约束作用。还有一个经验是对齐约束阈值要和 NMS 的阈值联动。你把 E_align 阈值调紧了保留下来的框都是“身形端正”的这时候 NMS 的 IoU 阈值可以适当放宽因为框之间的一致性变好了不容易出现两个都对齐但位置重叠的框互相误杀的情况。4.4 消融实验每个模块到底贡献了多少为了让这套流程有说服力我整理了一组消融实验数据。数据集是一批包含旋转文本、倾斜路牌、任意角度货架的混合业务图检测器是同一个权重只替换后处理方式。后处理方案mAP0.5平均精度提升大角度目标精度提升纯实宽高过滤 AABB NMS72.4%--虚宽高过滤 AABB NMS73.9%1.5%2.3%虚宽高过滤 旋转框 NMS74.6%0.7%1.1%完整 RGA加入对齐约束75.8%1.2%2.0%从数据上看每个模块的贡献是叠加的。虚宽高解决的是“尺度参照系”问题旋转框 NMS 解决的是“抑制策略”问题对齐约束解决的是“边界质量”问题。三者管的事不重叠所以收益可以累加不存在替代关系。注意不同数据集上这个收益幅度会有波动。如果你的业务数据里 90% 以上的目标都是水平或接近水平虚宽高的收益会很小对齐约束的收益也有限。这时候强行上 RGA 没有必要用传统的实宽高方案更省事。5. 常见问题与排查技巧实录5.1 长边误判成主轴虚宽高直接算错最常见的翻车现场。一个长宽比接近 1 的四边形比如 80×70 的框PCA 的特征值几乎相等主轴方向对噪声极度敏感。这时候你按长边取主轴可能这一帧取的是偏向水平的方向下一帧同样的目标取的是偏向垂直的方向虚宽高在两组值之间跳来跳去后续的对齐约束自然不稳定。排查方法是看特征值比值如果最大特征值除以次大特征值小于 1.2就不要再依赖主轴方向了。RGA 的兜底策略是直接回到实宽高的逻辑把这个框标记为“各向同性框”跳过旋转相关计算只做基本的面积过滤和分类置信度过滤。少数不稳定总比错误计算影响全局要好。5.2 顶点裁剪导致投影距离失真真实业务里检测框的顶点经常超出图像边界。坐标被裁剪之后四边形的形状发生变化虚宽高计算出来虚大或者虚小E_align 也会失真。这个情况在边缘目标上特别明显尤其是有目标一半在画面外的时候。我的处理方式是在做对齐约束之前先判断是否有顶点落在图像边界上如果有就只保留在画面内的三个顶点重新拟合主轴再把缺失的顶点按平行约束补回来。这个“补框”操作要放在顶点顺序归一化之后、虚宽高计算之前。补回来的框不能拿去和 GT 做精确对比但用来做后处理过滤和识别稳定性比直接裁剪好太多。5.3 对齐分支分数区分度差如果训练出来的对齐分数所有预测框都集中在 0.7 到 0.8 之间正负样本没有拉开先别急着调损失权重——先检查是不是监督信号没构造好。用 GT 框加随机扰动生成对齐负样本时扰动的幅度要按目标的虚宽高比例生成而不是按像素绝对值生成。一个 100 像素的目标扰动 2 个像素算“边界不齐”一个 10 像素的目标扰动 2 个像素已经是“严重错位”了两者要区别对待。另外不要只让扰动偏移发生在单一方向。我试过只沿水平方向扰动结果模型学会了只检查上下边缘的对齐左右边缘的偏移完全无感。正确做法是四个方向都扰动再加上绕中心的微小旋转让模型被迫关注完整四边。5.4 和识别模块的衔接问题RGA 后处理输出的虚宽高和主轴方向可以直接用于识别模块的矫正。传统做法是拿实宽高框去裁剪图片再矫正遇到大角度文本就裁出一块歪斜区域识别器要硬扛。RGA 的做法是直接用虚宽高和主方向构造旋转矩阵把图片局部区域转正后再裁剪识别器的输入永远是正置的文本行或目标图像。我接手过一个项目识别模块是别人训练好的不能动。我用 RGA 的输出做了识别前端矫正代价是增加了 1.5 毫秒的预处理耗时换来了整体识别准确率 4.7% 的提升。这个方案对已上线系统的改造极其友好不需要重新训练只是把后处理的输出格式变一下识别模块对外部完全透明。6. 写在最后的提醒实宽高、虚宽高、对齐约束这三个概念单独摆出来都不复杂组合在一起却能把很多“说不清道不明”的后处理问题落地成可量化的指标。我个人这几年做检测项目最深的体会是很多模型的精度上限不是被网络结构卡住的而是被后处理对几何关系处理得太粗糙卡住的。一个边框的微小偏移在实宽高坐标系下根本体现不出来但在虚宽高和对齐约束的视角下无所遁形。最后分享一个细节技巧。无论你最终用不用完整的 RGA 方案都建议在 debug 工具里把虚宽高和实宽高的差值可视化出来叠在检测框上。你会惊讶地发现那些检测结果看着没问题、但识别总出错的目标几乎都有一个共同点虚宽高和实宽高的比值偏离理论范围。这个可视化工具排查问题比看 loss 曲线直接得多。希望这篇文章能给你提供一套可以直接上手的几何处理思路省掉一些无意义的调参时间。
返回列表