[Computer Vision]实验五:SFM

目录

一、实验内容

二、实验过程

[2.1 使用Matplotlib绘制三维数据、绘制外极点和极线、三角剖分](#2.1 使用Matplotlib绘制三维数据、绘制外极点和极线、三角剖分)

[2.2 多视图三维重建](#2.2 多视图三维重建)

[2.3 对拍摄物(鼠标)的多视图三维重建](#2.3 对拍摄物(鼠标)的多视图三维重建)

三、实验小结


一、实验 内容

  1. 了解对极几何、基础矩阵、本质矩阵等相关知识点,对某个物体从不同角度拍摄多张照片,利用SFM构建出三维模型,并可视化。
  2. 输出基础矩阵、本质矩阵、投影矩阵(位姿)、部分三维空间点的坐标。

二、实验过程

2.1 使用Matplotlib绘制三维数据、绘制外极点和极线、三角剖分

(1)sfm.py文件:这段代码实现了一系列计算机视觉中常用的算法,主要用于处理立体视觉和三维重建中的基本矩阵(Fundamental Matrix)、本质矩阵(Essential Matrix)、三角测量(Triangulation)以及随机抽样一致性(RANSAC)算法。

python 复制代码
import numpy as np
from matplotlib.pyplot import cla, plot
from numpy import diag, dot, linspace, mean, sqrt, std, vstack, zeros
from scipy import linalg
from torch import det, svd
import torch
#计算基本矩阵(Fundamental Matrix)
def compute_fundamental(x1,x2):
    n=x1.shape[1]
    if x2.shape[1]!=n:
        raise ValueError("Number of points don't match.")
    A=zeros((n,9))
    for i in range(n):
        A[i]=[x1[0,i]*x2[0,i],x1[0,i]*x2[1,i],x1[0,i]*x2[2,i],
              x1[1,i]*x2[0,i],x1[1,i]*x2[1,i],x1[1,i]*x2[2,i],
              x1[2,i]*x2[0,i],x1[2,i]*x2[1,i],x1[2,i]*x2[2,i]]
        U,S,V=linalg.svd(A)
        F=V[-1].reshape(3,3)
        U,S,V=linalg.svd(F)
        S[2]=0
        F=dot(U,dot(diag(S),V))
        return F
    
#计算极点
def compute_episole(F):
    U,S,V=linalg.svd(F)
    e=V[-1]
    return e/e[2]

#绘制极线
def plot_epipolar_line(im,F,x,epipole=None,show_epipole=True):
    m,n=im.shape[:2]
    line=dot(F,x)
    t=linspace(0,n,100)
    lt=np.array([(line[2]+line[0]*tt)/(-line[1])for tt in t])
    ndx=(lt>=0)&(lt<m)
    plot(t[ndx],lt[ndx],linewidth=2)

    if show_epipole:
        if epipole is None:
            epipole=compute_episole(F)
        plot(epipole[0]/epipole[2],epipole[1]/epipole[2],'r*')

#三角测量
def triangle_point(x1,x2,P1,P2):
    M=zeros((6,6))
    M[:3,:4]=P1
    M[3:,:4]=P2
    M[:3,4]=-x1
    M[3:,5]=-x2

    U,S,V=linalg.svd(M)
    X=V[-1,:4]
    return X/X[3]

def triangulate(x1,x2,P1,P2):
    n=x1.shape[1]
    if x2.shape[1]!=n:
        raise ValueError("Number of points don't match.")
    X=[triangle_point(x1[:,i],x2[:,i],P1,P2)for i in range(n)]
    return np.array(X).T

#从点对应关系计算投影矩阵
def compute_P(x,X):
    n=x.shape[1]
    if X.shape[1]!=n:
        raise ValueError("Number of points don't match.")
    M=zeros((3*n,12+n))
    for i in range(n):
        M[3*i,0:4]=X[:,i]
        M[3*i+1,4:8]=X[:,i]
        M[3*i+2,8:12]=X[:,i]
        M[3*i:3*i+3,i+12]=-x[:,i]
    U,S,V=linalg.svd(M)
    return V[-1,:12].reshape((3,4))

#从基本矩阵计算投影矩阵
def compute_P_from_fundamental(F):
    e=compute_episole(F.T)
    Te=skew(e)
    return vstack((dot(Te,F.T).T,e)).T

def skew(a):
    return np.array([[0,-a[2],a[1]],[a[2],0,-a[0]],[-a[1],a[0],0]])

#从本质矩阵计算投影矩阵
def compute_P_from_essential(E):
    U,S,V=linalg.svd(E)
    if det(torch.tensor(dot(U, V)))<0:
        V=-V
    E=dot(U,dot(diag([1,1,0]),V))

    Z=skew([0,0,-1])
    W=np.array([[0,-1,0],[1,0,0],[0,0,1]])

    P2=[vstack((dot(U,dot(W,V)).T,U[:,2])).T,
        vstack((dot(U,dot(W,V)).T,-U[:,2])).T,
        vstack((dot(U,dot(W.T,V)).T,U[:,2])).T,
        vstack((dot(U,dot(W.T,V)).T,-U[:,2])).T]
    return P2

#归一化八点法计算基本矩阵
def compute_fundamental_normalized(x1,x2):
        n=x1.shape[1]
        if x2.shape[1]!=n:
            raise ValueError("Number of points don't match.")
        
        x1=x1/x1[2]
        mean_1=mean(x1[:2],axis=1)
        S1=sqrt(2)/std(x1[:2])
        T1=np.array([[S1,0,-S1*mean_1[0]],[0,S1,-S1*mean_1[1]],[0,0,1]])
        x1=dot(T1,x1)
        
        x2=x2/x2[2]
        mean_2=mean(x2[:2],axis=1)
        S2=sqrt(2)/std(x2[:2])
        T2=np.array([[S2,0,-S2*mean_2[0]],[0,S2,-S2*mean_2[1]],[0,0,1]])
        x2=dot(T2,x2)
        F=compute_fundamental(x1,x2)
        F=dot(T1.T,dot(F,T2))
        return F/F[2,2]

#RANSAC模型 用于拟合基本矩阵
class RansacModel(object):
    def __init__(self,debug=False):
        self.debug=debug

    def fit(self,data):
        data=data.T
        x1=data[:3,:8]
        x2=data[3:,:8]
        F=compute_fundamental_normalized(x1,x2)
        return F
    
    def get_error(self,data,F):
        data=data.T
        x1=data[:3]
        x2=data[3:]
        Fx1=dot(F,x1)
        Fx2=dot(F,x2)
        denom=Fx1[0]**2+Fx1[1]**2+Fx2[0]**2+Fx2[1]**2
        err=(diag(dot(x1.T,dot(F,x2))))**2/denom
        return err
    
def F_from_ransac(x1,x2,model,maxiter=5000,match_theshold=1e-6):
        import ransac
        data=vstack((x1,x2))
        F,ransac_data=ransac.ransac(data.T,model,8,maxiter,match_theshold,10,
                                    return_all=True)
        return F,ransac_data['inliers']

(2)load_vggdata.py文件

python 复制代码
from tkinter import NO
from PIL import Image
from matplotlib import figure, pyplot
from matplotlib.pyplot import imshow,plot,axis, show
from numpy import genfromtxt, loadtxt, ones,  vstack, where
import numpy as np
import camera
from mpl_toolkits.mplot3d import axes3d
import sfm

image1 = Image.open("D:/Computer vision/3d/images/001.jpg")

im1 = np.array(image1)
image2 = Image.open("D:/Computer vision/3d/images/002.jpg")

im2 = np.array(image2)
points2D=[loadtxt('D:/Computer vision/3d/2D/00'+str(i+1)+'.corners').T for i in range(3)]
points3D=loadtxt('D:/Computer vision/3d/3D/p3d').T
corr = genfromtxt('D:/Computer vision/3d/2D/nview-corners', dtype='int', invalid_raise=False, usemask=True)
P = [camera.Camera(loadtxt(f'D:/Computer vision/3d/2D/00{i+1}.P')) for i in range(3)]

def process1():
    X=vstack((points3D,ones(points3D.shape[1])))
    x=P[0].project(X)
    pyplot.figure()
    imshow(im1)
    plot(points2D[0][0],points2D[0][1],'*')
    axis('off')

    pyplot.figure()
    imshow(im1)
    plot(x[0],x[1],'r.')
    axis('off')
    show()

def process2():
    fig=pyplot.figure()
    ax=fig.gca(projection="3d")
    ax.plot(points3D[0],points3D[1],points3D[2],'k.')
    show()

def process3():
    ndx=(corr[:,0]>=0)&(corr[:,1]>=0)
    x1=points2D[0][:,corr[ndx,0]]
    x1=vstack((x1,ones(x1.shape[1])))
    x2=points2D[1][:,corr[ndx,1]]
    x2=vstack((x2,ones(x2.shape[1])))

    F=sfm.compute_fundamental(x1,x2)

    e=sfm.compute_episole(F)

    pyplot.figure()
    imshow(im1)
    for i in range(5):
        sfm.plot_epipolar_line(im1,F,x2[:,i],e,False)
    axis('off')
    pyplot.figure()
    imshow(im2)

    for i in range(5):
        plot(x2[0,i],x2[1,i],'o')
    axis('off')
    show()

def process4():

    ndx=(corr[:,0]>=0)&(corr[:,1]>=0)

    x1=points2D[0][:,corr[ndx,0]]
    x1=vstack((x1,ones(x1.shape[1])))
    x2=points2D[1][:,corr[ndx,1]]
    x2=vstack((x2,ones(x2.shape[1])))
    Xtrue=points3D[:,ndx]
    Xtrue=vstack((Xtrue,ones(Xtrue.shape[1])))

    Xest=sfm.triangulate(x1,x2,P[0].P,P[1].P)
    print(Xest[:,:3])
    print(Xtrue[:,:3])

    fig1=pyplot.figure()
    ax1=fig1.gca(projection='3d')
    ax1.plot(Xest[0],Xest[1],Xest[2],'ko')
    ax1.plot(Xtrue[0],Xtrue[1],Xtrue[2],'r.')
    show()

def process5():
    corr1=corr[:,0]
    ndx3D=where(corr1>=0)[0]
    ndx2D=corr1[ndx3D]

    x=points2D[0][:,ndx2D]
    x=vstack((x,ones(x.shape[1])))
    X=points3D[:,ndx3D]
    X=vstack((X,ones(X.shape[1])))

    Pest=camera.Camera(sfm.compute_P(x,X))

    print(Pest.P/Pest.P[2,3])
    print(P[0].P/P[0].P[2,3])
    xest=Pest.project(X)

    pyplot.figure()
    imshow(im1)
    plot(x[0],x[1],'bo')
    plot(xest[0],xest[1],'r.')
    axis('off')
    show()

if __name__ == '__main__':
    process1()
    process2()
    process3()
    process4()
    process5()

(3)结果展示

a.如图1所示,显示视图与图像点、投影的三维点
图1

b. 如图2所示,使用Matplottib工具绘制数据集中的三维点
图2

c. 如图3所示,绘制图像的外极线和外极点
图3

d.对图像点通过三角剖分恢复这些点的三维位置,首先利用前两个视图的信息对图像点进行三角剖分,输出前三个图像点的齐次坐标,最后绘制出最接近的三维图像点,输出三维点的信息如图4所示,这些三维点的可视化如图5所示。
图4
图5

e. 由三维点计算照相机矩阵,如图6所示,以归一化的格式打印估计出的照相机矩阵,使用估计出的照相机矩阵投影这些三维点,绘制投影后的结果如图7所示。
图6
图7

2.2 多视图 三维重建

(1)module_test.py文件:这段代码实现了一个完整的三维重建流程,包括特征提取、匹配、本质矩阵计算、投影矩阵计算、三角测量以及三维点的可视化。

python 复制代码
from PIL import Image
import cv2
from pylab import *
import numpy as np
import camera
import homography
import sfm
import os
#环境设置与相机内参
os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'

K=array([[ 2142.86273,  146.047950,  1409.66408],
         [-313.655714,  2372.61671,  646.528794],
         [-0.206318753, -0.0289928473,  0.978055206]])
K1=array([[ 1887.51493,  152.070907,  1736.23309],
         [-438.905450,  2363.03101,  607.565547],
         [-0.360084369, -0.0406467998,  0.932033843]])
K2=array([[ 2260.20652  ,42.1635948 , 1220.66876 ],
          [-120.310166 , 2397.29053 , 619.849171 ],
         [-0.123407237 ,-0.000706258860 , 0.992355861  ]])

#图像加载与特征提取
im1 = array(Image.open('D:\\Computer vision\\3d\\Alcatraz_courtyard\\San_Francisco_2313.jpg'))
im2 = array(Image.open('D:\\Computer vision\\3d\\Alcatraz_courtyard\\San_Francisco_2314.jpg'))

im3 = array(Image.open('D:\\Computer vision\\3d\\Alcatraz_courtyard\\San_Francisco_2364.jpg'))

orb = cv2.ORB_create()
kp1, des1 = orb.detectAndCompute(cv2.cvtColor(im1, cv2.COLOR_RGB2GRAY), None)
kp2, des2 = orb.detectAndCompute(cv2.cvtColor(im2, cv2.COLOR_RGB2GRAY), None)
kp3, des3 = orb.detectAndCompute(cv2.cvtColor(im3, cv2.COLOR_RGB2GRAY), None)

l1 = np.array([kp.pt for kp in kp1]).T
l2 = np.array([kp.pt for kp in kp2]).T
l3 = np.array([kp.pt for kp in kp3]).T

print("l1 shape:",l1.shape)
print("l2 shape:",l2.shape)
print("l3 shape:", l3.shape)

#特征匹配
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)#暴力匹配(BFMatcher)对特征描述子进行匹配
matches = bf.match(des1, des2)
matches = sorted(matches, key=lambda x: x.distance)
matches3 = bf.match(des1, des3)
matches3 = sorted(matches3, key=lambda x: x.distance)
ndx = np.array([m.queryIdx for m in matches])
ndx2 = np.array([m.trainIdx for m in matches])
ndx3 = np.array([m.queryIdx for m in matches3])
ndx3_train = np.array([m.trainIdx for m in matches3])
a=l1[:, ndx]
b=l2[:, ndx2]
a3 = l1[:, ndx3]
b3 = l3[:, ndx3_train]

print("a:",a.shape)
print("b:",b.shape)
print("a3:", a3.shape)
print("b3:", b3.shape)

#使用 homography.make_homog 将像素坐标转换为齐次坐标。
x1 = homography.make_homog(l1[:, ndx])
x2 = homography.make_homog(l2[:, ndx2])

print("Shape of x1 before normalization:", x1.shape)
print("Shape of x2 before normalization:", x2.shape)
inv_K = np.linalg.inv(K)
inv_K1 = np.linalg.inv(K1)
inv_K2 = np.linalg.inv(K2)
#计算相机内参矩阵的逆矩阵,用于将像素坐标转换为归一化图像平面坐标。
x1_new = homography.make_homog(l1[:, ndx3])
x3_new = homography.make_homog(l3[:, ndx3_train])
print("Shape of x1_new before normalization:", x1_new.shape)
print("Shape of x3_new before normalization:", x3_new.shape)
x1n = dot(inv_K, x1)
x2n = dot(inv_K1, x2)
x1n_new = dot(inv_K, x1_new)
x3n_new = dot(inv_K2, x3_new)

model = sfm.RansacModel()
E, inliers = sfm.F_from_ransac(x1n, x2n, model)
E_new, inliers_new = sfm.F_from_ransac(x1n_new, x3n_new, model)
print("本质矩阵E:",E)
P1 = array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0]])
P2 = sfm.compute_P_from_essential(E)
P3 = sfm.compute_P_from_essential(E_new)

