orb特征点检测
特征算子由“关键点检测”和“描述子计算”两部分组成,核心改进是在 FAST 和 BRIEF 的基础上分别引入方向和多尺度机制,兼顾速度、内存占用与匹配鲁棒性。
一、ORB介绍
ORB 算法本质上是将两种已有的成熟算法进行了改进与结合:ORB(Oriented FAST and Rotated BRIEF)
-
特征点检测:FAST(速度极快,但没有方向,没有尺度信息)。
-
特征点描述:BRIEF(二进制描述子,速度快,但对旋转非常敏感)。
ORB 的贡献在于:为 FAST 增加了方向性,并为 BRIEF 增加了旋转不变形。
二、 算法五大步骤
1. FAST 特征点检测 (Detection)
-
原理:检查像素点周围 16 个像素,如果有连续 N个像素(通常N=9或12) 的亮度显著高于或低于中心点,则判定为特征点。
-
ORB 的改进:
多尺度(Scale Invariance):构建图像金字塔。在每一层金字塔上检测 FAST 特征点,从而保证在不同距离下都能检测到物体。
特征点限额:使用 Harris 响应值对检测到的 FAST 点进行排序,只保留前 N个响应最强的点(例如前 500 个)。
2. 赋予特征点方向(Orientation)
这是 ORB 的关键。它使用 灰度质心法(Intensity Centroid):
-
计算特征点周围圆形区域内的矩(Moments)。
-
找到该区域的“质心”(所有像素亮度的重心)。
-
连接“几何中心”和“质心”,得到一个向量。这个向量的方向就是该特征点的主方向(
θ)。
3. 构建描述子:BRIEF (Descriptor)
原理:在特征点周围随机挑选 P对像素点,对比每一对像素的亮度。如果 A>B,记为 1,否则为 0。
结果:最终形成一个 256 位(bit)的二进制串。这种二进制存储极其节省内存,且比对速度极快。
4. 旋转不变性改进:rBRIEF (Steerable BRIEF)
-
普通的 BRIEF 在图片旋转后,选取的像素对位置就乱了。
- ORB 的做法:利用步骤 2 算出的方向
θ,将 BRIEF 选取的随机点对进行旋转平移。
-
这样,无论图片怎么转,描述子选取的像素对永远相对于特征点的主方向对齐。
5. 解决描述子相关性:学习优选
-
BRIEF 随机选取的点对并不一定都是有效的。ORB 通过贪心算法,从大量点对中挑选出那些区分度最高、相关性最低的点对,从而提高识别精度。
三、 ORB 的优缺点
| 维度 | 表现 | 备注 |
| 速度 | 极快 | 是 SIFT 的 100 倍,SURF 的 10 倍左右。 |
| 版权 | 免费 (开源) | SIFT/SURF 曾受专利保护(现已过期,但 ORB 依然是 BSD 协议首选)。 |
| 存储 | 极小 | 256bit 二进制串,非常适合移动端和嵌入式设备。 |
| 匹配 | 汉明距离 | 使用 XOR 异或运算匹配,比欧式距离计算快得多。 |
| 旋转不变性 | 优秀 | 归功于灰度质心法。 |
| 尺度不变性 | 一般 | 依赖图像金字塔,效果略逊于 SIFT。 |
| 光照敏感度 | 中等 | 对大幅度的亮度变化敏感。 |
四、 匹配方式:汉明距离 (Hamming Distance)
对于 ORB 这种二进制描述子,我们不计算复杂的根号运算,而是看两个二进制串有多少位不同。
-
运算:count_set_bits(string1 XOR string2)。
-
特点:在 CPU 上是一条指令,速度达到了极致
五、Python OpenCV 代码实现
import cv2 # 1. 加载图片并转为灰度图 img = cv2.imread('0.jpg', cv2.IMREAD_GRAYSCALE) # 2. 初始化 ORB 检测器 # nfeatures: 保留的最大特征点数量 orb = cv2.ORB_create(nfeatures=500) # 3. 检测特征点并计算描述子 keypoints, descriptors = orb.detectAndCompute(img, None) # 4. 绘制特征点 img_with_keypoints = cv2.drawKeypoints(img, keypoints, None, color=(0, 255, 0)) cv2.imwrite("result.jpg", img_with_keypoints)

