OPENCV(python)--初学之路(十八)特征匹配+ Homography查找对象

一前言

今天的更新依旧是周末合刊,而且这是OPENCV的第一阶段的最后一次更新了,我们的下一个系列是YOLOv8,不过需要时间,可能未来一周都不会更新,而且我会同时开一个系列,是关于蓝桥杯JAVA赛道的备赛之路,说实话,我没有一点JAVA基础,所以这是真的零基础备赛,至于这两个系列怎么安排,我也会调整。今天的章节也很有意思,我和十八似乎有种缘分,我家是十八层,高中是十八中,我现在十八岁。哈哈哈

二主要内容

蛮力匹配的基础知识

蛮力匹配器很简单。它采用第一组中的一个特征点描述子,并使用一些距离计算与第二组中的所有其他特征点匹配。并返回距离最近的一个特征点。

对于 BF 匹配器,首先我们必须使用 cv.BFMatcher()

bf = cv.BFMatcher(normType=cv.NORM_L2, crossCheck=False)

创建 BFMatcher 对象。它需要两个可选的参数。第一个是 normType。它指定要使用的距离测量方法。默认情况下,它是 cv.NORM_L2 。它很适合于 SIFT 和 SURF 等( cv.NORM_L1 也可以)。对于基于二进制字符串的描述子,如 ORB,BRIEF,BRISK 等,应使用 cv.NORM_HAMMING ,它使用汉明距离作为度量。如果 ORB 使用 WTA_K == 3 或 4,则应使用 cv.NORM_HAMMING2

第二个参数是布尔变量 crossCheck,默认为 false。如果为 True,则 Matcher 仅返回具有值(i,j)的匹配,使得集合 A 中的第 i 个描述子具有集合 B 中的第 j 个描述子作为最佳匹配,反之亦然。也就是说,两组中的两个特征点应该相互匹配。它提供了一致的结果,是 D.Lowe 在 SIFT 论文中提出的比率测试的一个很好的替代方案。

一旦创建,两个重要的方法是 BFMatcher.match()BFMatcher.knnMatch()。第一个返回最佳匹配。第二种方法返回 k 个最佳匹配,其中 k 由用户指定。当我们需要做更多的工作时,它可能会有用。

BFMatcher.match()BFMatcher.knnMatch() 对比

特性 match() knnMatch()
返回数量 1个最佳匹配 k个最佳匹配
返回值类型 List[DMatch] List[List[DMatch]]
常用筛选方法 按距离阈值筛选 Lowe's ratio test
计算复杂度 较低 较高(需计算k个)
匹配质量 可能有误匹配 通过ratio test可提高准确性

就像我们使用 cv.drawKeypoints()绘制特征点一样, cv.drawMatches() (之前我们的文章有过介绍)帮助我们绘制匹配。它水平堆叠两个图像,并从第一个图像到第二个图像绘制线条,显示最佳匹配。还有 cv.drawMatchesKnn ,它绘制了所有 k 个最佳匹配。如果 k = 2,它将为每个关键点绘制两条匹配线。因此,如果我们想要有选择地绘制它,我们必须传递一个掩模。

对 ORB 描述子使用蛮力匹配

在这里,我们将看到一个关于如何匹配两个图像之间的特征的简单示例。在这种情况下,我有一个 queryImage 和一个 trainImage。我们将尝试使用特征匹配在 trainImage 中查找 queryImage。 (图片为/samples/c/box.png 和/samples/c/box_in_scene.png)

我们使用 ORB 描述符来匹配功能。所以让我们从加载图像,查找描述子等开始。

我们使用 cv.NORM_HAMMING (因为我们使用的是 ORB)创建一个 BFMatcher 对象并且启用了 crossCheck 以获得更好的结果。然后我们使用 Matcher.match()方法在两个图像中获得最佳匹配。我们按照距离的升序对它们进行排序,以便最佳匹配(距离最小)出现在前面。然后我们只画出前 10 个匹配(仅为了能见度,你可以随意增加匹配的个数)。

代码如下

python 复制代码
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt

# 读取查询图像和训练图像
img1 = cv.imread(r'D:\python_code\pic\sumoiao.webp', 0)  # queryImage
img2 = cv.imread(r'D:\python_code\pic\smt.png', 0)       # trainImage

# 初始化ORB检测器
orb = cv.ORB_create()

# 使用ORB检测关键点和描述符
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)

# 创建BFMatcher对象
bf = cv.BFMatcher(cv.NORM_HAMMING, crossCheck=True)

# 匹配描述符
matches = bf.match(des1, des2)

# 根据距离排序匹配结果
matches = sorted(matches, key=lambda x: x.distance)

