【菜狗学三维重建】Slam对极几何实战—从两张未知相机内参的图片计算出来相机Rt——20250413

目录

任务

1、读取图像

2、特征点检测与匹配

3、从匹配的对应点中选择八个点

4、求解F矩阵(没有内参信息用基础矩阵F来求Rt)

        之前有一篇关于原理方面的视觉slam三维重建的文章,现在来实战一下,将书本上的知识转化为代码实现一下“视觉里程计-对极几何-2D-2D”。


任务

        从两张未知相机内参的图片计算出来相机R,t。

1、读取图像

import cv2

#读取两张图像
a=cv2.imread("00010.jpg")
b=cv2.imread("00030.jpg")
#print (a.shape,b.shape)     #两张图片的尺寸都是(240,320,3)

2、特征点检测与匹配

        特征匹配使用的是ORB特征,实际使用的时候用cv库直接调用,特征点匹配的原理方面可以参考其他的文章:

(四十二)特征点检测-ORB - 知乎

特征点的检测与匹配(ORB,SIFT,SURFT比较)[opencv-python]_特征点匹配算法-CSDN博客

ORB特征提取、匹配及位置估计_orb特征提取筛选-CSDN博客

        以下是对于两张图象进行特征点匹配:


        (1)通过cv.ORB_create()创建一个 ORB 检测器对象orb

orb=cv2.ORB_create()

        (2)检测关键点和计算描述子:调用orb.detectAndCompute(imgCat, None),对图像imgCat进行关键点检测和描述子计算。

  • 函数输入:

                imgCat是输入图像

                None表示不对图像进行掩码操作,即检测整个图像中的关键点。

  • 返回结果:detectAndCompute方法返回两个结果:

                kpCat:检测到的关键点列表

                desCat:与关键点对应的描述子列表

a_keypoint,a_description=orb.detectAndCompute(a,None)
b_keypoint,b_description=orb.detectAndCompute(b,None)

        (3)“BFMatcher_create(cv.NORM_HAMMING, crossCheck=True)”是 OpenCV 中用于创建暴力匹配器(Brute-Force Matcher)的代码,以下是对它的详细解释:

  • 功能:创建一个暴力匹配器对象,用于匹配两幅图像的描述子
  • 参数:
    • cv.NORM_HAMMING:指定描述子之间的距离度量方式。cv.NORM_HAMMING 是一种基于汉明距离的度量方式,适用于二进制描述子(如 ORB、BRIEF 等)。汉明距离用于衡量两个二进制字符串之间的差异,即不同位数的个数。
    • crossCheck=True:启用交叉检查模式。在这种模式下,匹配器会检查描述子之间的双向匹配关系,即如果描述子 A 匹配描述子 B,同时描述子 B 也匹配描述子 A,则认为它们是有效的匹配对。这有助于提高匹配的准确性,但可能会降低匹配的速度。
bf=cv2.BFMatcher_create()

        (4)“matches = bf.match(desCat, desSmallCat)”使用暴力匹配器 bf 来匹配两个描述符集合 desCat 和 desSmallCat。

  • desCat 和 desSmallCat 是通过 ORB 算法提取的描述符
  • bf.match 方法返回一个匹配列表 matches,其中每个匹配项是一个 cv.DMatch 对象。cv.DMatch 对象包含匹配描述符的索引、距离等信息。
  • 这些匹配项可以用于后续的图像处理任务,例如图像拼接、目标检测等。
matches=bf.match(a_description,b_description)   #匹配两个描述子集合

        (5)“cv.drawMatches”:是 OpenCV 提供的一个函数,用于在图像上绘制匹配的特征点。它会将两个图像上的匹配特征点用线条连接起来,方便可视化匹配结果。

  • matches: 这是一个匹配列表,包含了第一个图像中的特征点与第二个图像中特征点的匹配关系。
  • None: 这是一个可选参数,用于指定输出图像。如果传入 None,函数会自动创建一个新的图像来绘制匹配结果。
match=cv2.drawMatches(a,a_keypoint,b,b_keypoint,matches,None)    #绘制匹配点(有两个图像、两个关键点、匹配的描述子集合)

        (6)用imshow展示出来匹配点和两张图匹配点的连线

cv2.imshow("a_img",a)
cv2.imshow("b_img",b)
cv2.imshow("matching",match)

cv2.waitKey(0)  #等待键盘上有任意按键则退出

3、从匹配的对应点中选择八个点

        打印了一下描述子、特征点来观察其数据结构,发现描述子是(337, 32)的形状,有337 个特征点,每个点的 32 维 ORB 描述子。特征点a_keypoints是”< cv2.KeyPoint 0x7f94482cfc90>,“这种东西汇集在一起的元组。

【菜狗学三维重建】Slam对极几何实战—从两张未知相机内参的图片计算出来相机Rt——20250413_第1张图片

        (1)按照匹配距离来对于匹配的一系列对应点进行排序

#按照匹配距离进行排序
matches=sorted(matches,key=lambda x:x.distance)

        sorted(iterable, key=..., reverse=...)第一个参数是可迭代对象,这里就是匹配点matches,x.distance 获取 cv2.DMatchdistance 属性(描述子之间的匹配距离)。

        (2)从特征点中提取八个点

#想要从特征点中提取八个点,以(x,y)形式保存在matched_points列表中
matched_points=[]

for a in range(min(8,len(matches))):
    match=matches[a]

    a_idx=match.queryIdx
    a_point=a_keypoint[a_idx].pt

    b_idx=match.trainIdx
    b_point=b_keypoint[b_idx].pt

    matched_points.append((a_point,b_point))