然后看下面的代码,给出了匹配上的特征点。
import cv2 # 1. 加载两张图片(建议转为灰度图进行检测,效果更稳定) img1 = cv2.imread('1.jpg') img2 = cv2.imread('2.jpg') gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY) gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY) # 2. 初始化 ORB 检测器 # nfeatures: 增加到 1000 个点,可以让连线更丰富 orb = cv2.ORB_create(nfeatures=1000) # 3. 检测特征点并计算描述子 kp1, des1 = orb.detectAndCompute(gray1, None) kp2, des2 = orb.detectAndCompute(gray2, None) # 4. 创建匹配器 (BFMatcher) # 对于 ORB,必须使用 cv2.NORM_HAMMING # crossCheck=True 表示两点必须互相是对方的最优匹配,这能过滤掉很多错误的连线 bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) # 5. 进行匹配 matches = bf.match(des1, des2) # 6. 对匹配结果按距离排序(距离越短表示匹配度越高) matches = sorted(matches, key=lambda x: x.distance) # 7. 绘制匹配结果 # 我们只绘制前 50 个最优秀的匹配点,否则画面会太乱 # flags=2 表示只绘制匹配成功的点,不绘制未匹配的孤立点 img_matches = cv2.drawMatches( img1, kp1, img2, kp2, matches[:50], None, flags=cv2.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS ) # 8. 保存并显示结果 cv2.imwrite("orb_matches.jpg", img_matches) # 如果是在有界面的环境下运行,可以取消下面两行的注释 # cv2.imshow("ORB Matches", img_matches) # cv2.waitKey(0) print(f"匹配完成!共找到 {len(matches)} 个匹配点,已保存前 50 个最佳匹配到 orb_matches.jpg")

六、 应用场景
-
实时 SLAM:如 ORB-SLAM,是自动驾驶和无人机领域最著名的视觉算法之一。
-
全景拼接:快速找两张图的重合点。
-
三维重建:多视角特征匹配。
-
移动端图像搜索:由于内存占用极小。
七、用于同类图片分类
假设有四类图需要分类,如下:

