:
findEssentialMat
计算本质矩阵,recoverPose
恢复相机姿态,最后用triangulatePoints
重建零部件的三维点云。stereoCalibrate
和stereoRectify
对双目相机或多目相机进行校准和校正,获取视差图,再用reprojectImageTo3D
将视差图转换为三维点云,构建高精度三维模型。solvePnP
计算物体的三维位置,结合激光雷达数据进行融合,构建精确的三维地图。这是一个使用双目相机进行室内场景三维重建的完整流程,包括相机标定、立体校正、视差计算和点云生成:
import cv2
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 1. 相机标定(假设已经完成,直接加载标定结果)
def load_calibration_results():
# 加载左右相机内参和畸变系数
left_camera_matrix = np.array([
[832.73, 0, 318.22],
[0, 831.99, 242.15],
[0, 0, 1]
])
left_dist_coeffs = np.array([-0.365, 0.132, 0.001, 0.002, -0.027])
right_camera_matrix = np.array([
[830.47, 0, 321.54],
[0, 829.87, 245.11],
[0, 0, 1]
])
right_dist_coeffs = np.array([-0.371, 0.139, 0.003, 0.001, -0.029])
# 双目相机关系参数
R = np.array([
[0.9998, -0.0153, 0.0104],
[0.0153, 0.9999, -0.0006],
[-0.0104, 0.0007, 0.9999]
])
T = np.array([-119.97, 0.35, 1.22]) # 基线距离(毫米)
return left_camera_matrix, left_dist_coeffs, right_camera_matrix, right_dist_coeffs, R, T
# 2. 双目相机立体校正
def stereo_rectify(left_img, right_img):
# 加载标定结果
K1, D1, K2, D2, R, T = load_calibration_results()
# 图像尺寸
h, w = left_img.shape[:2]
# 计算校正变换
R1, R2, P1, P2, Q, validPixROI1, validPixROI2 = cv2.stereoRectify(
K1, D1, K2, D2, (w, h), R, T, alpha=0)
# 生成校正映射
map1x, map1y = cv2.initUndistortRectifyMap(K1, D1, R1, P1, (w, h), cv2.CV_32FC1)
map2x, map2y = cv2.initUndistortRectifyMap(K2, D2, R2, P2, (w, h), cv2.CV_32FC1)
# 应用校正
rectified_left = cv2.remap(left_img, map1x, map1y, cv2.INTER_LINEAR)
rectified_right = cv2.remap(right_img, map2x, map2y, cv2.INTER_LINEAR)
return rectified_left, rectified_right, Q
# 3. 计算视差图
def compute_disparity(left_img, right_img):
# 使用StereoSGBM算法(更精确但速度较慢)
window_size = 3
min_disp = 0
num_disp = 112 - min_disp
stereo = cv2.StereoSGBM_create(
minDisparity=min_disp,
numDisparities=num_disp,
blockSize=5,
P1=8 * 3 * window_size ** 2,
P2=32 * 3 * window_size ** 2,
disp12MaxDiff=1,
uniquenessRatio=15,
speckleWindowSize=0,
speckleRange=2,
preFilterCap=63,
mode=cv2.STEREO_SGBM_MODE_SGBM_3WAY
)
# 计算视差
disparity = stereo.compute(left_img, right_img).astype(np.float32) / 16.0
# 归一化视差图以便显示
disparity_normalized = cv2.normalize(disparity, None, 0, 255, cv2.NORM_MINMAX, dtype=cv2.CV_8U)
return disparity, disparity_normalized
# 4. 从视差图生成点云
def generate_point_cloud(disparity, Q, left_img=None):
# 从视差图和校正矩阵计算3D点
points_3d = cv2.reprojectImageTo3D(disparity, Q)
# 如果提供了左图像,则使用颜色信息
if left_img is not None:
colors = cv2.cvtColor(left_img, cv2.COLOR_BGR2RGB)
mask = disparity > disparity.min()
out_points = points_3d[mask]
out_colors = colors[mask]
else:
mask = disparity > disparity.min()
out_points = points_3d[mask]
out_colors = None
return out_points, out_colors
# 5. 可视化点云(使用matplotlib)
def visualize_point_cloud(points, colors=None):
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
# 设置坐标轴范围
max_range = np.array([
points[:, 0].max() - points[:, 0].min(),
points[:, 1].max() - points[:, 1].min(),
points[:, 2].max() - points[:, 2].min()
]).max() / 2.0
mid_x = (points[:, 0].max() + points[:, 0].min()) * 0.5
mid_y = (points[:, 1].max() + points[:, 1].min()) * 0.5
mid_z = (points[:, 2].max() + points[:, 2].min()) * 0.5
ax.set_xlim(mid_x - max_range, mid_x + max_range)
ax.set_ylim(mid_y - max_range, mid_y + max_range)
ax.set_zlim(mid_z - max_range, mid_z + max_range)
# 绘制点云
if colors is not None:
ax.scatter(points[:, 0], points[:, 1], points[:, 2], c=colors/255.0, s=2)
else:
ax.scatter(points[:, 0], points[:, 1], points[:, 2], c='b', s=2)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.title('3D Point Cloud')
plt.show()
# 主函数
def main():
# 读取双目图像
left_img = cv2.imread('left_image.jpg')
right_img = cv2.imread('right_image.jpg')
# 确保图像为灰度图
if len(left_img.shape) == 3:
left_gray = cv2.cvtColor(left_img, cv2.COLOR_BGR2GRAY)
else:
left_gray = left_img
if len(right_img.shape) == 3:
right_gray = cv2.cvtColor(right_img, cv2.COLOR_BGR2GRAY)
else:
right_gray = right_img
# 立体校正
rectified_left, rectified_right, Q = stereo_rectify(left_gray, right_gray)
# 计算视差
disparity, disparity_normalized = compute_disparity(rectified_left, rectified_right)
# 生成点云
points, colors = generate_point_cloud(disparity, Q, left_img)
# 可视化结果
cv2.imshow('Left Image', left_img)
cv2.imshow('Right Image', right_img)
cv2.imshow('Rectified Left', rectified_left)
cv2.imshow('Rectified Right', rectified_right)
cv2.imshow('Disparity Map', disparity_normalized)
cv2.waitKey(0)
cv2.destroyAllWindows()
# 显示点云
visualize_point_cloud(points, colors)
if __name__ == "__main__":
main()
下面是一个简化的单目SfM流程,通过多幅图像重建3D场景:
import cv2
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.spatial.transform import Rotation
# 1. 特征提取与匹配
def feature_detection_and_matching(img1, img2):
# 使用ORB特征检测器
orb = cv2.ORB_create()
# 检测关键点和描述符
kp1, des1 = orb.detectAndCompute(img1, None)
kp2, des2 = orb.detectAndCompute(img2, None)
# 使用BFMatcher匹配描述符
bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
matches = bf.match(des1, des2)
# 按距离排序
matches = sorted(matches, by=lambda x: x.distance)
# 获取匹配点
points1 = np.float32([kp1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2)
points2 = np.float32([kp2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2)
return points1, points2, matches, kp1, kp2
# 2. 计算本质矩阵和相机位姿
def estimate_pose(points1, points2, camera_matrix):
# 计算本质矩阵
E, mask = cv2.findEssentialMat(points1, points2, camera_matrix, method=cv2.RANSAC, prob=0.999, threshold=1.0)
# 从本质矩阵恢复旋转和平移
points, R, t, mask = cv2.recoverPose(E, points1, points2, camera_matrix)
return E, R, t, mask
# 3. 三角测量恢复3D点
def triangulate_points(points1, points2, R1, t1, R2, t2, camera_matrix):
# 构建投影矩阵
P1 = np.dot(camera_matrix, np.hstack((R1, t1)))
P2 = np.dot(camera_matrix, np.hstack((R2, t2)))
# 三角测量
points_4d = cv2.triangulatePoints(P1, P2, points1.T, points2.T)
points_3d = points_4d[:3, :] / points_4d[3, :]
return points_3d.T
# 4. 主重建流程
def main():
# 相机内参矩阵(示例值,需根据实际相机标定)
camera_matrix = np.array([
[800, 0, 320],
[0, 800, 240],
[0, 0, 1]
])
# 读取图像序列
img1 = cv2.imread('image1.jpg')
img2 = cv2.imread('image2.jpg')
img3 = cv2.imread('image3.jpg')
# 转为灰度图
gray1 = cv2.cvtColor(img1, cv2.COLOR_BGR2GRAY)
gray2 = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
gray3 = cv2.cvtColor(img3, cv2.COLOR_BGR2GRAY)
# 第一对图像的特征匹配和位姿估计
points1_1, points1_2, matches1, kp1_1, kp1_2 = feature_detection_and_matching(gray1, gray2)
E1, R1, t1, mask1 = estimate_pose(points1_1, points1_2, camera_matrix)
# 第二对图像的特征匹配和位姿估计
points2_1, points2_2, matches2, kp2_1, kp2_2 = feature_detection_and_matching(gray2, gray3)
E2, R2, t2, mask2 = estimate_pose(points2_1, points2_2, camera_matrix)
# 初始相机位姿(第一幅图像为世界坐标系原点)
R_cam1 = np.eye(3)
t_cam1 = np.zeros((3, 1))
# 第二幅图像的相机位姿
R_cam2 = R1
t_cam2 = t1
# 第三幅图像的相机位姿(相对于第二幅图像)
R_cam3 = np.dot(R2, R_cam2)
t_cam3 = np.dot(R2, t_cam2) + t2
# 三角测量第一对图像的3D点
points_3d_1 = triangulate_points(
points1_1[mask1.ravel() > 0].reshape(-1, 2),
points1_2[mask1.ravel() > 0].reshape(-1, 2),
R_cam1, t_cam1, R_cam2, t_cam2, camera_matrix
)
# 三角测量第二对图像的3D点(只考虑与第一对图像的共同特征)
# 实际应用中需要进行特征跟踪和 Bundle Adjustment
# 可视化点云和相机位姿
fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')
# 绘制3D点
ax.scatter(points_3d_1[:, 0], points_3d_1[:, 1], points_3d_1[:, 2], c='b', s=2)
# 绘制相机位置
ax.scatter(0, 0, 0, c='r', s=100, marker='^') # 相机1
# 绘制相机2的位置和朝向
cam2_pos = -np.dot(R_cam2.T, t_cam2).flatten()
ax.scatter(cam2_pos[0], cam2_pos[1], cam2_pos[2], c='g', s=100, marker='^')
# 绘制相机3的位置和朝向
cam3_pos = -np.dot(R_cam3.T, t_cam3).flatten()
ax.scatter(cam3_pos[0], cam3_pos[1], cam3_pos[2], c='m', s=100, marker='^')
# 设置坐标轴范围
max_range = np.array([
points_3d_1[:, 0].max() - points_3d_1[:, 0].min(),
points_3d_1[:, 1].max() - points_3d_1[:, 1].min(),
points_3d_1[:, 2].max() - points_3d_1[:, 2].min()
]).max() / 2.0
mid_x = (points_3d_1[:, 0].max() + points_3d_1[:, 0].min()) * 0.5
mid_y = (points_3d_1[:, 1].max() + points_3d_1[:, 1].min()) * 0.5
mid_z = (points_3d_1[:, 2].max() + points_3d_1[:, 2].min()) * 0.5
ax.set_xlim(mid_x - max_range, mid_x + max_range)
ax.set_ylim(mid_y - max_range, mid_y + max_range)
ax.set_zlim(mid_z - max_range, mid_z + max_range)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.title('3D Reconstruction with SfM')
plt.show()
if __name__ == "__main__":
main()
下面是一个使用ArUco标记进行物体位姿估计的示例,可用于AR增强现实应用:
import cv2
import numpy as np
# 1. 加载相机标定参数(假设已经完成标定)
def load_camera_params():
# 相机内参矩阵
camera_matrix = np.array([
[832.73, 0, 318.22],
[0, 831.99, 242.15],
[0, 0, 1]
])
# 畸变系数
dist_coeffs = np.array([-0.365, 0.132, 0.001, 0.002, -0.027])
return camera_matrix, dist_coeffs
# 2. 检测ArUco标记并估计位姿
def detect_aruco_and_estimate_pose(image):
# 加载相机参数
camera_matrix, dist_coeffs = load_camera_params()
# 创建ArUco字典和参数
aruco_dict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_6X6_250)
aruco_params = cv2.aruco.DetectorParameters_create()
# 检测ArUco标记
corners, ids, rejected = cv2.aruco.detectMarkers(image, aruco_dict, parameters=aruco_params)
# 如果检测到标记
if ids is not None and len(ids) > 0:
# 绘制检测到的标记
image = cv2.aruco.drawDetectedMarkers(image.copy(), corners, ids)
# 标记大小(米)
marker_size = 0.05 # 5厘米
# 估计每个标记的位姿
rvecs, tvecs, _ = cv2.aruco.estimatePoseSingleMarkers(corners, marker_size, camera_matrix, dist_coeffs)
# 绘制每个标记的坐标轴
for i in range(len(ids)):
cv2.aruco.drawAxis(image, camera_matrix, dist_coeffs, rvecs[i], tvecs[i], 0.1) # 绘制0.1米长的坐标轴
# 打印每个标记的位置和姿态
print(f"Marker ID: {ids[i][0]}")
print(f"Position (x, y, z): {tvecs[i][0]}")
# 旋转向量转旋转矩阵
R, _ = cv2.Rodrigues(rvecs[i])
# 旋转矩阵转欧拉角
r = Rotation.from_matrix(R)
euler_angles = r.as_euler('xyz', degrees=True)
print(f"Orientation (roll, pitch, yaw): {euler_angles}")
print("-------------------")
return image
# 3. 主函数(从摄像头捕获视频)
def main():
# 打开摄像头
cap = cv2.VideoCapture(0)
while True:
# 读取一帧
ret, frame = cap.read()
if not ret:
break
# 检测ArUco标记并估计位姿
result = detect_aruco_and_estimate_pose(frame)
# 显示结果
cv2.imshow('ArUco Marker Detection', result)
# 按ESC键退出
if cv2.waitKey(1) & 0xFF == 27:
break
# 释放资源
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
main()
通过这些案例可以看出,OpenCV的calib3d模块为计算机视觉中的3D重建提供了基础工具,结合其他技术(如深度学习)可以构建更强大的应用系统。