CQU CV Project2 特征点匹配 基于opencv

opencv/samples/cpp/generic_descriptor_match.cpp at 2.4 · opencv/opencv

samples\cpp\generic_descriptor_match.cpp 多种描述符匹配算法,提取图像特征点

#include "opencv2/opencv_modules.hpp"
#include <cstdio>

// 检查是否包含OpenCV的非自由模块,如果没有则输出错误信息
#ifndef HAVE_OPENCV_NONFREE

int main(int, char​**​)
{
    printf("The sample requires nonfree module that is not available in your OpenCV distribution.\n");
    return -1;
}

#else

// 包含必要的OpenCV头文件
# include "opencv2/opencv_modules.hpp"
# include "opencv2/calib3d/calib3d.hpp"
# include "opencv2/features2d/features2d.hpp"
# include "opencv2/highgui/highgui.hpp"
# include "opencv2/imgproc/imgproc.hpp"
# include "opencv2/nonfree/nonfree.hpp"

using namespace cv;

// 帮助函数,显示程序用法
static void help()
{
    printf("使用SURF描述子匹配两幅图像的关键点\n");
    printf("格式: \n./generic_descriptor_match <image1> <image2> <algorithm> <XML参数文件>\n");
    printf("例如: ./generic_descriptor_match ../c/scene_l.bmp ../c/scene_r.bmp FERN fern_params.xml\n");
}

// 函数声明:绘制关键点匹配结果
Mat DrawCorrespondences(const Mat& img1, const vector<KeyPoint>& features1, const Mat& img2,
                        const vector<KeyPoint>& features2, const vector<DMatch>& desc_idx);

int main(int argc, char​**​ argv)
{
    // 检查参数数量,不正确则显示帮助信息
    if (argc != 5)
    {
        help();
        return 0;
    }

    // 从命令行参数获取输入信息
    std::string img1_name = std::string(argv[1]);
    std::string img2_name = std::string(argv[2]);
    std::string alg_name = std::string(argv[3]);      // 匹配算法名称,如FERN
    std::string params_filename = std::string(argv[4]); // 参数文件路径

    // 创建通用描述子匹配器,根据算法名称和参数文件初始化
    Ptr<GenericDescriptorMatcher> descriptorMatcher = GenericDescriptorMatcher::create(alg_name, params_filename);
    if( descriptorMatcher == 0 )
    {
        printf ("无法创建描述子匹配器\n");
        return 0;
    }

    // 读取两张输入图像,转为灰度图
    Mat img1 = imread(img1_name, CV_LOAD_IMAGE_GRAYSCALE);
    Mat img2 = imread(img2_name, CV_LOAD_IMAGE_GRAYSCALE);

    // 使用SURF算法提取关键点
    SURF surf_extractor(5.0e3); // SURF特征检测器,阈值设为5000
    vector<KeyPoint> keypoints1, keypoints2;

    // 提取第一幅图像的关键点
    surf_extractor(img1, Mat(), keypoints1);
    printf("从第一幅图像中提取到 %d 个关键点\n", (int)keypoints1.size());

    // 提取第二幅图像的关键点
    surf_extractor(img2, Mat(), keypoints2);
    printf("从第二幅图像中提取到 %d 个关键点\n", (int)keypoints2.size());

    // 使用描述子匹配器进行关键点匹配
    vector<DMatch> matches2to1; // 存储匹配结果,从img2到img1的匹配
    printf("正在寻找最近邻匹配... \n");
    descriptorMatcher->match( img2, keypoints2, img1, keypoints1, matches2to1 );
    printf("匹配完成\n");

    // 绘制匹配结果并显示
    Mat img_corr = DrawCorrespondences(img1, keypoints1, img2, keypoints2, matches2to1);
    imshow("correspondences", img_corr);
    waitKey(0); // 等待按键
}

