一、ORB特征点:为什么它成了视觉SLAM的标配?
做SLAM这些年,我试过不少特征点。SIFT太慢,SURF有专利问题,直到ORB出现,才真正解决了实时性与鲁棒性的矛盾。说白了,ORB就是FAST角点加上BRIEF描述子,再做一些改进。你想想看,一个能在手机上跑30帧的特征提取方法,谁不爱?
我个人习惯把ORB拆成三块来理解:
- 特征点提取:用FAST找角点
- 方向信息:用灰度质心法算主方向
- 描述子:用改进的BRIEF生成二进制描述
嗯,这里要注意,ORB的全称是Oriented FAST and Rotated BRIEF,重点就在"Oriented"和"Rotated"这两个词上。没有方向信息的FAST,旋转后就匹配不上了。
二、FAST角点:快是真的快,但问题也不少
2.1 FAST的核心思想
FAST的全称是Features from Accelerated Segment Test。它的思路很简单:如果一个像素和它周围一圈的像素差别很大,那它很可能是个角点。
具体怎么做?我画个图你就明白了:
判断规则很简单:如果圆周上有连续N个像素的亮度超过或低于中心像素一定阈值,就认为p是角点。N通常取12,这就是FAST-12。我最早用OpenCV跑FAST时,一张640×480的图只要几毫秒,比Harris快了近一个数量级。
2.2 FAST的改进:多尺度与方向
但FAST有个致命问题——它没有尺度不变性。图像一缩放,角点就找不到了。解决办法是构建图像金字塔,每层都提取FAST角点。
还有一个问题:FAST没有方向信息。ORB的解决办法是用灰度质心法:
// 计算图像块的矩
double m00 = 0, m10 = 0, m01 = 0;
for (int y = -r; y <= r; y++) {
for (int x = -r; x <= r; x++) {
double val = image.at<uchar>(cy+y, cx+x);
m00 += val;
m10 += x * val;
m01 += y * val;
}
}
// 质心坐标
double cx = m10 / m00;
double cy = m01 / m00;
// 方向角度
double angle = atan2(cy, cx);
三、BRIEF描述子:二进制才是王道
3.1 BRIEF的原理
BRIEF描述子说白了就是一组二进制位。它在特征点周围随机选128对或256对像素,比较每对的亮度大小,亮的记1,暗的记0。
举个例子,假设我们选了3对像素:
- 对1:p1(10,20) 和 p2(15,25),p1更亮 → 1
- 对2:p3(30,40) 和 p4(35,45),p4更亮 → 0
- 对3:p5(50,60) 和 p6(55,65),p5更亮 → 1
最终描述子就是101...(共256位)。两个描述子的相似度用汉明距离衡量——数一下有多少位不同就行。这个计算用CPU的POPCNT指令,一次能算64位,快得飞起。
3.2 旋转不变性:steered BRIEF
原始BRIEF没有旋转不变性。ORB的改进是:根据之前算出的方向角,把像素对旋转到对应方向再比较。
我记得第一次实现这个时踩了个坑——旋转后的像素坐标是浮点数,需要插值。直接用最近邻插值会导致描述子对旋转敏感,后来改用双线性插值才稳定。
四、特征匹配:暴力匹配 vs FLANN
4.1 暴力匹配
暴力匹配就是拿每个查询描述子,跟所有训练描述子算一遍距离,取最近的那个。代码很简单:
// OpenCV暴力匹配示例
cv::BFMatcher matcher(cv::NORM_HAMMING);
std::vector<cv::DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);
暴力匹配的复杂度是O(N×M),N和M分别是两幅图的特征点数。如果每幅图有2000个点,就要算400万次汉明距离。在CPU上大概要几十毫秒,勉强能接受。
4.2 FLANN匹配
FLANN(Fast Library for Approximate Nearest Neighbors)用KD-Tree或LSH来加速。对于二进制描述子,OpenCV的FLANN默认用LSH(局部敏感哈希)。
// FLANN匹配示例
cv::FlannBasedMatcher matcher(
new cv::flann::LSHParams(20, 10, 2)
);
std::vector<cv::DMatch> matches;
matcher.match(descriptors1, descriptors2, matches);
- 特征点少于500:暴力匹配更快,因为建索引也有开销
- 特征点超过1000:FLANN优势明显,能快3-5倍
- 精度要求极高:暴力匹配更可靠,FLANN是近似解
五、特征点均匀化策略:别让特征扎堆
做SLAM时最怕什么?特征点全挤在纹理丰富的区域,其他地方一个都没有。这样位姿估计会退化,甚至直接崩掉。
我常用的均匀化策略有几种:
5.1 网格化提取
把图像分成M×N的网格,每个网格最多提取K个特征点。这样能保证特征点分布均匀。
// 网格化提取伪代码
int grid_rows = 4, grid_cols = 4;
int max_per_cell = 50;
for each grid cell:
extract FAST corners in this cell
keep top max_per_cell by Harris response
add to final keypoints
5.2 四叉树均匀化
ORB-SLAM用的就是这个方法。它把图像空间递归四等分,直到每个节点包含的特征点数量满足要求。我特别喜欢这个方法,因为它能自适应地分配特征点——纹理丰富的地方节点多,纹理少的地方节点少。
5.3 非极大值抑制
FAST角点经常扎堆出现。非极大值抑制(NMS)能保留响应最强的那个,去掉周围的弱角点。我一般用Harris响应值作为排序依据,效果比直接用FAST得分好。
六、实战建议:ORB参数调优
最后分享一些我在项目中积累的参数经验:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| nfeatures | 1000-2000 | 特征点数量,室内1000够用,室外建议2000 |
| scaleFactor | 1.2 | 金字塔缩放因子,越大尺度覆盖越广 |
| nlevels | 8 | 金字塔层数,8层能覆盖约2.5倍的尺度变化 |
| fastThreshold | 20 | FAST阈值,越大角点越少但越稳定 |
| patchSize | 31 | 描述子计算窗口,31像素是经验值 |
嗯,说到参数调优,我记得有一次在无人机上跑SLAM,图像分辨率是1920×1080,我设了3000个特征点,结果每帧处理时间飙到了80ms。后来降到1500个点,配合四叉树均匀化,稳定在30ms以内。所以我的建议是:特征点数量不是越多越好,够用就行。
ORB这套组合拳,从FAST的快速检测,到BRIEF的紧凑描述,再到均匀化策略保证分布质量,每一步都有它的道理。你在实际项目中遇到问题时,不妨从这几个环节逐一排查,往往能找到症结所在。
公众号:蓝海资料掘金营,微信deep3321