ind = 0
maxres = 0
for i in range(4):

#使用三角测量方法从匹配的点对中恢复三维点坐标。
    X = sfm.triangulate(x1n[:, inliers], x2n[:, inliers], P1, P2[i])
    d1 = dot(P1, X)[2]
    d2 = dot(P2[i], X)[2]
    X_new = sfm.triangulate(x1n_new[:, inliers_new], x3n_new[:, inliers_new], P1, P3[i])
    d1_new = dot(P1, X_new)[2]
    d3_new = dot(P3[i], X_new)[2]

    if sum(d1 > 0) + sum(d2 > 0) > maxres:
        maxres = sum(d1 > 0) + sum(d2 > 0)
        ind = i
        infront = (d1 > 0) & (d2 > 0)
        maxres_new = sum(d1_new > 0) + sum(d3_new > 0)
        ind_new = i
        infront_new = (d1_new > 0) & (d3_new > 0)

    X = sfm.triangulate(x1n[:, inliers], x2n[:, inliers], P1, P2[ind])
    X = X[:, infront]
    X_new = sfm.triangulate(x1n_new[:, inliers_new], x3n_new[:, inliers_new], P1, P3[ind_new])
    X_new = X_new[:, infront_new]

fig = figure()
ax = fig.gca(projection='3d')
ax.plot(-X[0], X[1], X[2], 'k.')
ax.plot(-X_new[0], X_new[1], X_new[2], 'r.')
axis('off')