// 函数定义:绘制两幅图像的关键点及匹配连线
Mat DrawCorrespondences(const Mat& img1, const vector<KeyPoint>& features1, const Mat& img2,
                        const vector<KeyPoint>& features2, const vector<DMatch>& desc_idx)
{
    // 创建足够大的画布,将两幅图像并排放置
    Mat img_corr(Size(img1.cols + img2.cols, MAX(img1.rows, img2.rows)), CV_8UC3);
    img_corr = Scalar::all(0); // 初始化为黑色背景

    // 将img1绘制在画布左侧
    Mat part = img_corr(Rect(0, 0, img1.cols, img1.rows));
    cvtColor(img1, part, COLOR_GRAY2RGB); // 转为彩色

    // 将img2绘制在画布右侧
    part = img_corr(Rect(img1.cols, 0, img2.cols, img2.rows));
    cvtColor(img2, part, COLOR_GRAY2RGB);

    // 在img1的关键点位置绘制红色圆圈
    for (size_t i = 0; i < features1.size(); i++)
    {
        circle(img_corr, features1[i].pt, 3, Scalar(0, 0, 255), -1);
    }

    // 在img2的关键点位置绘制红色圆圈,并连线到img1中的匹配点
    for (size_t i = 0; i < features2.size(); i++)
    {
        // 计算img2关键点在画布上的位置(右侧)
        Point pt(cvRound(features2[i].pt.x + img1.cols), cvRound(features2[i].pt.y));
        circle(img_corr, pt, 3, Scalar(0, 0, 255), -1); // 绘制红色圆圈

        // 绘制从img1到img2的绿色连线
        line(img_corr, features1[desc_idx[i].trainIdx].pt, pt, Scalar(0, 255, 0));
    }

    return img_corr;
}

#endif // HAVE_OPENCV_NONFREE

这个示例文件演示了如何使用OpenCV中的通用描述子匹配器(Generic Descriptor Matcher)对两幅图像进行特征点匹配,主要功能包括:

  1. 使用SURF算法从两幅图像提取关键点
  2. 使用指定的匹配算法(如FERN)匹配两幅图像之间的关键点
  3. 可视化显示匹配结果:将两幅图像并排显示并用线条连接匹配的特征点

我尝试编译运行 发现多种问题

先是老版本Opencv的CMakeLists不支持最新版的Cmake4+

于是我尝试使用旧版Cmake3.10+

发现在编译时,我的cpp编译套件版本又过高.

得出结论 如果没有配套虚拟环境 难以复现老版本opencv的sample

遂放弃旧版,学习新版本

import cv2
import numpy as np

# 读取图片
img1 = cv2.imread('img1.jpg', cv2.IMREAD_GRAYSCALE)
img2 = cv2.imread('img2.jpg', cv2.IMREAD_GRAYSCALE)

if img1 is None or img2 is None:
    print('请确保 demo 目录下有 img1.jpg 和 img2.jpg 两张图片!')
    exit(1)

# SIFT 特征点匹配
def sift_match(img1, img2):
    if not hasattr(cv2, 'SIFT_create'):
        print('当前 OpenCV 不支持 SIFT')
        return None
    sift = cv2.SIFT_create()
    kp1, des1 = sift.detectAndCompute(img1, None)
    kp2, des2 = sift.detectAndCompute(img2, None)
    matcher = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)
    matches = matcher.match(des1, des2)
    matches = sorted(matches, key=lambda x: x.distance)
    img_matches = cv2.drawMatches(img1, kp1, img2, kp2, matches[:30], None, flags=2)
    return img_matches

