orb特征点检测

   特征算子由“关键点检测”和“描述子计算”两部分组成,核心改进是在 FAST 和 BRIEF 的基础上分别引入方向和多尺度机制,兼顾速度、内存占用与匹配鲁棒性。

 一、ORB介绍

  ORB 算法本质上是将两种已有的成熟算法进行了改进与结合:ORB(Oriented FAST and Rotated BRIEF)

  1. 特征点检测:FAST(速度极快,但没有方向,没有尺度信息)。

  2. 特征点描述: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)

image

   然后看下面的代码,给出了匹配上的特征点。

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")

orb_matches

 

六、 应用场景

  1. 实时 SLAM:如 ORB-SLAM,是自动驾驶和无人机领域最著名的视觉算法之一。

  2. 全景拼接:快速找两张图的重合点。

  3. 三维重建:多视角特征匹配。

  4. 移动端图像搜索:由于内存占用极小。

 

七、用于同类图片分类

  假设有四类图需要分类,如下:

grid_result

   代码如下:

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

image

   最后输出了识别类别与置信度。

 

小结: 本文主要介绍了orb的原理及使用,给出代码对图片进行分类与测试结果。

 

 

 

 

 

 若存在错误或不足之处,欢迎评论与指出。

 

  

 

posted @ 2026-02-09 14:18  wancy  阅读(58)  评论(0)    收藏  举报