cam1 = camera.Camera(P1)
cam2 = camera.Camera(P2[ind])
cam3 = camera.Camera(P3[ind_new])
x1p = cam1.project(X)
x2p = cam2.project(X)
x3p = cam3.project(X_new)

print("相机投影矩阵 P1:\n", P1)
print("相机投影矩阵 P2:\n", P2[ind])
print("相机投影矩阵 P3:\n", P3[ind_new])
print("部分三维点坐标 X:\n", X[:4])
print("部分三维点坐标 X_new:\n", X_new[:4])

x1p = dot(K, x1p)
x2p = dot(K1, x2p)
x3p = dot(K2, x3p)
figure()
imshow(im1)
gray()
plot(x1p[0], x1p[1], 'o')
plot(l1[0], l1[1], 'r.')
axis('off')
figure()
imshow(im2)
gray()
plot(x2p[0], x2p[1], 'o')
plot(l2[0], l2[1], 'r.')
axis('off')
figure()
imshow(im3)
gray()
plot(x3p[0], x3p[1], 'o')
plot(l3[0], l3[1], 'r.')
axis('off')
show()

(2)实验结果

a.如图8所示,打印本质矩阵E
图8

b.如图9所示,打印相机投影矩阵,实验中选取了三张不同视图的图片
图9