# Harris + ORB 描述子匹配
def harris_orb_match(img1, img2):
    orb = cv2.ORB_create()
    # Harris 角点检测
    def get_harris_keypoints(img):
        harris = cv2.cornerHarris(np.float32(img), 2, 3, 0.04)
        harris = cv2.dilate(harris, None)
        thresh = 0.01 * harris.max()
        keypoints = np.argwhere(harris > thresh)
        # 转为 cv2.KeyPoint
        keypoints = [cv2.KeyPoint(float(pt[1]), float(pt[0]), 3) for pt in keypoints]
        return keypoints
    kp1 = get_harris_keypoints(img1)
    kp2 = get_harris_keypoints(img2)
    # 用 ORB 计算描述子
    kp1, des1 = orb.compute(img1, kp1)
    kp2, des2 = orb.compute(img2, kp2)
    matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
    matches = matcher.match(des1, des2)
    matches = sorted(matches, key=lambda x: x.distance)
    img_matches = cv2.drawMatches(img1, kp1, img2, kp2, matches[:30], None, flags=2)
    return img_matches

# ORB 特征点匹配
def orb_match(img1, img2):
    orb = cv2.ORB_create()
    kp1, des1 = orb.detectAndCompute(img1, None)
    kp2, des2 = orb.detectAndCompute(img2, None)
    matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
    matches = matcher.match(des1, des2)
    matches = sorted(matches, key=lambda x: x.distance)
    img_matches = cv2.drawMatches(img1, kp1, img2, kp2, matches[:30], None, flags=2)
    return img_matches

# 展示匹配结果
sift_img = sift_match(img1, img2)
if sift_img is not None:
    cv2.imshow('SIFT Feature Matching', sift_img)
harris_img = harris_orb_match(img1, img2)
cv2.imshow('Harris+ORB Feature Matching', harris_img)
orb_img = orb_match(img1, img2)
cv2.imshow('ORB Feature Matching', orb_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

img1

img2

results

Haris+ORB

SIFT

ORB

三种特征点匹配算法的详细阐述(含代码对照)

SIFT算法

SIFT(Scale-Invariant Feature Transform,尺度不变特征变换)是一种强大的特征检测与描述算法:

基本原理:

  • 通过构建高斯差分金字塔(DoG)检测尺度空间中的极值点
  • 对特征点位置进行精确定位和筛选
  • 为每个关键点分配主方向,确保旋转不变性
  • 生成具有128维的特征描述子

代码实现:

# 创建SIFT特征检测器
sift = cv2.SIFT_create()

# 检测特征点并计算描述子
kp1, des1 = sift.detectAndCompute(img1, None)  # 第一张图片的关键点和描述子
kp2, des2 = sift.detectAndCompute(img2, None)  # 第二张图片的关键点和描述子

主要特点:

  • 对图像缩放、旋转、亮度变化和视角变化具有很强的鲁棒性
  • 特征点分布均匀,重复性好
  • 描述子具有较强的区分性

匹配方式:

# 创建暴力匹配器 - 使用L2范数(欧氏距离)进行特征向量匹配,crossCheck=True确保匹配的唯一性
matcher = cv2.BFMatcher(cv2.NORM_L2, crossCheck=True)

# 进行特征匹配
matches = matcher.match(des1, des2)
  • 使用欧氏距离(L2范数)计算特征向量间的相似度
  • 特征向量距离越小,匹配度越高

优缺点:

  • 优点:匹配精度高,鲁棒性强
  • 缺点:计算复杂度高,实时性较差,有专利限制

ORB算法

ORB(Oriented FAST and Rotated BRIEF)是一种高效的特征检测与描述算法:

基本原理:

  • 结合FAST角点检测算法和改进的BRIEF描述子
  • 使用图像金字塔实现多尺度特征检测
  • 通过灰度质心法计算关键点的方向,确保旋转不变性
  • 生成二进制特征描述子

代码实现:

# 创建ORB特征检测器
orb = cv2.ORB_create()

# 检测特征点并计算描述子
kp1, des1 = orb.detectAndCompute(img1, None)  # 第一张图片的关键点和描述子
kp2, des2 = orb.detectAndCompute(img2, None)  # 第二张图片的关键点和描述子

主要特点:

  • 计算效率非常高,适合实时应用
  • 生成的是二进制描述子,占用内存小
  • 对一定程度的旋转、尺度变化有鲁棒性

匹配方式:

# 创建暴力匹配器 - 使用汉明距离进行ORB二进制描述子的匹配
matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

# 进行特征匹配
matches = matcher.match(des1, des2)

# 按照距离排序
matches = sorted(matches, key=lambda x: x.distance)
  • 使用汉明距离计算二进制描述子间的相似度
  • 汉明距离计算效率高(计算不同位的数量)

优缺点:

  • 优点:速度快,无专利限制,适合资源受限设备
  • 缺点:对大尺度变化和视角变化的鲁棒性不如SIFT

Harris角点 + ORB描述子

这是一种混合方法,结合了Harris角点检测器和ORB描述子:

基本原理:

  • 使用Harris算法检测角点(图像中梯度变化显著的区域)
  • 采用ORB算法计算这些角点的描述子
  • 结合两种算法的优势,实现特征点匹配

代码实现 – Harris角点检测:

# 使用cornerHarris函数检测角点
# 参数2是blockSize,表示角点检测的邻域大小
# 参数3是ksize,表示Sobel算子的孔径大小
# 参数0.04是Harris检测器的自由参数
harris = cv2.cornerHarris(np.float32(img), 2, 3, 0.04)

# 膨胀角点,增强显示效果
harris = cv2.dilate(harris, None)

# 设置阈值,只保留显著的角点
thresh = 0.01 * harris.max()

Harris角点转换为KeyPoint:

# 获取超过阈值的角点坐标
keypoints = np.argwhere(harris > thresh)

# 将角点坐标转换为cv2.KeyPoint对象,便于后续处理
# 注意坐标系转换:图像坐标是(行,列),而KeyPoint需要(x,y)即(列,行)
keypoints = [cv2.KeyPoint(float(pt[1]), float(pt[0]), 3) for pt in keypoints]

使用ORB计算描述子:

# 使用ORB计算Harris角点的描述子
# ORB描述子是二进制描述子,计算效率高
kp1, des1 = orb.compute(img1, kp1)  # 计算第一张图片角点的ORB描述子

主要特点:

  • Harris角点检测对旋转具有不变性,但对尺度变化较敏感
  • 通过阈值筛选显著的角点,提高特征点的质量
  • ORB描述子计算效率高,生成二进制描述子

匹配方式:

# 创建暴力匹配器 - 使用汉明距离进行二进制描述子的匹配
matcher = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)