代码如下:
import time import cv2 import numpy as np import matplotlib.pyplot as plt class RotatedStickerClassifier: def __init__(self, method='orb'): """ method: 'orb' 或 'sift' ORB: 免费,速度更快 SIFT: 更准确,但有专利(OpenCV 4.4.0+ 已免费) """ if method == 'orb': # 使用ORB self.detector = cv2.ORB_create(nfeatures=1000) # 为ORB使用不同的匹配器 self.index_params = dict(algorithm=6, # FLANN_INDEX_LSH table_number=6, key_size=12, multi_probe_level=1) self.search_params = dict(checks=50) else: # 使用SIFT self.detector = cv2.SIFT_create() self.index_params = dict(algorithm=1, trees=5) # FLANN_INDEX_KDTREE self.search_params = dict(checks=50) self.method = method self.templates = {} # 存储模板特征 self.template_images = {} # 存储模板图片 def create_matcher(self, descriptors): """动态创建匹配器""" if self.method == 'orb': # ORB使用LSH return cv2.FlannBasedMatcher(self.index_params, self.search_params) else: # SIFT使用KDTree return cv2.FlannBasedMatcher(self.index_params, self.search_params) def add_template(self, name, image_path, roi=None): """添加模板图像""" img = cv2.imread(image_path) if roi: x, y, w, h = roi img = img[y:y + h, x:x + w] # 存储原图 self.template_images[name] = img.copy() # 提取特征 gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) keypoints, descriptors = self.detector.detectAndCompute(gray, None) if descriptors is None: print(f"警告:无法从模板 {name} 提取特征") return False self.templates[name] = { 'kp': keypoints, 'des': descriptors, 'image': img } print(f"模板 {name} 添加成功,提取到 {len(keypoints)} 个特征点") return True def match_with_ransac(self, query_des, template_name, min_matches=10): """使用RANSAC进行鲁棒匹配""" template_data = self.templates[template_name] template_des = template_data['des'] if query_des is None or template_des is None: return 0, None # 创建匹配器 matcher = self.create_matcher(query_des) # 安全的KNN匹配 good_matches = [] try: # 尝试k=2的匹配 matches = matcher.knnMatch(query_des, template_des, k=2) # 应用Lowe's ratio test(安全版本) for match_pair in matches: if len(match_pair) < 2: continue # 跳过不足2个匹配的情况 m, n = match_pair # 标准ratio test if m.distance < 0.75 * n.distance: good_matches.append(m) except Exception as e: print(f"KNN匹配错误 ({template_name}): {e}") # 回退到简单匹配 try: bf = cv2.BFMatcher(cv2.NORM_HAMMING if self.method == 'orb' else cv2.NORM_L2) matches = bf.match(query_des, template_des) matches = sorted(matches, key=lambda x: x.distance) # 使用距离阈值过滤 max_distance = 0.7 * matches[0].distance if matches else 100 good_matches = [m for m in matches if m.distance < max_distance] except Exception as e2: print(f"回退匹配也失败: {e2}") return 0, None if len(good_matches) < min_matches: # print(f"匹配点不足: {len(good_matches)} < {min_matches}") return len(good_matches), None # 提取匹配点 try: query_pts = np.float32([self.query_kp[m.queryIdx].pt for m in good_matches]) template_pts = np.float32([template_data['kp'][m.trainIdx].pt for m in good_matches]) # 使用RANSAC估计单应性矩阵 if len(good_matches) >= 4: # 单应性矩阵至少需要4个点 H, mask = cv2.findHomography(template_pts, query_pts, cv2.RANSAC, 5.0) inlier_count = np.sum(mask) return inlier_count, H else: return len(good_matches), None except Exception as e: print(f"点提取或RANSAC错误: {e}") return len(good_matches), None def classify(self, query_image, visualize=False): """分类查询图像""" if isinstance(query_image, str): query_img = cv2.imread(query_image) else: query_img = query_image.copy() # 提取查询图像特征 query_gray = cv2.cvtColor(query_img, cv2.COLOR_BGR2GRAY) self.query_kp, query_des = self.detector.detectAndCompute(query_gray, None) if query_des is None: print("无法从查询图像提取特征") return None, 0, None best_match = None best_score = 0 best_homography = None # 与所有模板匹配 for name in self.templates.keys(): inlier_count, H = self.match_with_ransac(query_des, name) # 计算匹配分数 total_kp = len(self.templates[name]['kp']) score = inlier_count / min(len(self.query_kp), total_kp) * 100 if total_kp > 0 else 0 if score > best_score: best_score = score best_match = name best_homography = H print(f"与模板 '{name}' 匹配: {inlier_count}个内点, 分数: {score:.2f}") # 可视化结果 if visualize and best_match: self.visualize_match(query_img, best_match, best_homography) return best_match, best_score, best_homography def visualize_match(self, query_img, template_name, H): """可视化匹配结果""" template_img = self.templates[template_name]['image'] # 这里保持原来的可视化代码... # ... def test_rotation_invariance(self, image_path, angles=[0, 45, 90, 135, 180]): """测试旋转不变性""" img = cv2.imread(image_path) if img is None: print(f"无法读取图像: {image_path}") return [] h, w = img.shape[:2] center = (w // 2, h // 2) results = [] for angle in angles: # 旋转图像 M = cv2.getRotationMatrix2D(center, angle, 1.0) rotated = cv2.warpAffine(img, M, (w, h)) # 分类 match, score, _ = self.classify(rotated) results.append({ 'angle': angle, 'match': match, 'score': score }) print(f"旋转 {angle}度: 匹配到 {match}, 分数: {score:.2f}") return results if __name__ == '__main__': # 初始化分类器 classifier = RotatedStickerClassifier(method='orb') #ORB 明显更快(通常快10-100倍) # classifier = RotatedStickerClassifier(method='shift') # 添加模板 templates = [ ('sticker_A', './templates/1.jpg'), ('sticker_B', './templates/2.jpg'), ('sticker_C', './templates/3.jpg'), ('sticker_D', './templates/4.bmp') ] for name, path in templates: success = classifier.add_template(name, path) if not success: print(f"警告:模板 {name} 添加失败") # 测试旋转不变性 # test_image = r'5.jpg' # print(f"\n测试图像: {test_image}") # results = classifier.test_rotation_invariance(test_image, angles=[0, 45, 90]) # # # 输出结果 # print("\n旋转测试结果:") # for result in results: # print(f"旋转 {result['angle']}度: 匹配到 {result['match']}, 分数: {result['score']:.2f}") print("------------------------------------") test_image=cv2.imread(r'6.jpg',1) t1=time.time() # 分类新图像 match, score, H = classifier.classify(test_image, visualize=True) print(f"识别结果: {match}, 置信度: {score:.2f}%") t2=time.time() print(t2-t1) #0.14ms

最后输出了识别类别与置信度。
小结: 本文主要介绍了orb的原理及使用,给出代码对图片进行分类与测试结果。
若存在错误或不足之处,欢迎评论与指出。

浙公网安备 33010602011771号