前阵子在做一个实时特征匹配Demo,一开始图省事直接上了SIFT,精度确实没话说,但笔记本上跑一帧要差不多半秒,调了半天还是卡在算力上。后来换成ORB,同样的流程压到20多毫秒,还给后面的位姿估计留了不少余量。从那次之后我就一直在折腾ORB的参数和匹配策略,也算踩了不少坑。这篇想把ORB从原理到实战完整梳理一遍——它到底牛在哪,为什么能成为实时系统的默认选项,OpenCV里那些参数分别是什么意思,匹配阶段又有哪些容易翻车的细节,希望对正打算用特征点的同学有点帮助。
先一句话概括ORB:它等于"带方向的FAST检测器 + 旋转感知的BRIEF描述子",加一块金字塔解决尺度变化。正是因为把这三个经典模块拼在了一起,ORB才能在CPU上保持实时性的同时,扛住旋转和尺度的考验。接下来我会从算法底层逻辑开始,一步步拆到OpenCV的每个参数,再用真实跑过的配置和坑来验证每个结论。
1. ORB的底层逻辑:FAST不算真正特征,方向与尺度才是分水岭
1.1 角点为什么比图像块更"可靠"
第一次接触特征提取的人往往有个疑问:为什么不直接用整幅图像的像素作为匹配依据?答案是像素本身太脆弱——光照稍微变一下,同一块墙的RGB值就完全不同了。但角点不一样,它是图像中二维梯度都发生剧烈变化的位置,比如桌角、门框交点、书页转折处。这些点在视角变化、光照变化下依然能被稳定找到,而且数量远小于像素数,适合做对应点搜索和几何估计。
一个合格的特征点至少要满足三件事:重复性(换个角度还能找到它)、可区分性(每个点和别的点长得不一样)、计算效率(不能掏空CPU)。ORB里的FAST负责"快",但FAST本身不满足前两条的完整版,所以才需要ORB在此基础上补方向、补尺度、补描述子。
1.2 FAST检测:速度快,但没有任何"身份信息"
FAST的思路很直接:假如某像素点和它周围圆周上的点相比,存在一条连续弧段全都更亮或更暗,那这个像素就很可能是角点。标准做法是在待测点周围取半径3的圆形邻域,共16个像素点,如果其中至少连续9个点的亮度超过中心点亮度加上阈值t,或者低于中心点亮度减去阈值t,就记为角点候选。这个阈值t就是OpenCV里的fastThreshold,默认20,含义是"亮度差异必须超过20才算有效响应"。
FAST的第二个问题是特征点扎堆。一个真正的角点周围往往有几十个像素都满足判定条件,看起来像一团密集的云。ORB作者的处理办法是先用非极大值抑制,在邻域内保留响应最强的点;即便如此,初筛出来的点可能依然很多,所以ORB在FAST之后会计算每个角点的Harris响应,只保留响应最强的前nfeatures个。这个"先FAST后Harris"的设计既有速度又有质量,是我在当时实现里最欣赏的一点。
1.3 ORB打的两个补丁:金字塔和灰度质心法
FAST本身没有方向、没有尺度。一个角点,你把它转30度再检测一次,FAST仍然能在差不多的地方找到它吗?不能严格保证,因为它对邻域做了固定位置的圆周比较。尺度就更明显:手机离建筑物10米和离50米,同一角点在图像里占的像素范围完全不同,FAST的固定圆随尺度变化就失效了。ORB的方案是两个经典操作:
第一个是图像金字塔。构造多分辨率图像层,每层都是上一层按固定比例缩小后的结果。检测时每层都单独跑FAST,特征点会记录自己所在的层编号,这个编号就是它的尺度信息。OpenCV默认scaleFactor=1.2、nlevels=8,也就是说从原图开始一共叠8层,最上层图像约为原图尺寸的0.33倍。有了这一系列尺度,ORB才能处理"物体靠近或远离相机"这类连续尺度变化。
第二个是灰度质心法,用来给每个特征点算一个主方向。做法是在特征点周围的一个圆形patch内,按像素灰度计算图像的矩:
[ m_{pq}=\sum_{x,y} x^p y^q I(x,y) ]
然后得到质心:
[ C=(\frac{m_{10}}{m_{00}}, \frac{m_{01}}{m_{00}}) ]
从特征点坐标指向质心的向量方向,就是该特征点的主方向,角度用(\theta=\tan^{-1}(m_{01}, m_{10}))计算。这个方法的聪明之处在于它完全是统计性的,不依赖梯度直方图,计算量很轻;尤其在配合BRIEF描述子时,一个方向角度可以统一用于后面的坐标旋转对齐,逻辑非常顺。
当时我读到这里有个疑问:为什么不直接算梯度方向直方图?答案就是成本。直方图要统计多个bin,还要平滑,而灰度质心法只需要两次累加就能出角度。ORB的设计哲学一直是"能省则省,省下来的算力全部投入到特征质量和匹配率上"。
2. 描述子设计:BRIEF的二进制审美和rBRIEF的纠偏史
2.1 BRIEF:用256个小问题描述一块局部图像
检测到特征点之后,需要为每个点生成一个"指纹",这就是描述子。SIFT描述子是一个128维浮点向量,精度高但存储和计算都不便宜;BRIEF则走了完全不同的路线:它在特征点附近的固定邻域内,按照某种采样规则选出一组像素对,逐个比较两点亮度大小,亮则记为1,暗则记为0。选256对像素,就得到一个256位的二进制串。这个二进制串就是BRIEF描述子。
为什么BRIEF这么快?因为两次像素比较之间没有依赖关系,可以并行处理,而且比较结果天然适合用位运算处理。匹配时不用算欧氏距离,直接按位比较即可,后面会讲到。256位等于32字节,一个特征点才占32字节存储空间,对比SIFT的512字节,内存占用少了一个量级。在需要维护几十万张图像特征的场景里,这个优势是压倒性的。
2.2 旋转是BRIEF的阿喀琉斯之踵
BRIEF的一个致命问题是:它对旋转极度敏感。原因很简单,BRIEF在采样像素对时,坐标是基于特征点的原始方向坐标轴定义的。图像一旦旋转,同一个物理点的邻域在图像坐标系里整体转了个角度,如果采样坐标还按原来的轴去取,取到的就不再是原本的物理点了,比较结果会完全乱掉。实测图像旋转超过20度以后,BRIEF的匹配率就开始明显下跌;旋转45度时基本匹配不上。
这里可以打一个比方:你和一个朋友约好在广场的旗杆下见面,但如果事先没有约定"旗杆在东边还是西边",你看到的东边那个长椅和朋友看到的东边那个长椅,根本不是同一张。旋转前的BRIEF就是"没有约定方向"的描述子。
2.3 steered BRIEF到rBRIEF:加方向反而导致了"变糊"
既然BRIEF怕旋转,那最简单的方法就是先把图像按特征点的主方向旋转到"标准姿态"再采样,或者等价地,把采样坐标按主方向(\theta)旋转后再取像素。这就是steered BRIEF的思路。但ORB作者实测后发现一个尴尬现象:加了方向校正的BRIEF,在旋转不变性上是好了,但描述子整体的方差反而下降了,特征之间的区分度也因此变弱。
为什么?因为方向一固定,原本随机散布在邻域内的点对会被统一旋转到接近同一角度,彼此之间出现更高的相关性。高相关性意味着信息冗余:你用两个高度相关的像素对去判断"像不像",其实只是把同样的问题问了两遍,区分性自然降低。
rBRIEF则是在这个基础上做了一次"点对优选"。它从邻域内所有可能的像素对中,用贪心策略挑选出一组满足两个条件的点对:单个点对的方差足够大,点对之间的相关性足够低。最终选出256个点对作为固定采样模式。这一步的目标和PCA追求的目标本质上是一样的:最大化信息量,最小化冗余。只是PCA是线性变换找主成分,rBRIEF是离散挑选点对,机制不同,但思路殊途同归。
2.4 汉明距离:为什么ORB匹配天生适合CPU
描述子变成了位串,匹配操作也就变成了位运算。两个ORB描述子之间的距离通常用汉明距离,也就是两个位串之间不同比特的个数。CPU上计算汉明距离有专门的指令,一条POPCNT能直接统计一个寄存器里有多少个1,两个描述子XOR之后统计1的数量就是距离。这一个描述子匹配的时间大概只要几十个CPU周期,比SIFT的128维浮点欧氏距离快一个数量级还多。
所以ORB的整套设计是自洽的:检测快、描述子生成快、存储省、匹配快。这四个特性串起来,才让它在需要实时的场景里脱颖而出,比如定位、拼接预览、视频追踪这类任务。后面我要讲的词袋模型,其实也是因为二进制描述子够小够快才被带起来的。
3. OpenCV实战:ORB_create参数逐个拆解与一次调优全过程
3.1 ORB_create那十个参数到底在管什么
OpenCV的Python接口封装了完整的ORB实现,创建检测器就一句话:cv2.ORB_create()。但它背后有十几个参数,很多人直接默认值一把梭,遇到问题不知道从哪下手。我把最常用的参数列出来,你看一眼就知道每个旋钮的作用:
| 参数 | 默认值 | 作用 | 常见调整方向 |
|---|---|---|---|
nfeatures | 500 | 最多保留的特征点数量 | 点太密就降低,点不够就调高 |
scaleFactor | 1.2 | 金字塔相邻层的缩放比例 | 需要更大尺度覆盖就改1.5 |
nlevels | 8 | 金字塔层数 | 尺度范围不够就增加 |
edgeThreshold | 31 | 距离图像边界多近的特征点会被忽略 | 边界伪点严重时可调小 |
firstLevel | 0 | 金字塔哪一层作为原始层 | 一般保持默认 |
WTA_K | 2 | 每个描述子位由多少像素比较产生 | 想要更强区分度调3或4 |
scoreType | HARRIS_SCORE | 排序特征点的响应类型 | 追求速度可换FAST_SCORE |
patchSize | 31 | 描述子采样所在邻域大小 | 一般不动 |
fastThreshold | 20 | FAST角点判定的亮度差阈值 | 低纹理场景可下调到10以下 |
这里重点说三个我踩过坑的参数。WTA_K如果改成3或4,描述子每个比特不再是比较两个像素谁亮,而是比较3个或4个像素中谁最亮,并记录索引。这样每个比特携带的信息更多,区分性更强,但匹配时的距离范数变了——必须用NORM_HAMMING2,如果还在用NORM_HAMMING,要么报错要么算出错误距离。我有一版代码就是改了WTA_K忘改匹配范数,结果全场匹配率掉到接近零,排查了半天。
scoreType决定最终保留哪些特征点。默认是ORB_HARRIS_SCORE,会先算Harris响应再排序,质量高一点但额外消耗一点算力;如果实时性吃紧,可以换ORB_FAST_SCORE,直接用FAST的响应强度做排序,速度快不少,代价是特征点的重复率略差。我的实际经验是:只要不是特别追求极限帧率,默认的HARRIS_SCORE更省心。
fastThreshold是FAST检测时的亮度差阈值,平时跑普通室内场景默认20没问题;到了低纹理场景,比如白墙、走廊、室内开阔区域,20会直接导致找不到几个点。把它降到7或者10,点会明显多起来,但噪声点也会变多。这时候一般我会配合后续匹配的RANSAC验证把错误点过滤掉,而不是在检测阶段追求完美的纯净度。
3.2 金字塔参数的平衡:scaleFactor为什么常见1.2
scaleFactor取多大是个经典问题。取值越大,相邻层图像尺寸差异越大,每层看到的特征尺度越稀疏;取值越接近1,尺度采样越密集,但同样的层数只能覆盖更小的尺度范围。1.2是一个在采样密度和覆盖范围之间取平衡的常见选择。如果你的应用场景尺度变化剧烈,比如无人机低空贴近建筑物,那1.5甚至2.0也合理,但层间距大会导致特征点在连续尺度间跳变,匹配稳定性会略受影响。
一个容易被忽视的细节是nlevels和scaleFactor的关系。当你把scaleFactor调到2.0时,8层金字塔实际覆盖的尺度就是1到1/128,听着很广;但中间层的尺度不连续,一个真实尺度落在两层之间的特征点,在两层中都只能被近似表达,匹配率自然会下降。反过来如果是近距离轻微移动的场景,尺度变化不超过20%,其实用4到5层金字塔就够了,多余层纯属增加计算量。
3.3 一次真实调优案例:从默认参数到定制参数
我之前在一个图像拼接的小Demo里做过一次对照测试。测试集是同一面书架的左、右两张照片,画面中有大量边缘和小物体。默认参数下,nfeatures=500、scaleFactor=1.2、nlevels=8、fastThreshold=20,每帧提取大约500个特征点,匹配后RANSAC筛出的内点大约200个,单帧提取加匹配大约28ms。
我调成:nfeatures=1000、scaleFactor=1.5、nlevels=6、fastThreshold=10,同时把scoreType保持为HARRIS_SCORE。改进后特征点数量上升到约1500(因为阈值降低了),匹配内点数升到450左右,单帧耗时反而只增加到35ms。内点多了,最终计算的单应矩阵明显更稳,拼接接缝处的重影也少了。代价是多花了7ms,在实时预览的场景里完全能接受。
但如果把nfeatures直接顶到5000,情况就变了。特征点数量上去了,匹配对数暴涨,RANSAC要迭代更多次,耗时跳到120ms以上,提升却被验证阶段大量冗余匹配抵消了。这里我想说的经验是:nfeatures并不是越大越好,它应该和图像内容的纹理丰富度匹配。一张白墙为主图片,你给它5000的预算,它也只能找到几十个有效点;一张密集纹理图,500个点又明显不够,瓶颈就变成了重复点的筛选和排序。
4. 匹配策略:二分KNN、比率阈值、几何验证三层过滤
4.1 BFMatcher的正确用法:crossCheck能省一半心
有了描述子,下一步是匹配。最简单的方法是暴力匹配:把所有特征点描述子和另一张图的描述子逐一比较,取距离最近的一个作为匹配对。OpenCV里写起来很简短:
import cv2 import numpy as np img1 = cv2.imread('bookshelf_left.jpg', cv2.IMREAD_GRAYSCALE) img2 = cv2.imread('bookshelf_right.jpg', cv2.IMREAD_GRAYSCALE) orb = cv2.ORB_create(nfeatures=1000, scaleFactor=1.2, nlevels=8, fastThreshold=10) kp1, des1 = orb.detectAndCompute(img1, None) kp2, des2 = orb.detectAndCompute(img2, None) bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(des1, des2) matches = sorted(matches, key=lambda x: x.distance)crossCheck=True是个被低估的参数。它的意思是只保留"A图中的特征点x匹配到B图中的y,同时B图中的y匹配到的最近邻也是x"这样的双向一致匹配。这个条件能直接过滤掉大量歧义匹配,代价是匹配数量稍微减少。我在实际项目中,只要匹配质量比数量重要,就一定会开crossCheck。
4.2 knnMatch和ratio test:为什么比直接match稳得多
进一步的做法是用k近邻匹配,取每个点的前2个最近邻,然后比较这两个最近邻的距离比例。原理来自Lowe提出的ratio test思路:如果最优点和次优点的距离非常接近,说明这个匹配有歧义,很可能匹配错了;只有当最优点明显优于次优点,也就是距离比例足够小的时候,才值得信任。
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=False) raw_matches = bf.knnMatch(des1, des2, k=2) good = [] for m_n in raw_matches: if len(m_n) != 2: continue m, n = m_n if m.distance < 0.75 * n.distance: good.append(m)这个比例阈值取多少合适?Lowe当年对SIFT建议0.8,ORB社区常见的做法是0.7到0.8之间。阈值小,匹配更严格但数量少;阈值大,匹配多但误匹配也多。我一般从0.75起步,如果RANSAC后内点太少就把阈值放开到0.8,如果误匹配明显就把阈值收紧到0.7。这个数值没有标准答案,关键看场景和后续几何验证的强度。
4.3 FLANN加速:LSH是一种为二值描述子设计的索引
当特征点数很多、需要批量匹配时,暴力匹配O(n×m)的开销不可忽视。OpenCV提供了FLANN匹配器,底层用近似最近邻索引加速查询。这里有个关键坑:FLANN默认配置是为SIFT这类浮点特征设计的KDTree,不能直接用于ORB的二进制描述子。ORB要选择LSH(Locality Sensitive Hashing)索引,因为它天然适合二进制串的高维近似检索。标准配置如下:
flann_index_lsh = dict( algorithm=6, # FLANN_INDEX_LSH table_number=6, # 哈希表数量 key_size=12, # 哈希key长度 multi_probe_level=1 # 探测相邻桶数 ) flann = cv2.FlannBasedMatcher(flann_index_lsh, {}) matches_lsh = flann.knnMatch(des1, des2, k=2)table_number越大,检索精度越高但内存也越大;key_size越大,哈希桶越少,查询更精确但可能漏掉真实近邻;multi_probe_level控制探测额外桶的层数,数值越大召回越高。默认值在大多数场景够用。我在6000对6000点的批量匹配里,FLANN+LSH比暴力匹配大约快3到5倍,精度下降在几个百分点以内,算是一笔非常划算的交易。
4.4 RANSAC单应矩阵验证:从"看起来匹配"到"几何上正确"
前面的匹配只考虑了描述子外观相似度,还需要几何验证。二视图匹配里最常用的验证手段是用随机采样一致性(RANSAC)求解单应矩阵(H),然后统计多少匹配对满足这个变换。几何一致的匹配点称为内点,不一致的就是误匹配,直接扔掉。
if len(good) >= 4: src_pts = np.float32([kp1[m.queryIdx].pt for m in good]).reshape(-1, 1, 2) dst_pts = np.float32([kp2[m.trainIdx].pt for m in good]).reshape(-1, 1, 2) H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 3.0) good_ransac = [m for m, ok in zip(good, mask.ravel()) if ok]ransacReprojThreshold默认是3.0,单位是像素,表示内点允许的投影误差。这个值越小,留下的匹配越严格;匹配点本来就少的时候可以放宽到5.0甚至7.0。我印象很深的一次是匹配对里混了一堆书架木纹的错误匹配,ratio test只过滤了其中一半,RANSAC阈值一开始设1.0,结果内点数量骤降到80多个;放宽到5.0,内点回到300个,但能明显看到有少量误匹配混进来。最终取3.0,内点稳定在250个左右,效果最好。
三层过滤缺一不可:crossCheck负责粗筛,ratio test负责歧义,RANSAC负责几何一致性。顺序上RANSAC一定放最后,因为它的输入必须是比较精致的小集合,否则迭代次数会爆炸。
5. ORB的边界:光照、重复纹理、圆对称,以及和深度特征的取舍
5.1 光照剧烈变化:基于强度比较的天然短板
ORB描述子做的是像素间亮度比较,而这有一个隐含假设:同一物理位置在不同图像中的相对亮度大体不变。这个假设在亮度整体变化时基本成立,但在局部光照剧烈变化时可能崩掉。比如窗户附近被阳光直射的墙面,阴影边缘两侧的亮度关系会发生局部翻转,原本"像素A比像素B亮"的比较结果可能反转,导致描述子错判。
缓解办法有两个:一是用直方图均衡或CLAHE做预处理,把图像亮度归一化到相似分布再提特征;二是在检测阶段适当提高fastThreshold,避免把光照变化区域里的不稳定角点纳入候选。我在一个室内移动机器人的定位场景里对比过,加了CLAHE之后,匹配内点数大概涨了15%到20%,代价只是每帧多花几毫秒预处理。这个方案性价比很高,建议先试。
5.2 重复纹理与低纹理:描述子的"撞车"问题
砖墙、百叶窗、棋盘格这类重复纹理是ORB的噩梦。因为每个角点在描述子层面长得都一样,BRIEF比较的像素对落在同一块重复图案上,生成的特征串几乎相同。ratio test在重复纹理下也很尴尬:每个点都有一大堆"第二近邻"和最近邻距离一样近,比例接近1,按0.75的阈值全部被拒掉,结果是匹配数骤降甚至为零。
这种情况下方案不多:要么人为引入空间约束,比如把特征点聚类后限定在邻域内搜索;要么换数据集区域,选一些纹理更独特的局部区域作为特征点来源;要么干脆换检测策略,在重复纹理区降低特征点密度,而在独特纹理区多采样。如果场景本身就是规则的工业件表面,那ORB确实不太合适,可以考虑轮廓特征或直接上模板匹配。
5.3 圆对称结构与质心漂移
灰度质心法看起来万能,但它有个物理前提:特征点邻域内的质心要能稳定反映几何方向。对于圆对称结构,比如圆盘、圆形印章、同心圆图案,质心会落在圆心附近,方向角变得极其不稳定,稍微有一点噪声就大幅跳动。这个问题的直接后果是描述子方向错乱,同一特征点在两幅图里的方向可能差几十度,匹配也随之失败。
处理这类场景,一个是放弃ORB方向,直接关闭旋转校正,把问题退化成纯BRIEF;另一个是换用其他特征点,比如BRISK或AKAZE,它们的方向计算方式对对称结构更鲁棒。我在某个圆形工件定位项目里吃过这个亏,后来改用BRISK才把旋转稳定性找回来。如果你要处理的物体有明显的圆对称性,需要提前留个心眼。
5.4 深度学习时代为什么还要用ORB
现在一提特征匹配,很容易被引导到SuperPoint、LoFTR这类深度学习方案上。它们确实能处理光照剧变、大视角、甚至无纹理区域,但在实际工程里有两个硬伤:模型推理需要GPU或大算力,模型体积和依赖链对于嵌入式平台很不友好。很多视觉设备的算力只能满足几百毫瓦的CPU负载,根本没有多余的资源跑神经网络。
ORB的优势恰恰在于它的克制的计算量:一个几百点的特征提取加匹配在普通CPU上只需要几十毫秒,不依赖GPU,没有复杂的运行时。对于实时定位、轻量拼接、嵌入式视觉这类需求,它仍然是许多系统里默认的引擎。深度特征适合做离线处理和高质量建图,经典特征适合做在线响应和轻量化部署,两者不是替代关系,而是选择问题。
6. 从特征到系统:ORB在视觉SLAM前端里的角色与部署坑
6.1 为什么SLAM框架普遍选ORB而不是SIFT
视觉SLAM的典型流程是:提取特征点,与当前地图点匹配,估计相机位姿,更新地图。这个流程每帧都要执行,而且往往在CPU上跑,所以特征提取的实时性直接决定系统能否达到30帧每秒。SIFT在这种场景里基本跑不动,ORB则凭借轻量检测加二进制描述子的组合,成为许多开源SLAM框架的默认前端。
但SLAM还有另一个需求:长期循环闭合检测。系统要判断"现在看到的地方是不是以前来过",需要在历史关键帧的图像里做检索。这种检索如果一帧一帧暴力匹配,代价很高。ORB的二进制描述子在这里能发挥一个隐藏优势:它可以直接进入词袋模型。
6.2 词袋模型:二进制描述子带来的检索红利
词袋模型的思想是:把每张图像中所有描述子"投票"到一组视觉词汇上,图像整体被表示成一个稀疏的词频向量。检索时先比较词频向量,快速筛出少数候选关键帧,再对候选帧做精确的特征匹配。这里要求描述子之间能计算"距离"或至少能聚类,统计上相似度才有意义。浮点描述子也可以做,但比较和聚类开销大;ORB的二进制描述子聚类快、存储小,检索性能自然更好。这也是为什么很多SLAM系统会把ORB和词袋绑定在一起。
6.3 我在部署ORB系统时踩过的三个坑
第一个坑是盲目调高nfeatures。最初为了让匹配更充足,我把特征点上限调到5000,结果在纹理丰富的室内场景里,特征点挤成一团,RANSAC单应矩阵的迭代时间从10ms涨到80ms,最终精度却没有提升。后来把nfeatures降到1500,同时让fastThreshold控制在10到15之间,点数量充足且分布更均匀,整体耗时才回到合理范围。
第二个坑是金字塔层间重复检测。同一角点在不同金字塔层可能被分别检出,造成一个物理点被记录成多个特征点。这样的重复点在匹配时会大量出现"近重复匹配",看似匹配数暴涨,实际信息量没变。解决办法是控制nlevels不要过多,比如室内场景6层足够,同时可以在匹配后对特征点坐标做去重,物理距离小于几个像素的点保留响应最强的一个。
第三个坑是低纹理走廊里点数不足。一开始fastThreshold=20在走廊里几乎提不到特征点,匹配失败率很高。把阈值降到8后点多了,但噪声也多了,我靠两步弥补:先开crossCheck=True洗掉一部分假匹配,再用RANSAC把几何不一致的点剔除,最终在纯白墙面为主的场景里也能稳定保持每帧80到100个有效内点。这个经验说明,检测阈值不是越高越好,关键是要留出足够的候选点给后面的验证阶段去筛。
我自己的体会是:ORB不是一个需要"调到一个完美参数后一劳永逸"的算法,它更像一个工具箱,你得根据场景决定里面每个工具怎么用、用多狠。理解它每一条设计决策背后的动机——为什么用质心法、为什么挑点对、为什么匹配要分层过滤——比记住几个推荐的参数值重要得多。下次你遇到旋转匹配不稳定的问题,至少你知道先去检查方向计算是否被圆对称结构干扰;遇到匹配点不足,你知道往fastThreshold和金字塔层数那边看。这些东西亲手踩过一遍,才算真正把ORB用明白了。