c.如图10所示,打印部分三维坐标
图10

d.实现可视化,如图11、12所示,在原图像上绘制了投影的二维点,使用 Matplotlib 绘制三维点云,但是三维重建的效果较差。
图11
图12

2. 3 对拍摄物(鼠标)的多视图 三维重建

(1)SFM.py文件

python 复制代码
import cv2
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

images = ["D:\\Computer vision\\1118\\picture1.jpg", 
          "D:\\Computer vision\\1118\\picture2.jpg",
          "D:\\Computer vision\\1118\\picture6.jpg", 
          "D:\\Computer vision\\1118\\picture5.jpg",
          "D:\\Computer vision\\1118\\picture10.jpg",
          "D:\\Computer vision\\1118\\picture8.jpg",
          "D:\\Computer vision\\1118\\picture7.jpg",
          "D:\\Computer vision\\1118\\picture9.jpg"]
imgs = [cv2.imread(img) for img in images]
gray_imgs = [cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) for img in imgs]

orb = cv2.ORB_create()

keypoints_and_descriptors = [orb.detectAndCompute(img, None) for img in gray_imgs]
keypoints = [kp_desc[0] for kp_desc in keypoints_and_descriptors]
descriptors = [kp_desc[1] for kp_desc in keypoints_and_descriptors]

bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(descriptors[0], descriptors[1])

good_matches = sorted(matches, key=lambda x: x.distance)[:50]

pts1 = np.float32([keypoints[0][m.queryIdx].pt for m in good_matches])
pts2 = np.float32([keypoints[1][m.trainIdx].pt for m in good_matches])

img_matches = cv2.drawMatches(imgs[0], keypoints[0], imgs[1], keypoints[1], good_matches, None)
cv2.imshow('Matches', img_matches)
cv2.waitKey(0)
cv2.destroyAllWindows()

K = np.array([[2394, 0, 932], [0, 2398, 628], [0, 0, 1]])

pts1_normalized = cv2.undistortPoints(np.expand_dims(pts1, axis=1), K, None)
pts2_normalized = cv2.undistortPoints(np.expand_dims(pts2, axis=1), K, None)

F, mask = cv2.findFundamentalMat(pts1_normalized, pts2_normalized, cv2.FM_8POINT)
print("基础矩阵F:",F)

E = K.T @ F @ K
print("本质矩阵E:",E)

_, R, t, mask = cv2.recoverPose(E, pts1_normalized, pts2_normalized)

points4D = cv2.triangulatePoints(np.hstack((np.eye(3), np.zeros((3, 1)))), np.hstack((R, t)), pts1_normalized.T, pts2_normalized.T).reshape(-1, 4)
points3D = points4D[:, :3] / points4D[:, 3:]