# 绘制前10个匹配点
img3 = cv.drawMatches(img1, kp1, img2, kp2, matches[:10], None, flags=2)

# 显示匹配结果
plt.imshow(img3)
plt.show()
 

这里的图片可以使用一张图片然后截一部分,就有了两张图片,完整的放上面,截图放下面

效果如下

这个匹配器对象是什么?

matches = bf.match(des1,des2)的结果是 DMatch 对象的列表。此 DMatch 对象具有以下属性:

  • DMatch.distance - 描述子之间的距离。越低越好。
  • DMatch.trainIdx - 目标图像中描述子的索引
  • DMatch.queryIdx - 查询图像中描述子的索引
  • DMatch.imgIdx - 目标图像的索引。

对 SIFT 描述符进行蛮力匹配和比率测试

这一次,我们将使用 BFMatcher.knnMatch()来获得最佳匹配。在这个例子中,我们将采用 k = 2,以便我们可以在使用 D.Lowe 论文中的比率测试。

代码如下

python 复制代码
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

# 图像路径设置
query_img_path = r'D:\python_code\pic\sumoiao.webp'
train_img_path = r'D:\python_code\pic\smt.png'

# 灰度模式读取图像
img1 = cv.imread(query_img_path, 0)  
img2 = cv.imread(train_img_path, 0)

# 初始化SIFT检测器
sift = cv.SIFT_create()

# 关键点检测与描述符计算
kp1, des1 = sift.detectAndCompute(img1, None)
kp2, des2 = sift.detectAndCompute(img2, None)

# 创建暴力匹配器
bf = cv.BFMatcher(cv.NORM_L2, crossCheck=False)

# K近邻匹配
matches = bf.knnMatch(des1, des2, k=2)

# Lowe's比率测试筛选
ratio_threshold = 0.75
good_matches = []
for m,n in matches:
    if m.distance < ratio_threshold * n.distance:
        good_matches.append(m)

# 匹配结果可视化
if len(good_matches) > 10:  # 仅当有足够匹配时才绘制
    result_img = cv.drawMatches(img1, kp1, img2, kp2, 
                               good_matches[:50], 
                               None, 
                               flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
    
    plt.figure(figsize=(15,10))
    plt.imshow(result_img)
    plt.axis('off')
    plt.show()
else:
    print("未找到足够数量的可靠匹配点")
 

效果如下

还是很精准的,效果很不错

基础

那我们上面做了什么?我们使用了一个 queryImage,在其中找到了一些特征点,我们采用了另一个 trainImage,找到了该图像中的特征,最后找到它们之间特征点的最佳匹配。简而言之,我们在另一个杂乱的图像中找到了一个对象的某些部分的位置。这些信息足以在 trainImage 上准确找到对象。

为此,我们可以使用来自 calib3d 模块的函数,即 cv.findHomography()

H, mask = cv.findHomography(srcPoints, dstPoints, method=None, ransacReprojThreshold=None, maxIters=None, confidence=None)

参数详解

输入参数表格

参数名称 类型 描述 默认值 常用值
srcPoints np.ndarray 源图像中的点坐标 必需 形状: (N, 2)(N, 1, 2)
dstPoints np.ndarray 目标图像中的对应点 必需 形状与 srcPoints 相同
method int 计算方法 0 选项见下方方法表格
ransacReprojThreshold float RANSAC重投影误差阈值 - 1.0-5.0
maxIters int RANSAC最大迭代次数 2000 1000-5000
confidence float 置信度 0.995 0.95-0.999

返回值表格

返回值 类型 描述 可能值
H np.ndarray 3×3单应性矩阵 形状: (3, 3)None
mask np.ndarrayNone 内点掩码 形状: (N,),仅当使用RANSAC/LMEDS时返回

这就是cv.findHomography()的详细用法。它在计算机视觉的许多应用中都非常重要,特别是图像配准、全景拼接和增强现实等领域。

如果将两个图像中的特征点集传递给这个函数,它将找到该对象的透视变换。然后我们可以使用 cv.perspectiveTransform()

dst = cv.perspectiveTransform(src, M)

参数说明

参数 类型 描述
src np.ndarray 输入点集,形状为 (N, 1, 2)(N, 2)
M np.ndarray 3×3 透视变换矩阵
dst np.ndarray 输出点集,形状与输入相同

来查找对象。它需要至少四个正确的点来找到这种变换。

我们已经看到匹配时可能存在一些可能的错误,这可能会影响结果。为了解决这个问题,算法使用 RANSAC 或 LEAST_MEDIAN(可以由标志位决定)。因此,提供正确估计的良好匹配称为内点,剩余称为外点。 cv.findHomography() 返回一个指定了内点和外点的掩模。

代码

首先,像往常一样,让我们在图像中找到 SIFT 特征并应用比率测试来找到最佳匹配。

代码如下

python 复制代码
import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt

MIN_MATCH_COUNT = 10
img1 = cv.imread(r'D:\python_code\pic\sumoiao.webp',0)          # 查询图像
img2 = cv.imread(r'D:\python_code\pic\smt.png',0)               # 训练图像

# 初始化SIFT检测器
sift = cv.SIFT_create()

# 检测关键点并计算描述符
kp1, des1 = sift.detectAndCompute(img1,None)
kp2, des2 = sift.detectAndCompute(img2,None)

# 设置FLANN匹配器参数
FLANN_INDEX_KDTREE = 1
index_params = dict(algorithm = FLANN_INDEX_KDTREE, trees = 5)
search_params = dict(checks = 50)

# 创建FLANN匹配器
flann = cv.FlannBasedMatcher(index_params, search_params)
matches = flann.knnMatch(des1,des2,k=2)

# 应用Lowe's比率测试筛选优质匹配点
good = []
for m,n in matches:
    if m.distance < 0.7*n.distance:
        good.append(m)

# 如果找到足够多的匹配点,计算单应性矩阵
if len(good) > MIN_MATCH_COUNT:
    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)
    
    # 使用RANSAC方法计算单应性矩阵
    M, mask = cv.findHomography(src_pts, dst_pts, cv.RANSAC,5.0)
    matchesMask = mask.ravel().tolist()
    
    # 获取查询图像的尺寸
    h,w = img1.shape
    pts = np.float32([[0,0],[0,h-1],[w-1,h-1],[w-1,0]]).reshape(-1,1,2)
    
    # 对查询图像的四个角点进行透视变换
    dst = cv.perspectiveTransform(pts,M)
    
    # 在训练图像上绘制匹配区域的多边形
    img2_with_box = img2.copy()
    img2_with_box = cv.polylines(img2_with_box,[np.int32(dst)],True,255,3, cv.LINE_AA)
