230 lines
9.1 KiB
Python
230 lines
9.1 KiB
Python
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 |