print("3D Points:\n", points3D)

valid_points = points3D[~np.any(np.isinf(points3D), axis=1) & ~np.any(np.isnan(points3D), axis=1)]

print("有效点数:", valid_points.shape[0])
print("最小值:", valid_points.min(axis=0))
print("最大值:", valid_points.max(axis=0))

ax_lim = (valid_points.max(axis=0) - valid_points.min(axis=0)) * 0.1
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(valid_points[:, 0], valid_points[:, 1], valid_points[:, 2])
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
ax.set_xlim(valid_points[:, 0].min() - ax_lim[0], valid_points[:, 0].max() + ax_lim[0])
ax.set_ylim(valid_points[:, 1].min() - ax_lim[1], valid_points[:, 1].max() + ax_lim[1])
ax.set_zlim(valid_points[:, 2].min() - ax_lim[2], valid_points[:, 2].max() + ax_lim[2])
plt.show()

(2)实验结果

a.如图13,打印基础矩阵
图13

b.如图14,打印本质矩阵
图14

c.如图15,输出三维点坐标
图15

d.如图16、17,实现三维坐标的可视化
图16
图17

三、实验小结

本次实验通过SFM实现三维重建,但是从实验结果中可以看到,三维重建后的可视化效果较差,可能涉及到以下原因:1.输入图像样本数太少,导致模型的效果较差;2.标定矩阵K值设定不准确导致模型训练效果较差;3.在对鼠标进行三维重建时,有效点有47个,但是将三维点可视化时却看到三维点远少于47个,可能是因为数据的数值范围差异较大导致。

相关推荐
wu_jing_sheng06 分钟前
哨兵卫星数据下载工具:基于CDSE的PySide6桌面应用开发实践
人工智能
小唔w7 分钟前
2026年AI培训行业观察:主流学习渠道一览
人工智能·学习
美狐美颜sdk10 分钟前
直播APP开发技术架构解析:从音视频流到第三方美颜SDK接入的完整方案
大数据·人工智能·音视频·美颜sdk·美颜api
wabs66611 分钟前
关于图论【A*算法 | 卡码网127.骑士的攻击的思考】
数据结构·算法·图论·卡码网·广搜的改进版
CIO_Alliance18 分钟前
AI认知系列(3)| 数据、算法、算力、场景四要素协同
人工智能·算法·ipaas·系统集成·企业cio联盟·企业级ai化转型
技术传感器26 分钟前
让 Hermes 自动完成一个软件需求:从 Issue 到 PR 的 AI 开发流水线
人工智能·架构·aigc·需求分析·issue
啥都想学点的研究生27 分钟前
一篇文章讲清楚:机器学习的相关概念
人工智能·机器学习
Capricorn198833 分钟前
知芽研究问题看板无法推进?从孵化区到笔记的机制排查指南
大数据·论文阅读·人工智能·笔记·学习方法·论文笔记
Wang's Blog1 小时前
AI Agent白手起家76:使用 CrewAI 搭建多智能体营销策略生成器
人工智能
梦想的旅途21 小时前
企微 API 二次开发:利用 AI Agent 实现自动化运维
运维·人工智能·企业微信