# 进行特征匹配
matches = matcher.match(des1, des2)

# 按照距离排序
matches = sorted(matches, key=lambda x: x.distance)
  • 同样使用汉明距离进行二进制描述子的匹配
  • 通过距离排序筛选最佳匹配点对

优缺点:

  • 优点:特征点定位准确性较好,描述子计算效率高
  • 缺点:对尺度变化的适应性不足,特征点数量可能受限于Harris检测器参数

算法比较

这三种算法各有优缺点,适用于不同的应用场景:

  • SIFT适合对精度要求高、计算资源充足的场景
  • ORB适合需要实时处理、计算资源有限的场景
  • Harris+ORB是一种折中方案,在某些特定场景可能提供更好的平衡

# 展示匹配结果 # 计算并显示三种不同特征点匹配算法的结果 sift_img = sift_match(img1, img2) if sift_img is not None: cv2.imshow(‘SIFT Feature Matching’, sift_img) # 显示SIFT匹配结果 harris_img = harris_orb_match(img1, img2) cv2.imshow(‘Harris+ORB Feature Matching’, harris_img) # 显示Harris+ORB匹配结果

本技术内容仅供学习和交流使用,如有疑问请联系qq2014160588并注明来意。请确保在使用过程中遵守相关法律法规。任何因使用本技术内容而导致的直接或间接损失,作者概不负责。用户需自行承担因使用本技术内容而产生的所有风险和责任。请勿将本技术内容用于任何非法用途。
上一篇
下一篇