else:
    print(f"Not enough matches are found - {len(good)}/{MIN_MATCH_COUNT}")
    matchesMask = None
    img2_with_box = img2

# 设置绘制参数
draw_params = dict(matchColor = (0,255,0),
                   singlePointColor = None,
                   matchesMask = matchesMask,
                   flags = 2)

# 绘制匹配结果
img3 = cv.drawMatches(img1,kp1,img2_with_box,kp2,good,None,**draw_params)

# 显示结果
plt.imshow(img3, 'gray')
plt.show()
 

效果如下

这个效果更加明显,效果更好

三最后一语

其实我也很想继续写下去,但是我想你们也应该感觉过迷茫,不知道该如何使用,不知道学习这个对比赛或是项目有这个那么用,我也感受到了,所以为了尽快到达比赛水平,我将采取学用结合,我会买一块k230的板子进行研究,大家一起努力吧。

我祝福你

愿你经得起长久的离别

种种考验、古凶未卜的折磨

漫长的昏暗的路程

依照你的意愿安排生活吧

只要你觉得好就行

------帕斯捷尔纳克《日瓦戈医生》

感谢观看,定会再见,共勉!!

相关推荐
橙露5 小时前
工业视觉检测软件全解析:技术栈、选型指南与实践场景
计算机视觉
lxmyzzs5 小时前
【图像算法 - 38】工业巡检应用:基于 YOLO 与 OpenCV 的高精度管道缺陷检测系统实现
opencv·算法·yolo·管道检测
lxmyzzs5 小时前
【图像算法 - 39】环保监测应用:基于 YOLO 与 OpenCV 的高精度水面垃圾检测系统实现
opencv·算法·yolo·水下垃圾检测
点云SLAM5 小时前
Redundant 英文单词学习
人工智能·学习·英文单词学习·雅思备考·redundant·冗余的·多余的 、重复的
lxmyzzs5 小时前
【图像算法 - 40】海洋监测应用:基于 YOLO 与 OpenCV 的高精度海面目标检测系统实现
opencv·算法·yolo·海上目标检测
爱笑的眼睛115 小时前
SQLAlchemy 核心 API 深度解析:超越 ORM 的数据库工具包
java·人工智能·python·ai
知白守黑V5 小时前
OWASP 2025 LLM 应用十大安全风险深度解析
人工智能·安全·ai agent·ai智能体·ai应用·ai安全·大模型安全
zhaodiandiandian5 小时前
生成式AI重构内容创作生态:人机协同成核心竞争力
大数据·人工智能·重构
努力毕业的小土博^_^5 小时前
【AI课程领学】基于SmolVLM2与Qwen3的多模态模型拼接实践:从零构建视觉语言模型(一)
人工智能·深度学习·神经网络·机器学习·语言模型·自然语言处理