Files
innovate_project/3D_construction/script/pose_estimation.py
2025-11-02 21:36:35 +08:00

230 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import itertools
import cv2
import numpy as np
from .reconstruction import get_camera_parameters # 我们仍然需要内参
def get_ground_truth_seams():
"""
【V5 - 基于图片和新坐标的最终版】
以公共交点为原点 (0,0,0) 建立坐标系。
Y轴: 沿着中间公共焊缝。
X轴: 沿着左下焊缝。
Z轴: 垂直于XY平面向上。
"""
print("--- INFO: Using new ground truth based on visual inspection. ---")
# 1. 定义关键点
p_origin = np.array([0.0, 0.0, 0.0]) # 公共交点,坐标系原点
p_middle_end = np.array([0.0, 50.3, 0.0]) # 中间焊缝的终点
p_bottom_start = np.array([-142.2, 0.0, 0.0]) # 左下焊缝的起点 (沿X负半轴)
# 对于 up_line1我们需要一个合理的3D坐标。
# 它从 p_middle_end (0, 50.3, 0) 开始。
# 假设它主要在Z方向延伸我们给它一个长度比如150。
# 你给的(-11.7, 142.5)可能存在测量误差或坐标系定义偏差。
# 我们先用一个理想化的、非退化的点来保证算法能工作。
p_top_end = np.array([0.0, 50.3, 150.0]) # 假设它竖直向上
ground_truth = {
# 上半部分拍摄的两条焊缝
'up_line1': {
'start_3d': p_middle_end, # (0, 50.3, 0)
'end_3d': p_top_end # (0, 50.3, 150)
},
'up_line2': {
'start_3d': p_origin, # (0, 0, 0)
'end_3d': p_middle_end # (0, 50.3, 0)
},
# 下半部分拍摄的两条焊缝
'bottom_line1': {
'start_3d': p_bottom_start, # (-142.2, 0, 0)
'end_3d': p_origin # (0, 0, 0)
},
'bottom_line2': { # 与 up_line2 完全相同
'start_3d': p_origin,
'end_3d': p_middle_end
}
}
return ground_truth
# def get_ground_truth_seams():
# """返回你手动测量的三维坐标(物体坐标系)。"""
# ground_truth = {
# 'up_line1': {
# 'start_3d': np.array([142.2, 0, 7.3]),
# 'end_3d': np.array([153.9, 0, 149.8])
# },
# 'up_line2': {
# 'start_3d': np.array([142.2, 0, 7.3]),
# 'end_3d': np.array([142.2, 50.3, 7.3])
# },
# 'bottom_line1': {
# 'start_3d': np.array([8.9, 0, 7.3]),
# 'end_3d': np.array([140.2, 0, 7.3])
# },
# 'bottom_line2': {
# 'start_3d': np.array([142.2, 0, 7.3]),
# 'end_3d': np.array([142.2, 50.3, 7.3])
# }
# }
# return ground_truth
def estimate_camera_pose(image_points_2d, object_points_3d, camera_side='L'):
"""
使用 solvePnP 估计相机位姿。
Args:
image_points_2d (np.ndarray): 图像上的2D点 (N, 2)。
object_points_3d (np.ndarray): 对应的物体坐标系下的3D点 (N, 3)。
camera_side (str): 'L' or 'R', 用于选择相机内参。
Returns:
tuple: (rotation_vector, translation_vector) 相机的位姿。
这是从物体坐标系到相机坐标系的变换。
"""
cam_L, cam_R, _ = get_camera_parameters()
if camera_side == 'L':
camera_matrix = cam_L['K']
dist_coeffs = cam_L['kc']
else:
camera_matrix = cam_R['K']
dist_coeffs = cam_R['kc']
# solvePnP 需要 float64 类型的输入
object_points_3d = np.array(object_points_3d, dtype=np.float64)
image_points_2d = np.array(image_points_2d, dtype=np.float64)
# 使用 solvePnP 求解位姿
# success: 是否成功
# rvec: 旋转向量 (Rodrigues vector)
# tvec: 平移向量
success, rvec, tvec = cv2.solvePnP(object_points_3d, image_points_2d, camera_matrix, dist_coeffs)
if not success:
print("Warning: solvePnP failed to estimate camera pose.")
return None, None
return rvec, tvec
def reproject_to_object_coords(endpoints_2d_L, endpoints_2d_R, part_type='up'):
"""
全新的重建流程V2 - 修正版):
1. 确定2D点和3D点之间最可能的对应关系。
2. 使用正确的对应关系估计左右相机位姿。
3. 利用双目信息对所有点进行三角化。
"""
ground_truth = get_ground_truth_seams()
cam_L_params, cam_R_params, _ = get_camera_parameters()
# --- 准备 solvePnP 的原始输入数据 ---
# object_points_3d: 3D真值点列表顺序固定
# image_points_2d_L: 识别出的2D点顺序可能需要调整
object_points_3d = []
image_points_2d_L = []
seam_keys = [] # 记录焊缝的key方便后续整理
for line_name in ['line1', 'line2']:
gt_key = f"{part_type}_{line_name}"
if gt_key in ground_truth:
# 添加3D真值点
object_points_3d.append(ground_truth[gt_key]['start_3d'])
object_points_3d.append(ground_truth[gt_key]['end_3d'])
# 添加对应的2D识别点
key_L = f"{part_type}_l_{line_name}"
image_points_2d_L.append(endpoints_2d_L[key_L]['start'])
image_points_2d_L.append(endpoints_2d_L[key_L]['end'])
seam_keys.append(gt_key)
# --- 1. 寻找最佳的2D-3D点对应关系 ---
# 对于每条焊缝2个点有2种可能的匹配正序或反序
# 如果有N条焊缝就有 2^N 种组合
# 我们有两条焊缝,所以有 2^2 = 4 种组合
best_reprojection_error = float('inf')
best_image_points_L = None
# a, b 分别代表line1和line2是否需要翻转 (0=不翻转, 1=翻转)
for a, b in itertools.product([0, 1], repeat=2):
swaps = (a, b)
current_image_points_L = list(image_points_2d_L) # 创建一个副本
# 根据组合,翻转对应焊缝的起点和终点
if swaps[0]: # 翻转 line1
current_image_points_L[0], current_image_points_L[1] = current_image_points_L[1], current_image_points_L[0]
if swaps[1]: # 翻转 line2
current_image_points_L[2], current_image_points_L[3] = current_image_points_L[3], current_image_points_L[2]
# 使用当前的对应关系,尝试估计位姿
rvec_L_try, tvec_L_try = estimate_camera_pose(current_image_points_L, object_points_3d, 'L')
if rvec_L_try is not None:
# 计算重投影误差来评估这个组合的好坏
projected_points, _ = cv2.projectPoints(np.array(object_points_3d), rvec_L_try, tvec_L_try,
cam_L_params['K'], cam_L_params['kc'])
error = cv2.norm(np.array(current_image_points_L, dtype=np.float32),
projected_points.reshape(-1, 2).astype(np.float32), cv2.NORM_L2)
if error < best_reprojection_error:
best_reprojection_error = error
best_image_points_L = current_image_points_L
if best_image_points_L is None:
print(f"Error: Could not find a valid pose for '{part_type}' part.")
return None
print(f"Found best point correspondence for '{part_type}' with reprojection error: {best_reprojection_error:.2f}")
# --- 2. 使用最佳对应关系,重新进行完整的重建流程 ---
# 纠正右相机2D点的顺序
# 这一步有点复杂,我们先假设左右相机的起点/终点翻转是一致的
# 这是个合理的假设,因为相机离得很近,看到的几何方向应该一样
best_image_points_R = []
for line_name in ['line1', 'line2']:
key_R = f"{part_type}_r_{line_name}"
points = [endpoints_2d_R[key_R]['start'], endpoints_2d_R[key_R]['end']]
# 检查左相机的点是否被翻转了
original_L = [endpoints_2d_L[key_R.replace('_r_', '_l_')]['start'],
endpoints_2d_L[key_R.replace('_r_', '_l_')]['end']]
idx = 0 if line_name == 'line1' else 2
# 如果左边翻转了,右边也翻转
if best_image_points_L[idx] != original_L[0]:
points.reverse()
best_image_points_R.extend(points)
# 估计左相机位姿
rvec_L, tvec_L = estimate_camera_pose(best_image_points_L, object_points_3d, 'L')
R_L, _ = cv2.Rodrigues(rvec_L)
P_L = cam_L_params['K'] @ np.hstack((R_L, tvec_L))
# 估计右相机位姿
rvec_R, tvec_R = estimate_camera_pose(best_image_points_R, object_points_3d, 'R')
R_R, _ = cv2.Rodrigues(rvec_R)
P_R = cam_R_params['K'] @ np.hstack((R_R, tvec_R))
# 三角化
points_2d_L_undistorted = cv2.undistortPoints(np.array(best_image_points_L, dtype=np.float32), cam_L_params['K'],
cam_L_params['kc'], P=cam_L_params['K'])
points_2d_R_undistorted = cv2.undistortPoints(np.array(best_image_points_R, dtype=np.float32), cam_R_params['K'],
cam_R_params['kc'], P=cam_R_params['K'])
points_4d = cv2.triangulatePoints(P_L, P_R, points_2d_L_undistorted.reshape(-1, 2).T,
points_2d_R_undistorted.reshape(-1, 2).T)
points_3d_object = (points_4d[:3] / points_4d[3]).T
# 整理输出
final_seams = {}
for i, key in enumerate(seam_keys):
final_seams[key] = {
'start_3d': points_3d_object[i * 2],
'end_3d': points_3d_object[i * 2 + 1]
}
return final_seams