print(matched_points)

       在 cv2.BFMatcher.match() 之后,matches 是一个包含 cv2.DMatch 对象的列表,其中每个 cv2.DMatch 代表一个匹配点对

每个 cv2.DMatch 对象包含:

  • queryIdx第一张图像(A)中匹配关键点的索引

  • trainIdx第二张图像(B)中匹配关键点的索引

  • distance:描述子之间的欧几里得距离,用于衡量两个关键点的相似程度,值越小表示匹配越好

 cv2.KeyPoint 是一个包含多个属性的对象,其中包括了关键点的位置信息(即坐标)。为了提取每个关键点的 xy 坐标,可以通过 cv2.KeyPoint 对象的 pt 属性,它返回一个包含 (x, y) 坐标的元组

        输出matched_points,得到:

[((188.6400146484375, 105.12000274658203), (224.6400146484375, 120.96001434326172)), ((55.0, 54.0), (78.0, 66.0)), ((59.0, 54.0), (82.80000305175781, 66.0)), ((194.08900451660156, 84.60289764404297), (228.9254913330078, 97.04450225830078)), ((146.88002014160156, 57.02400588989258), (174.1824188232422, 66.35520935058594)), ((184.32000732421875, 113.76000213623047), (221.18402099609375, 129.60000610351562)), ((188.40000915527344, 104.4000015258789), (224.40000915527344, 120.00000762939453)), ((64.0, 55.0), (90.0, 66.0))]

        分析其数据结构,是一个列表里面有八个元组,每个元组里面有两个坐标元组,分别是两张图的对应点的坐标。


        (3)想要遍历这个列表中的每个元组,把每个元组中的第一个元组拿出来在第一张图上面画出,把每个元组中的第二个元组拿出来在第二张图上面画出。下面就是feature_1里面存了所有第一个图像上面八个特征点的坐标的列表,feature_2同理,是第二张图像的列表。

# 将第一张图、第二张图的特征点分别保存在两个列表中
feature_1=[]
feature_2=[]
for element in matched_points:
    feature_1.append(element[0])
    feature_2.append(element[1])
print(feature_1)
print(feature_2)

        对这八对点进行匹配的可视化,还是要用match,把坐标先转化为Keypoints,之后用DMatch函数表示匹配关系,再绘制匹配点和匹配连接。

#想要对八对点的映射可视化,首先把坐标转化为KeyPoint类型,使得可以match
a_keypoint_8=[cv2.KeyPoint(x,y,10) for (x,y) in feature_1]
b_keypoint_8=[cv2.KeyPoint(x,y,10) for (x,y) in feature_2]

#创建八对点的匹配对象, 使得TrainIdm和QueryIdm数量一样,特征点之间的距离为0
matches=[cv2.DMatch(i,i,0) for i in range(len(a_keypoint_8))]
match=cv2.drawMatches(a,a_keypoint_8,b,b_keypoint_8,matches,None)    #绘制匹配点(有两个图像、两个关键点、匹配的描述子集合)

cv2.imshow("a_img",a)
cv2.imshow("b_img",b)
cv2.imshow("matching",match)
cv2.waitKey(0)

        cv2.DMatch是 OpenCV 中的一个类,用于表示特征点之间的匹配关系。它包含以下属性:(queryIdx:查询图像中特征点的索引,trainIdx:目标图像中特征点的索引,imgIdx:目标图像的索引(在多图像匹配中使用),distance:特征点之间的距离(匹配度))。


4、求解F矩阵(没有内参信息用基础矩阵F来求Rt)

        把两张图像上面对应的匹配点的坐标列表转化为numpy数组,利用方法cv2.FM_8POINT八点法,用findFundamentalMat()计算基础矩阵F并输出。

# 第一张图像上的点,创建一个与 feature_1 数据相同的 NumPy 数组
points1 = np.array(feature_1)

# 8 对匹配点 (第二张图像上的点)
points2 = np.array(feature_2)

# 计算基础矩阵
F, mask = cv2.findFundamentalMat(points1, points2, method=cv2.FM_8POINT)

# 输出结果
print("Fundamental Matrix:\n", F)

        之后对于F矩阵进行奇异值分解,要用到numpy里面的np.linalg.svd()函数,将F分解为左奇异向量、奇异值、右奇异向量,可能的t就是U的第三列,R是用一个固定的旋转矩阵和U、V相乘得到的。

# 假设 F 是一个 3x3 的基础矩阵
F = np.array([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
])

# 步骤 1:SVD 分解 F
U, S, Vt = np.linalg.svd(F)

# 步骤 2:构造矩阵 W
W = np.array([
    [0, -1, 0],
    [1, 0, 0],
    [0, 0, 1]
])

# 步骤 3:生成可能的旋转矩阵 R1 和 R2
R1 = U @ W @ Vt
R2 = U @ W.T @ Vt

# 步骤 4:检查行列式,确保行列式为 1
if np.linalg.det(R1) < 0:
    R1 = -R1
if np.linalg.det(R2) < 0:
    R2 = -R2

# 步骤 5:提取平移向量 t
t = U[:, 2]

# 步骤 6:组合旋转矩阵和平移向量
Rt1 = np.hstack((R1, t.reshape(-1, 1)))  # 使用 R1
Rt2 = np.hstack((R2, t.reshape(-1, 1)))  # 使用 R2

print("Rt1 (使用 R1):")
print(Rt1)

print("\nRt2 (使用 R2):")
print(Rt2)

——不拖延的小狗照亮每一天

2025.04.13

你可能感兴趣的:(数码相机,计算机视觉,深度学习,笔记,opencv,人工智能)