# -- coding: utf-8 -- from ctypes import * import cv2 # 4.9.0 print(cv2.__version__) # 4.9.0 import numpy as np print(np.__version__) # 1.24.4 import open3d as o3d print(o3d.__version__) # 0.18.0 import math import os import time # from skimage import feature import aurco_3d_detect as aurco_3d_detect from os import abort from utils import Log from config import * # 点云整体移动 kk # 边缘点散射后需要重新投影到平面 # 顶点算法再优化 # 流程 # 3D点云整理修正,kk参数(该参数与物体反射率相关,通过MAP映射,后期不断更新MAP内容) # 3D点云空间过滤 # 3D点云提取标定板 seg_mark_cloud # 生成 im_2d_to_3d (采用相机内参) # 生成 im_project (采用相机内参) # 通过 im_project 识别标定板2D区域 obj_roi # 将 im_2d_to_3d 中的3D点云数据按照 obj_roi 进行归集,形成单独的标定板的3D数据文件 # 优化标定板3D数据 correct_mark_cloud # 3D 数据平滑、游离点\噪点去除 # 使用 RANSAC 平面拟合,计算平面模型 [A,B,C,D]、计算内点集合 inlier_cloud 、计算外点集合 outlier_cloud # 最小二乘计算 inlier_cloud 拟合的平面 # 通过平面模型 [A,B,C,D] 将内外点重投至该平面,计算 inlier_pcd_correct # 将 inlier_pcd_correct 和 inlier_cloud 合并至 inlier_pcd_correct # 3D标定板角点提取 select_conners_from_mark_cloud_2d # 通过 select_conners_from_mark_cloud_3d 对 inlier_pcd_correct 自动选取 3D 角点 (3D角点自动提取算法) # 将 inlier_pcd_correct 投影至 2D # 生成 im_2d_to_3d_n (n:1-4)(采用相机内参) # 生成 im_project_n (n:1-4) (采用相机内参) # 在2D图中人工标记角点 # 通过2D点、H、[A,B,C,D] 得到3D角点 c_lidar[16] # 【check】通过3D角点 c_lidar[16] 计算得出的标定板边长 l 和实际值的误差(绝对误差、4条边的均方差) # 2D标定板角点提取 # 定位 ARUCO 标记,计算外参 # 计算标定板4个角点的3D值 c_camera[16] # 【check】通过3D角点 c_camera[16] 计算得出的标定板边长 l 和实际值的误差(绝对误差、4条边的均方差) # SVD # 使用SVD c_lidar[16] 和 c_camera[16] 计算相机和雷达的旋转矩阵 RT # 循环采用不同数据通过SVD进行上述计算,采用最大似然优化 RT # 【check】c_lidar[16] 通过 RT 恢复到的位置和 c_camera[16] 比较(绝对误差、4条边的均方差) # 【check】3D点云通过重投影到2D图像的角点和标定板图像角点的误差估计(人工) # 推理验证 # 2D识别标定板 # 2D标定板角点提取 # 通过2D点、H、[A,B,C,D] 得到3D角点,c_lidar_camera[16] # 【check】c_lidar_camera[16]、c_lidar[16] 的误差(绝对误差、4条边的均方差) # 【check】c_lidar_camera[16]、c_camera[16] 、c_lidar[16]计算得出的边长和实际值的误差(绝对误差、4条边的均方差) # 自动化参数优化 # 实现自动推理验证流程 # 生成参数和结果对照记录 # 自动优化参数 # 预埋件实测 def _eulerAnglesToRotationMatrix(theta) : R_x = np.array([[1, 0, 0 ], [0, math.cos(theta[0]), -math.sin(theta[0]) ], [0, math.sin(theta[0]), math.cos(theta[0]) ] ]) R_y = np.array([[math.cos(theta[1]), 0, math.sin(theta[1]) ], [0, 1, 0 ], [-math.sin(theta[1]), 0, math.cos(theta[1]) ] ]) R_z = np.array([[math.cos(theta[2]), -math.sin(theta[2]), 0], [math.sin(theta[2]), math.cos(theta[2]), 0], [0, 0, 1] ]) R = np.dot(R_z, np.dot( R_y, R_x )) return R def cal_project_matrix(): # src_size是原始图像的大小 src_size = (w, h) # 计算新的摄像机矩阵 new_camera_matrix = cv2.getOptimalNewCameraMatrix(camera_matrix, dist_coeffs, src_size, alpha=0) Log.info("2D 投影矩阵计算结果为:") print(new_camera_matrix) # print(new_camera_matrix) return new_camera_matrix # 定义获取点云深度最大最小值 def get_pcd_depth_range(pts_3d): # 初始化最小值和最大值为第一个点的x坐标 min_x = 0 max_x = 0 # 遍历所有点,更新最小值和最大值 for point_3d in pts_3d.points: x = point_3d[0] if x < min_x: min_x = x elif x > max_x: max_x = x return max_x, min_x def undistort(distorted_img): # 矫正畸变 undistorted_img = cv2.undistort(distorted_img, camera_matrix, dist_coeffs, None, camera_matrix) # cv2.imwrite("distorted_img.png", undistorted_img) # undistorted_img = cv2.imread("distorted_img.png") # cv2.imshow("1", undistorted_img) # cv2.waitKey(0) return undistorted_img def clear_folder(folder_path): for file_name in os.listdir(folder_path): file_path = os.path.join(folder_path, file_name) if os.path.isfile(file_path): os.remove(file_path) else: clear_folder(file_path) os.rmdir(file_path) def sort_marks_pcd_3d(masks): new_mask = [] left_pt_0, left_pt_1, left_pt_2, left_pt_3 = 0,0,0,0 delete_index = [] coordinate_temp = [0,1,2,3] max_val = 0 min_val = 999999 for i in range(4): left_pt = masks[i][0] val = left_pt[0] + left_pt[1] if val > max_val: left_pt_3 = i max_val = val if val < min_val: left_pt_0 = i min_val = val delete_index.append(left_pt_0) delete_index.append(left_pt_3) coordinate_temp = np.delete(coordinate_temp, delete_index, axis=0) if masks[coordinate_temp[0]][0][0] > masks[coordinate_temp[1]][0][0]: left_pt_1 = coordinate_temp[0] left_pt_2 = coordinate_temp[1] else: left_pt_2 = coordinate_temp[0] left_pt_1 = coordinate_temp[1] new_mask.append(masks[left_pt_0]) new_mask.append(masks[left_pt_1]) new_mask.append(masks[left_pt_2]) new_mask.append(masks[left_pt_3]) return new_mask def detect_marks_roi(im_project, show=False): masks = [] offset = 40 kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (30, 30)) im_tmp = cv2.morphologyEx(im_project, cv2.MORPH_DILATE, kernel) im_tmp = cv2.GaussianBlur(im_tmp, (29, 29), 0) # im_tmp = cv2.resize(im_tmp, (900, 700)) # cv2.imshow(".",im_tmp) # cv2.waitKey(0) # 查找联通区域 gray = cv2.cvtColor(im_tmp, cv2.COLOR_BGR2GRAY) _, binary = cv2.threshold(gray, 128, 255, cv2.THRESH_BINARY) # num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(binary, connectivity=8, ltype=cv2.CV_32S) contours, _ = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE) colors = [ (255, 0, 0) , (255, 255, 0) , (0, 0, 255) , (255, 0, 255) ] color_idx = 0 Log.info("3D->2D 投影连通区域的像素点数量:") for contour in contours: rect_conner_points = [] size = cv2.contourArea(contour) top_idx = 0 right_idx = 0 bottom_idx = 0 left_idx = 0 x, y, w, h = cv2.boundingRect(contour) for idx in range(len(contour)): if y == contour[idx][0][1]: top_idx = idx if x == contour[idx][0][0]: left_idx = idx if y + h - 1 == contour[idx][0][1]: bottom_idx = idx if x + w - 1 == contour[idx][0][0]: right_idx = idx contour[left_idx][0][0] -= offset contour[top_idx][0][1] -= offset contour[right_idx][0][0] += offset contour[bottom_idx][0][1] += offset if contour[left_idx][0][0] < 0 or contour[top_idx][0][1] < 0 or \ contour[right_idx][0][0] > global_w or contour[bottom_idx][0][1] > global_h: continue if size > rmin: print(size) if size > rmin and size < rmax: contour[left_idx][0][0] -= offset contour[top_idx][0][1] -= offset contour[right_idx][0][0] += offset contour[bottom_idx][0][1] += offset cv2.circle(im_project, (contour[top_idx][0][0], contour[top_idx][0][1]), 25, colors[color_idx] , -1) cv2.circle(im_project, (contour[right_idx][0][0], contour[right_idx][0][1]), 25, colors[color_idx], -1) cv2.circle(im_project, (contour[bottom_idx][0][0], contour[bottom_idx][0][1]), 25, colors[color_idx], -1) cv2.circle(im_project, (contour[left_idx][0][0], contour[left_idx][0][1]), 25, colors[color_idx], -1) rect_conner_points.append(contour[left_idx][0]) rect_conner_points.append(contour[top_idx][0]) rect_conner_points.append(contour[right_idx][0]) rect_conner_points.append(contour[bottom_idx][0]) masks.append(rect_conner_points) color_idx += 1 if len(masks) != MARK_COUNT: Log.error("3D 重投影未找到4个mark") show = True if show: im_project = cv2.resize(im_project, (900, 700)) cv2.imshow("im", im_project) cv2.waitKey(0) return [] # 按照 左上1,右上2,左下3,右下4的顺序重新排序 masks = sort_marks_pcd_3d(masks) # for test if show: im_project = cv2.resize(im_project, (900, 700)) cv2.imshow("im", im_project) cv2.waitKey(0) return masks def project_cloud(pcd_cloud): im_project = np.zeros((global_h, global_w, 3), dtype=np.uint8) im_2d_to_3d = np.zeros((global_h ,global_w, 3), dtype=np.float64) rvec = np.array([np.array([0.]), np.array([0.]), np.array([0.])]) tvec = np.float64([0.0, 0.0, 0.0]) pts_2d, _ = cv2.projectPoints(np.array(pcd_cloud.points), rvec, tvec, camera_matrix, dist_coeffs) color = (255,255,255) list_pts_2d = pts_2d.tolist() list_pts_2d = [[[int(item) for item in subsublist] for subsublist in sublist] for sublist in list_pts_2d] points_np = np.asarray(pcd_cloud.points) for i, pt_2d in enumerate(list_pts_2d): # x, y = pt_2d.ravel() # if x == float('nan') or y == float('nan'): continue # x, y = int(x), int(y) # x, y = pt_2d[0][0], pt_2d[0][1] if 0 <= pt_2d[0][0] < global_w and 0 <= pt_2d[0][1] < global_h: im_project[pt_2d[0][1], pt_2d[0][0]] = color # im_2d_to_3d[y, x][0] = pcd_cloud.points[i][0] # im_2d_to_3d[y, x][1] = pcd_cloud.points[i][1] # im_2d_to_3d[y, x][2] = pcd_cloud.points[i][2] im_2d_to_3d[pt_2d[0][1], pt_2d[0][0]] = points_np[i] return im_project, im_2d_to_3d, pts_2d, list_pts_2d def seg_mark_cloud(ply_file_path, kk, show=False): marks_pcd = [] src_cloud_load = o3d.io.read_point_cloud(ply_file_path) if src_cloud_load is None or len(src_cloud_load.points) == 0 : abort() start_time = time.time() # 点云旋转 Rx = src_cloud_load.get_rotation_matrix_from_xyz((roll, 0, 0)) Ry = src_cloud_load.get_rotation_matrix_from_xyz((0, pitch, 0)) Rz = src_cloud_load.get_rotation_matrix_from_xyz((0, 0, yaw)) # t_matrix = np.identity(4) # t_matrix[:3, 3] = T_vector src_cloud_load.rotate(Rx, (1,0,0)) src_cloud_load.rotate(Ry, (0,1,0)) src_cloud_load.rotate(Rz, (0,0,1)) src_cloud_load.translate(T_vector) # 点云筛选 project_inlier_cloud = o3d.geometry.PointCloud() for point_3d in np.asarray(src_cloud_load.points): if -2.0 < point_3d[0] < 2.0 and -2.0 < point_3d[1] < 2.0 and point_3d[2] < 3.5: # 筛选x,y,z坐标 _r = math.sqrt(pow(point_3d[0], 2) + pow(point_3d[1], 2) + pow(point_3d[2], 2)) if _r == 0: continue if point_3d[0] > 0: point_3d[0] = point_3d[0] + point_3d[0] / _r * kk else: point_3d[0] = point_3d[0] + point_3d[0] / _r * kk if point_3d[1] > 0: point_3d[1] = point_3d[1] + point_3d[1] / _r * kk else: point_3d[1] = point_3d[1] + point_3d[1] / _r * kk point_3d[2] = point_3d[2] + point_3d[2] / _r * kk # point_3d[2] -= 0.0 project_inlier_cloud.points.append((point_3d[0], point_3d[1], point_3d[2])) # 投影3D点云 im_project, im_2d_to_3d, pts_2d, list_pts_2d = project_cloud(project_inlier_cloud) # 自动识别ROI角点 mark_rois = detect_marks_roi(im_project, show) if len(mark_rois) != MARK_COUNT: return marks_pcd # 保存4个点云的数据到文件 # clear_folder("./detect_cloud") clear_folder(conf_temp_cloud_path) # list_pts_2d = pts_2d.tolist() # list_pts_2d = [[[int(item) for item in subsublist] for subsublist in sublist] for sublist in list_pts_2d] for i in range(len(mark_rois)): if len(mark_rois[i]) < 4: continue one_mark_pcd = o3d.geometry.PointCloud() mark_rois_tmp = np.asarray(mark_rois[i]) for _, pt_2d in enumerate(list_pts_2d): # x, y = pt_2d.ravel() # x, y = int(x), int(y) # x, y = pt_2d[0][0], pt_2d[0][1] if cv2.pointPolygonTest(mark_rois_tmp, (pt_2d[0][0],pt_2d[0][1]), False) >= 0: one_mark_pcd.points.append((im_2d_to_3d[pt_2d[0][1], pt_2d[0][0]][0], im_2d_to_3d[pt_2d[0][1], pt_2d[0][0]][1], im_2d_to_3d[pt_2d[0][1], pt_2d[0][0]][2])) # o3d.io.write_point_cloud("./detect_cloud/" + str(i) + ".ply", tmp_cloud) o3d.io.write_point_cloud(conf_temp_cloud_path + str(i) + ".ply", one_mark_pcd) if show: o3d.visualization.draw_geometries([one_mark_pcd], mesh_show_wireframe=True) marks_pcd.append(one_mark_pcd) if show: im_project = cv2.resize(im_project, (int(934), int(700)), interpolation=cv2.INTER_LINEAR) cv2.imshow("im_project", im_project) cv2.waitKey(0) end_time = time.time() execution_time = end_time - start_time Log.info("3D 分割点云数据耗时:") print(execution_time * 1000) return marks_pcd def filter_cloud(pcd): # 获取点云中的点坐标 points = np.asarray(pcd.points) # 定义 x, y, z 的范围 x_min, x_max = -1.8, 1.8 y_min, y_max = -1.5, 1.5 z_min, z_max = 0.0, global_z_max # 创建布尔掩膜 mask = ( (points[:, 0] >= x_min) & (points[:, 0] <= x_max) & (points[:, 1] >= y_min) & (points[:, 1] <= y_max) & (points[:, 2] >= z_min) & (points[:, 2] <= z_max) ) # 应用掩膜以过滤点云 filtered_points = points[mask] # 更新点云对象 pcd.points = o3d.utility.Vector3dVector(filtered_points) return pcd def seg_mark_cloud_simple(ply_file_path, kk, show=False): marks_pcd = [] src_cloud_load = o3d.io.read_point_cloud(ply_file_path) if src_cloud_load is None or len(src_cloud_load.points) == 0 : abort() start_time = time.time() # 点云旋转 Rx = src_cloud_load.get_rotation_matrix_from_xyz((roll, 0, 0)) Ry = src_cloud_load.get_rotation_matrix_from_xyz((0, pitch, 0)) Rz = src_cloud_load.get_rotation_matrix_from_xyz((0, 0, yaw)) # t_matrix = np.identity(4) # t_matrix[:3, 3] = T_vector src_cloud_load.rotate(Rx, (1,0,0)) src_cloud_load.rotate(Ry, (0,1,0)) src_cloud_load.rotate(Rz, (0,0,1)) src_cloud_load.translate(T_vector) # 点云筛选 project_inlier_cloud = o3d.geometry.PointCloud() if kk != 0.0: for point_3d in np.asarray(src_cloud_load.points): if -2.0 < point_3d[0] < 2.0 and -2.0 < point_3d[1] < 2.0 and point_3d[2] < global_z_max: # 筛选x,y,z坐标 _r = math.sqrt(pow(point_3d[0], 2) + pow(point_3d[1], 2) + pow(point_3d[2], 2)) if _r == 0: continue if point_3d[0] > 0: point_3d[0] = point_3d[0] + point_3d[0] / _r * kk else: point_3d[0] = point_3d[0] + point_3d[0] / _r * kk if point_3d[1] > 0: point_3d[1] = point_3d[1] + point_3d[1] / _r * kk else: point_3d[1] = point_3d[1] + point_3d[1] / _r * kk point_3d[2] = point_3d[2] + point_3d[2] / _r * kk # point_3d[2] -= 0.0 project_inlier_cloud.points.append((point_3d[0], point_3d[1], point_3d[2])) else: project_inlier_cloud = filter_cloud(src_cloud_load) # 投影3D点云 im_project, im_2d_to_3d, pts_2d, list_pts_2d = project_cloud(project_inlier_cloud) # 自动识别ROI角点 mark_rois = detect_marks_roi(im_project, show) if len(mark_rois) != MARK_COUNT: return marks_pcd # 保存4个点云的数据到文件 # clear_folder("./detect_cloud") # clear_folder(conf_temp_cloud_path) # list_pts_2d = pts_2d.tolist() # list_pts_2d = [[[int(item) for item in subsublist] for subsublist in sublist] for sublist in list_pts_2d] Log.info("3D->2D project points") for i in range(len(mark_rois)): if len(mark_rois[i]) < 4: continue one_mark_pcd = o3d.geometry.PointCloud() mark_rois_tmp = np.asarray(mark_rois[i]) select_itmes = 0 for _, pt_2d in enumerate(list_pts_2d): # x, y = pt_2d.ravel() # x, y = int(x), int(y) # x, y = pt_2d[0][0], pt_2d[0][1] if cv2.pointPolygonTest(mark_rois_tmp, (pt_2d[0][0],pt_2d[0][1]), False) >= 0: # one_mark_pcd.points.append((im_2d_to_3d[y, x][0], im_2d_to_3d[y, x][1], im_2d_to_3d[y, x][2])) one_mark_pcd.points.append((im_2d_to_3d[pt_2d[0][1], pt_2d[0][0]][0], im_2d_to_3d[pt_2d[0][1], pt_2d[0][0]][1], im_2d_to_3d[pt_2d[0][1], pt_2d[0][0]][2])) select_itmes += 1 print(select_itmes) # o3d.io.write_point_cloud("./detect_cloud/" + str(i) + ".ply", tmp_cloud) # 不写入文件,节省时间 # o3d.io.write_point_cloud(conf_temp_cloud_path + str(i) + ".ply", one_mark_pcd) # if show: # o3d.visualization.draw_geometries([one_mark_pcd], mesh_show_wireframe=True) marks_pcd.append(one_mark_pcd) if show: im_project = cv2.resize(im_project, (int(934), int(700)), interpolation=cv2.INTER_LINEAR) cv2.imshow("im_project", im_project) cv2.waitKey(0) end_time = time.time() execution_time = end_time - start_time Log.info("3D 分割点云数据耗时:") print(execution_time * 1000) return marks_pcd # distance_threshold (float): # 平面内点的最大距离阈值。这是指一个点到估计平面的距离,如果距离小于或等于这个阈值,则认为该点属于此平面。 # ransac_n (int): # 每次迭代中用于估计模型的点数。对于平面分割,通常设置为 3,因为三个非共线点可以唯一确定一个平面。 # num_iterations (int): # RANSAC 算法的迭代次数。更多的迭代次数会增加找到最优解的概率,但也增加了计算时间。 def correct_mark_cloud(mark_pcd): plane_model, inliers = mark_pcd.segment_plane(distance_threshold=0.0155, ransac_n=3, num_iterations=10000) inlier_pcd = mark_pcd.select_by_index(inliers) inlier_pcd = reproject_cloud(plane_model, inlier_pcd) #利用投影得到的图像 inlier_pcd = inlier_pcd.paint_uniform_color([1.0, 0, 0]) outlier_cloud = mark_pcd.select_by_index(inliers, invert=True) # 暂时没有用到 # radius = 0.05 # inlier_pcd = smooth_point_cloud(inlier_pcd, radius) #利用拟合得到的图像 inlier_pcd_correct = reproject_cloud(plane_model, mark_pcd) #利用投影得到的图像 inlier_pcd_correct = inlier_pcd_correct.paint_uniform_color([0.0, 1.0, 0]) # o3d.visualization.draw_geometries([ inlier_pcd, inlier_pcd_correct ]) # inlier_pcd_correct = inlier_pcd # ??? return plane_model, inlier_pcd, inlier_pcd_correct # 3D标定板角点提取 select_conners_from_mark_cloud_2d # 通过 select_conners_from_mark_cloud_3d 对 inlier_pcd_correct 自动选取 3D 角点 (3D角点自动提取算法) # 将 inlier_pcd_correct 投影至 2D # 生成 im_2d_to_3d_n (n:1-4)(采用相机内参) # 生成 im_project_n (n:1-4) (采用相机内参) # 在2D图中人工标记角点 # 通过2D点、H、[A,B,C,D] 得到3D角点 c_lidar[16] # 【check】通过3D角点 c_lidar[16] 计算得出的标定板边长 l 和实际值的误差(绝对误差、4条边的均方差) def cal_H(im_2d_to_3d, pts_2d, list_pts_2d): pts_3d = [] # list_pts_2d = pts_2d.tolist() # list_pts_2d = [[[int(item) for item in subsublist] for subsublist in sublist] for sublist in list_pts_2d] for i, pt_2d in enumerate(list_pts_2d): # x, y = pt_2d.ravel() # x, y = int(x), int(y) # x, y = pt_2d[0][0], pt_2d[0][1] pts_3d.append([im_2d_to_3d[pt_2d[0][1], pt_2d[0][0]][0], im_2d_to_3d[pt_2d[0][1], pt_2d[0][0]][1], im_2d_to_3d[pt_2d[0][1], pt_2d[0][0]][2]]) ransacReprojThreshold = 3.0 # 查找单应矩阵 # 修改第三个参数 1.0 -> 5.0 # 利用基于RANSAC的鲁棒算法选择最优的四组配对点,再计算转换矩阵H(3*3)并返回,以便于反向投影错误率达到最小 # ransacReprojThreshold 设置为 1.0 # H, mask = cv2.findHomography(np.array(src_points), np.array(dst_points), cv2.RANSAC, ransacReprojThreshold, maxIters=1500, confidence=0.998) ## 修改 1.0 -> 5.0 pts_hom = [] for i in range(len(pts_2d)): pt = pts_2d[i] x,y = pt[0][0], pt[0][1] pts_hom.append([x*1.0, y*1.0, 1.0]) H, _ = cv2.findHomography(np.array(pts_3d), np.array(pts_hom), cv2.RANSAC, ransacReprojThreshold) ## 修改 1.0 -> 5.0 Log.info("2D-3D 单应性矩阵计算结果为:") print(H) return H def sort_conners_2d(conners): sort_conner = [] left, top, right, bottom = 0, 0, 0, 0 # maxx,maxy = 0,0 maxx,maxy = -100000,-100000 minx,miny = 100000,100000 for i in range(len(conners)): conner = conners[i] if conner[0] < minx: minx = conner[0] left = i if conner[0] > maxx: maxx = conner[0] right = i if conner[1] < miny: miny = conner[1] top = i if conner[1] > maxy: maxy = conner[1] bottom = i sort_conner.append(conners[left]) sort_conner.append(conners[top]) sort_conner.append(conners[right]) sort_conner.append(conners[bottom]) return sort_conner def remove_convex_hull_points(contour): hull = cv2.convexHull(contour, returnPoints=True) hull_indices = set(hull.flatten()) non_hull_points = [point for i, point in enumerate(contour) if i not in hull_indices] return np.array(non_hull_points) def smooth_convex_hull(contour, epsilon=0.1): # 计算凸包 hull = cv2.convexHull(contour) # 使用 approxPolyDP 减少顶点数量,从而达到平滑效果 epsilon_value = epsilon * cv2.arcLength(hull, True) smoothed_hull = cv2.approxPolyDP(hull, epsilon_value, True) return np.array(smoothed_hull) from scipy.interpolate import splprep, splev def spline_fit(points, s=0, k=2): # 确保有足够的数据点 if len(points) <= k: return None tck, u = splprep(points.T, s=s, k=k) u_new = np.linspace(u.min(), u.max(), 1000) x_new, y_new = splev(u_new, tck) return np.vstack((x_new, y_new)).T def smooth_convex_hull_with_spline(contour, s=0): hull = cv2.convexHull(contour) points = hull.reshape(-1, 2) smoothed_points = spline_fit(points, s=s) if smoothed_points is None: return None return smoothed_points.astype(np.int32) def find_largest_contour(contours): if not contours: return None # 计算所有轮廓的面积 areas = [cv2.contourArea(cnt) for cnt in contours] # 找到面积最大的轮廓索引 max_area_index = np.argmax(areas) # 返回面积最大的轮廓 return contours[max_area_index] from shapely.geometry import Polygon,MultiPolygon # 需要安装 shapely 库来处理多边形相交 def rect_to_polygon(rect): box = cv2.boxPoints(rect) return Polygon(box) def polygon_intersection(poly1, poly2): inter_poly = poly1.intersection(poly2) if not inter_poly.is_empty: return inter_poly else: return None def fill_intersection_white(image, rect1, rect2): """ 在给定图像中,用白色填充两个旋转矩形的相交区域。 参数: image (numpy.ndarray): 输入图像 rect1 (tuple): 第一个旋转矩形,格式为 (center, size, angle) rect2 (tuple): 第二个旋转矩形,格式为 (center, size, angle) """ # 将旋转矩形转换为多边形 poly1 = rect_to_polygon(rect1) poly2 = rect_to_polygon(rect2) # 计算相交区域 intersection = polygon_intersection(poly1, poly2) if intersection is not None: # 创建掩膜并填充白色 mask = np.zeros_like(image) # 如果相交区域是多边形 if isinstance(intersection, Polygon): inter_points = np.array(intersection.exterior.coords, dtype=np.int32) cv2.fillPoly(mask, [inter_points], (255, 255, 255)) elif isinstance(intersection, MultiPolygon): for geom in intersection.geoms: inter_points = np.array(geom.exterior.coords, dtype=np.int32) cv2.fillPoly(mask, [inter_points], (255, 255, 255)) # 应用掩膜到原图 image[mask == 255] = 255 def select_conners_from_mark_cloud_2d_old(mark_pcd, plane_model): # 将 inlier_pcd_correct 投影至 2D im_project, im_2d_to_3d, pts_2d, list_pts_2d = project_cloud(mark_pcd) im_project_gray = cv2.cvtColor(im_project, cv2.COLOR_BGR2GRAY) non_zero = cv2.findNonZero(im_project_gray) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (20, 20)) im_project = cv2.morphologyEx(im_project, cv2.MORPH_DILATE, kernel) for point in non_zero: im_project[point[0][1], point[0][0]] = (255,255,255) rect = cv2.minAreaRect(non_zero) box = cv2.boxPoints(rect) box = np.int0(box) # for test # cv2.drawContours(im_project, [box], 0, (255), 10) # im_project = cv2.resize(im_project, (1600, 1200)) # cv2.imshow("_im", im_project) # cv2.waitKey(0) # 计算 mark cloud 点云的单应矩阵 H = cal_H(im_2d_to_3d, pts_2d, list_pts_2d) # 获取高精确的角点 2D 位置 conners = sort_conners_2d(list(box)) # 通过单应矩阵计算角点 3D 位置 H_inv = np.linalg.inv(H) conners_pcd = o3d.geometry.PointCloud() for i in range(4): conner = conners[i] x, y = conner[0], conner[1] pts_hom = np.array([x, y, 1]) pts_3d = np.dot(H_inv, pts_hom.T) pts_3d = pts_3d/pts_3d[2] conners_pcd.points.append((pts_3d[0],pts_3d[1],pts_3d[2])) conners_pcd = reproject_cloud(plane_model, conners_pcd) Log.info("2D标记角点,得到得边长为:") for i in range(4): print(np.linalg.norm(conners_pcd.points[i] - conners_pcd.points[(i+1)%4])) return conners_pcd def select_conners_from_mark_cloud_2d(mark_pcd, plane_model): # 将 inlier_pcd_correct 投影至 2D im_project, im_2d_to_3d, pts_2d, list_pts_2d = project_cloud(mark_pcd) im_project_gray = cv2.cvtColor(im_project, cv2.COLOR_BGR2GRAY) non_zero = cv2.findNonZero(im_project_gray) kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (20, 20)) im_project = cv2.morphologyEx(im_project, cv2.MORPH_DILATE, kernel) for point in non_zero: im_project[point[0][1], point[0][0]] = (255,255,255) rect = cv2.minAreaRect(non_zero) box = cv2.boxPoints(rect) box = np.int0(box) #================================================ kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (40, 40)) im_project_gray = cv2.morphologyEx(im_project_gray, cv2.MORPH_DILATE, kernel) kernel = cv2.getStructuringElement(cv2.MORPH_CROSS, (40, 40)) im_project_gray = cv2.morphologyEx(im_project_gray, cv2.MORPH_CLOSE, kernel) # im_project_gray = cv2.resize(im_project_gray, (1600, 1200)) # cv2.imshow("im_project_gray", im_project_gray) # cv2.waitKey(0) _, binary_img = cv2.threshold(im_project_gray, 0, 255, cv2.THRESH_BINARY) contours, _ = cv2.findContours(binary_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) # 对每个轮廓进行处理 # processed_contours = [remove_convex_hull_points(cnt) for cnt in contours] processed_contours = [smooth_convex_hull(cnt) for cnt in contours] contour = find_largest_contour(processed_contours) rect2 = cv2.minAreaRect(contour) box2 = cv2.boxPoints(rect2) box2 = np.int0(box2) # box = box2 temp_img = np.zeros((im_project_gray.shape[0], im_project_gray.shape[1], 1), dtype=np.uint8) fill_intersection_white(temp_img, rect, rect2) non_zero = cv2.findNonZero(temp_img) rect3 = cv2.minAreaRect(non_zero) box3 = cv2.boxPoints(rect3) box3 = np.int0(box3) box = box2 # processed_contours = [] # for cnt in contours: # smoothed_hull = smooth_convex_hull_with_spline(cnt, s=0.0) # if smoothed_hull is None: continue # # cv2.polylines(drawing, [smoothed_hull], isClosed=True, color=(0, 255, 0), thickness=2) # processed_contours.append(smoothed_hull) # drawing = np.zeros_like(im_project) # cv2.drawContours(drawing, processed_contours, -1, color=255, thickness=1) # cv2.imshow("Processed Contours", drawing) # cv2.waitKey(0) # # for test # cv2.drawContours(im_project, [box2], 0, (255), 15) # # cv2.drawContours(im_project, processed_contours, -1, color=(0,0,255), thickness=10) # cv2.drawContours(im_project, [box3], 0, (0,255,0), 20) # for point in non_zero: # im_project[point[0][1], point[0][0]] = (255,255,255) # im_project = cv2.resize(im_project, (1400, 1000)) # cv2.imshow("_im", im_project) # cv2.waitKey(0) #================================================ # 计算 mark cloud 点云的单应矩阵 H = cal_H(im_2d_to_3d, pts_2d, list_pts_2d) # 获取高精确的角点 2D 位置 conners = sort_conners_2d(list(box)) Log.error("3d-2d 的边长") print(np.linalg.norm(conners[0] - conners[1])) print(np.linalg.norm(conners[1] - conners[2])) print(np.linalg.norm(conners[2] - conners[3])) print(np.linalg.norm(conners[3] - conners[0])) # 通过单应矩阵计算角点 3D 位置 H_inv = np.linalg.inv(H) conners_pcd = o3d.geometry.PointCloud() for i in range(4): conner = conners[i] x, y = conner[0], conner[1] pts_hom = np.array([x, y, 1]) pts_3d = np.dot(H_inv, pts_hom.T) pts_3d = pts_3d/pts_3d[2] conners_pcd.points.append((pts_3d[0],pts_3d[1],pts_3d[2])) conners_pcd = reproject_cloud(plane_model, conners_pcd) Log.info("2D标记角点,得到得边长为:") for i in range(4): print(np.linalg.norm(conners_pcd.points[i] - conners_pcd.points[(i+1)%4])) return conners_pcd def extend_line_3d(pt1, pt2, distance): """ 计算 3D 空间两个点的延长线上的一个点 参数: - point1: 第一个点的坐标 (x1, y1, z1),类型为列表或数组 - point2: 第二个点的坐标 (x2, y2, z2),类型为列表或数组 - distance: 从第二个点延长的距离 (正值表示延长方向,负值表示反向) 返回: - 延长线上的点坐标 (x, y, z),类型为数组 """ # 转换为 NumPy 数组 point1 = np.array(pt1) point2 = np.array(pt2) # 计算方向向量 direction = point2 - point1 # 计算方向向量的单位向量 direction_unit = direction / np.linalg.norm(direction) # 延长线上的点 extended_point = point2 + distance * direction_unit return extended_point def correct_mask_conner(inlier_pcd_correct, pcd_conners_2d): oriented_bounding_box = inlier_pcd_correct.get_axis_aligned_bounding_box() center_point = oriented_bounding_box.get_center() # 0.3536 real_len = 0.3535534 pcd_correct_conners = o3d.geometry.PointCloud() for i in range(4): error_len = np.linalg.norm(pcd_conners_2d.points[i] - center_point) - real_len real_conner = extend_line_3d(center_point, pcd_conners_2d.points[i], -error_len) pcd_correct_conners.points.append(real_conner) # print("中心点距离:" + str(np.linalg.norm(pcd_conners_2d.points[i] - center_point) )) # print(real_conner) return pcd_correct_conners, center_point def fit_plane_least_squares(points): # 计算中心点 centroid = np.mean(points, axis=0) # 计算协方差矩阵 cov_matrix = (points - centroid).T.dot(points - centroid) # 计算协方差矩阵的特征值和特征向量 eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix) # 最小特征值对应的特征向量就是平面的法向量 normal = eigenvectors[:, np.argmin(eigenvalues)] return centroid, normal def smooth_point_cloud(pcd, radius): # Create a new PointCloud object smoothed_pcd = o3d.geometry.PointCloud() smoothed_points = [] # Build KDTree for the input point cloud kdtree = o3d.geometry.KDTreeFlann(pcd) # For each point, perform least squares plane fitting for point in np.asarray(pcd.points): # Find points within the radius [k, idx, _] = kdtree.search_radius_vector_3d(point, radius) if k < 3: # If there are not enough points, skip continue # Extract neighboring points neighbors = np.asarray(pcd.points)[idx, :] # Use least squares to fit a plane centroid, normal = fit_plane_least_squares(neighbors) # Project the point onto the fitted plane point_to_centroid = point - centroid distance = point_to_centroid.dot(normal) smoothed_point = point - distance * normal smoothed_points.append(smoothed_point) # Set the smoothed points smoothed_pcd.points = o3d.utility.Vector3dVector(smoothed_points) return smoothed_pcd def reproject_cloud(plane_model, cloud_load): reproject_pcd = o3d.geometry.PointCloud() A,B,C,D = plane_model for pts_3d in cloud_load.points: # 求线与平面的交点 t = -D / (A * pts_3d[0] + B * pts_3d[1] + C * pts_3d[2]) # pts_3d = np.array([t * pts_3d[0], t * pts_3d[1], t * pts_3d[2]]) reproject_pcd.points.append((t * pts_3d[0], t * pts_3d[1], t * pts_3d[2])) return reproject_pcd def o2t(o_pcd): t_pcd = o3d.t.geometry.PointCloud() t_pcd.point.positions = o3d.core.Tensor(np.asarray(o_pcd.points)) t_pcd.point.colors = o3d.core.Tensor(np.asarray(o_pcd.colors)) o_pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30)) t_pcd.point.normals = o3d.core.Tensor(np.asarray(o_pcd.normals)) return t_pcd def t2o(t_pcd): o_pcd = o3d.geometry.PointCloud() o_pcd.points = o3d.utility.Vector3dVector(t_pcd.point.positions.numpy()) # o_pcd.colors = o3d.utility.Vector3dVector(t_pcd.point.colors.numpy()) return o_pcd # def calculate_curvature(pcd): # # 估计法线 # pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30)) # # 计算曲率 # curvatures = [] # for i in range(len(pcd.points)): # normal = np.array(pcd.normals[i]) # k_neighbors = pcd.select_by_index(pcd.nearest_k_neighbor(i, 10)[1]) # 获取最近的10个邻居 # neighbor_normals = np.array(k_neighbors.normals) # curvature = np.mean(np.abs(neighbor_normals - normal)) # 简单地取平均绝对差作为曲率估计 # curvatures.append(curvature) # return np.array(curvatures) def calculate_curvature(pcd): # 估计法线 pcd.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30)) # 构建KDTree pcd_tree = o3d.geometry.KDTreeFlann(pcd) # 计算曲率 curvatures = [] for i in range(len(pcd.points)): [k, idx, _] = pcd_tree.search_knn_vector_3d(pcd.points[i], 10) # 获取最近的10个邻居 neighbor_normals = np.array([pcd.normals[j] for j in idx]) normal = np.array(pcd.normals[i]) curvature = np.mean(np.abs(neighbor_normals - normal)) # 简单地取平均绝对差作为曲率估计 curvatures.append(curvature) return np.array(curvatures) def select_conners_from_mark_cloud_3d(inlier_cloud): # 2. 计算凸包以获取边界点 hull, _ = inlier_cloud.compute_convex_hull() hull_points = np.asarray(hull.vertices) # 3. 将凸包顶点转换为点云对象以便后续处理 hull_pcd = o3d.geometry.PointCloud() hull_pcd.points = o3d.utility.Vector3dVector(hull_points) # 4. 简单地假设凸包是近似方形,计算四边形的四个角点 # 这里采用一种简化方法,即寻找距离最远的两个点对作为对角线端点 distances = np.linalg.norm(hull_points[:, None] - hull_points, axis=2) max_distance_indices = np.unravel_index(np.argmax(distances), distances.shape) diagonal_points = [] diagonal_points.append(hull_points[max_distance_indices[0]]) diagonal_points.append(hull_points[max_distance_indices[1]]) diagonal_points = np.asarray(diagonal_points) # 找到剩余两个角点,它们应位于对角线两端点构成的直线上 remaining_points = np.delete(hull_points, max_distance_indices, axis=0) idx = [] for i in range(len(remaining_points)): pt = remaining_points[i] if np.linalg.norm(pt - diagonal_points[0]) < 0.3 or np.linalg.norm(pt - diagonal_points[1]) < 0.3 : idx.append(i) remaining_points = np.delete(remaining_points, idx, axis=0) distances = np.linalg.norm(remaining_points[:, None] - remaining_points, axis=2) max_distance_indices = np.unravel_index(np.argmax(distances), distances.shape) diagonal_points2 = [] diagonal_points2.append(remaining_points[max_distance_indices[0]]) diagonal_points2.append(remaining_points[max_distance_indices[1]]) diagonal_points2 = np.asarray(diagonal_points2) corner_points = [diagonal_points[0], diagonal_points[1], diagonal_points2[0], diagonal_points2[1]] Log.info("3D标记角点,得到得边长为:") print(np.linalg.norm(diagonal_points[0] - diagonal_points2[0])) print(np.linalg.norm(diagonal_points[0] - diagonal_points2[1])) print(np.linalg.norm(diagonal_points[1] - diagonal_points2[0])) print(np.linalg.norm(diagonal_points[1] - diagonal_points2[1])) # 5. 创建一个新的点云来表示角点,并可视化 corner_pcd = o3d.geometry.PointCloud() corner_pcd.points = o3d.utility.Vector3dVector(corner_points) corner_pcd.paint_uniform_color([1, 0, 0]) return corner_pcd from sklearn.cluster import DBSCAN import copy import scipy def select_conners_from_mark_cloud_3d_v2(inlier_cloud): # 修正 downsampled_inlier_cloud = copy.deepcopy(inlier_cloud) for i in range(len(downsampled_inlier_cloud.points)): if i % 2 == 0: downsampled_inlier_cloud.points[i][0] += 0.0001 # 估计法线 downsampled_inlier_cloud.estimate_normals() # 寻找边界点 cl, ind = downsampled_inlier_cloud.remove_statistical_outlier(nb_neighbors=20, std_ratio=2.0) boundary_points = downsampled_inlier_cloud.select_by_index(ind, invert=True) # 将边界点转换为numpy数组 points = np.asarray(boundary_points.points) # 假设我们正在寻找的是矩形,我们可以尝试用凸包算法来找到最小包围四边形 hull = scipy.spatial.ConvexHull(points) # 凸包顶点索引 hull_vertices = hull.vertices # 如果凸包有4个顶点,那么这些可能是我们要找的角点 if len(hull_vertices) == 4: corner_points = points[hull_vertices] else: # 如果不是4个顶点,则需要进一步处理 # 这里简化处理,直接返回凸包顶点作为角点 corner_points = points[hull_vertices] # for i in range(len(downsampled_inlier_cloud.points)): # if i % 2 == 0: # downsampled_inlier_cloud.points[i][0] += 0.0001 # obb = downsampled_inlier_cloud.get_oriented_bounding_box() # 提取角点 # corner_points = np.asarray(obb.get_box_points()) # 创建新的点云对象表示角点 corner_pcd = o3d.geometry.PointCloud() corner_pcd.points = o3d.utility.Vector3dVector(corner_points) # 可视化结果 o3d.visualization.draw_geometries([inlier_cloud, corner_pcd]) # corner_points = [diagonal_points[0], diagonal_points[1], # diagonal_points2[0], diagonal_points2[1]] # Log.info("3D标记角点,得到得边长为:") # print(np.linalg.norm(diagonal_points[0] - diagonal_points2[0])) # print(np.linalg.norm(diagonal_points[0] - diagonal_points2[1])) # print(np.linalg.norm(diagonal_points[1] - diagonal_points2[0])) # print(np.linalg.norm(diagonal_points[1] - diagonal_points2[1])) # 5. 创建一个新的点云来表示角点,并可视化 # corner_pcd = o3d.geometry.PointCloud() # corner_pcd.points = o3d.utility.Vector3dVector(corner_points) # corner_pcd.paint_uniform_color([1, 0, 0]) return corner_pcd def create_spheres(points, sizes=None, color=[0, 0.706, 1]): """ 创建一系列球体以替代点云中的点,允许每个点有不同的大小。 参数: points: N x 3 数组,表示N个点的位置。 sizes: N维数组或列表,表示对应点的半径大小。如果为None,则所有球体大小相同。 color: RGB颜色值,默认是橙色。 返回: spheres: 包含所有球体的Open3D几何对象列表。 """ spheres = [] if sizes is None: sizes = [1] * len(points) # 如果没有提供大小,则默认所有球体大小相同 for i, point in enumerate(points): sphere = o3d.geometry.TriangleMesh.create_sphere(radius=sizes[i]) sphere.translate(point) # 将球体移动到点的位置 sphere.paint_uniform_color(color) spheres.append(sphere) return spheres def cal_marks_3d_conners(show=False): marks_3d_conners = [] for i in range(4): # mark_ply_file_path = './detect_cloud/' + str(i) + '.ply' mark_ply_file_path = conf_temp_cloud_path + str(i) + '.ply' # 计算标定板尺寸 # mark_ply_file_path = './detect_cloud/1.ply' mark_pcd = o3d.io.read_point_cloud(mark_ply_file_path) plane_model, inlier_cloud, inlier_pcd_correct = correct_mark_cloud(mark_pcd) # t_pcd = o2t(inlier_cloud) # _dir = dir(t_pcd) # for it in _dir: # print(it) # t_boundaries, mask = t_pcd.compute_boundary_points(0.05, 60) # boundaries = t2o(t_boundaries) # # 计算曲率 # curvatures = calculate_curvature(inlier_cloud) # threshold = np.percentile(curvatures, 95) # 使用95百分位数作为阈值 # corner_indices = np.where(curvatures > threshold)[0] # t_corner_pcd = inlier_cloud.select_by_index(corner_indices.tolist()) # t_corner_pcd.paint_uniform_color([0, 0, 1]) # 将角点涂成红色 used_pcd = inlier_cloud # inlier_cloud / inlier_pcd_correct pcd_conners_2d = select_conners_from_mark_cloud_2d(used_pcd, plane_model) pcd_conners_2d_correct, center_point = correct_mask_conner(used_pcd, pcd_conners_2d) Log.info("修正后2D标记角点,得到得边长为:") for i in range(4): print(np.linalg.norm(pcd_conners_2d_correct.points[i] - pcd_conners_2d_correct.points[(i+1)%4])) # pcd_conners_3d = select_conners_from_mark_cloud_3d(inlier_pcd_correct) marks_3d_conners.append(pcd_conners_2d) if show: # 角点显示的圆球半径,单位(米) r = 0.006 sizes = [r,r,r,r] spheres_2d = create_spheres(pcd_conners_2d.points, sizes, color=[0, 0.706, 1]) spheres_3d = create_spheres(pcd_conners_2d_correct.points, sizes, color=[0, 0, 0]) spheres_center_3d = create_spheres([center_point], [0.01], color=[0, 1, 1]) o3d.visualization.draw_geometries([ spheres_2d[0],spheres_2d[1],spheres_2d[2],spheres_2d[3], spheres_3d[0],spheres_3d[1],spheres_3d[2],spheres_3d[3], spheres_center_3d[0], mark_pcd.paint_uniform_color([1, 0, 0]), inlier_pcd_correct.paint_uniform_color([0, 1, 0]),]) # mark 顺序按照 左上,右上,左下,右下 # 每个mark的角点按照左、上、右、底顺序返回 return marks_3d_conners def cal_marks_3d_conners_simple(marks_pcd_4, show=False): marks_3d_conners = [] for i in range(4): # mark_ply_file_path = './detect_cloud/' + str(i) + '.ply' mark_ply_file_path = conf_temp_cloud_path + str(i) + '.ply' # 计算标定板尺寸 # mark_ply_file_path = './detect_cloud/1.ply' # 不从文件读取 # mark_pcd = o3d.io.read_point_cloud(mark_ply_file_path) mark_pcd = marks_pcd_4[i] plane_model, inlier_cloud, inlier_pcd_correct = correct_mark_cloud(mark_pcd) # t_pcd = o2t(inlier_cloud) # _dir = dir(t_pcd) # for it in _dir: # print(it) # t_boundaries, mask = t_pcd.compute_boundary_points(0.05, 60) # boundaries = t2o(t_boundaries) # # 计算曲率 # curvatures = calculate_curvature(inlier_cloud) # threshold = np.percentile(curvatures, 95) # 使用95百分位数作为阈值 # corner_indices = np.where(curvatures > threshold)[0] # t_corner_pcd = inlier_cloud.select_by_index(corner_indices.tolist()) # t_corner_pcd.paint_uniform_color([0, 0, 1]) # 将角点涂成红色 used_pcd = inlier_cloud # inlier_cloud / inlier_pcd_correct # inlier_cloud -> box # inlier_pcd_correct -> box2\box3 pcd_conners_2d = select_conners_from_mark_cloud_2d(used_pcd, plane_model) pcd_conners_2d_correct, center_point = correct_mask_conner(used_pcd, pcd_conners_2d) Log.info("修正后2D标记角点,得到得边长为:") for i in range(4): print(np.linalg.norm(pcd_conners_2d_correct.points[i] - pcd_conners_2d_correct.points[(i+1)%4])) # pcd_conners_3d = select_conners_from_mark_cloud_3d(inlier_pcd_correct) marks_3d_conners.append(pcd_conners_2d_correct) # pcd_conners_2d / pcd_conners_2d_correct # print(marks_3d_conners[0].points) # print(marks_3d_conners[0].points[0]) # print(np.linalg.norm(marks_3d_conners[0].points[0] - marks_3d_conners[0].points[1])) # print(np.linalg.norm(marks_3d_conners[0].points[1] - marks_3d_conners[0].points[2])) # print(np.linalg.norm(marks_3d_conners[0].points[2] - marks_3d_conners[0].points[3])) # print(np.linalg.norm(marks_3d_conners[0].points[3] - marks_3d_conners[0].points[0])) # Log.info("3D->3D 通过相邻角点计算得到的边长进行对比") if show: # 角点显示的圆球半径,单位(米) r = 0.006 sizes = [r,r,r,r] spheres_2d = create_spheres(pcd_conners_2d.points, sizes, color=[0, 0.706, 1]) spheres_3d = create_spheres(pcd_conners_2d_correct.points, sizes, color=[0, 0, 0]) spheres_center_3d = create_spheres([center_point], [0.01], color=[0, 1, 1]) o3d.visualization.draw_geometries([ spheres_2d[0],spheres_2d[1],spheres_2d[2],spheres_2d[3], spheres_3d[0],spheres_3d[1],spheres_3d[2],spheres_3d[3], spheres_center_3d[0], mark_pcd.paint_uniform_color([1, 0, 0]), inlier_pcd_correct.paint_uniform_color([0, 1, 0]),]) # mark 顺序按照 左上,右上,左下,右下 # 每个mark的角点按照左、上、右、底顺序返回 return marks_3d_conners def print_diff(aurco_pt1, aurco_pt2, lidar_pt1, lidar_pt2): aurco_distance = np.linalg.norm(aurco_pt1 - aurco_pt2) lidar_distance = np.linalg.norm(lidar_pt1 - lidar_pt2) print(aurco_distance) print(lidar_distance) print(abs(aurco_distance - lidar_distance)) # 测试2d角点分割算法 def _test_select_conners_from_mark_cloud_2d_2(): pass if __name__ == '__main__': marks_3d_conners = [] ply_file_path = os.path.dirname(__file__) + "\\samples\\calibration\\11\\output.ply" marks_pcd = seg_mark_cloud_simple(ply_file_path, kk) for i in range(4): mark_ply_file_path = conf_temp_cloud_path + str(i) + '.ply' mark_pcd = o3d.io.read_point_cloud(mark_ply_file_path) plane_model, inlier_cloud, inlier_pcd_correct = correct_mark_cloud(mark_pcd) # pcd_conners_2d = select_conners_from_mark_cloud_2d(mark_pcd, plane_model) pcd_conners_2d = select_conners_from_mark_cloud_3d_v2(inlier_pcd_correct) # image_project = cv2.imread("../caowei/new/output.png") # # if image_project is None: # # print("image_project is None!") # # exit() # # # 分割标定板 # ply_file_path = '../caowei/new/20241227175529496.ply' # marks_pcd = seg_mark_cloud(ply_file_path, kk) # if len(marks_pcd) != MARK_COUNT: # exit() # LEFT = 0 # TOP = 1 # RIGHT = 2 # BOTTOM = 3 # frame, marks_aurco = aurco_3d_detect.cal_marks_3d_conners("../caowei/new/output.png") # marks_pcd = cal_marks_3d_conners() # marks_pcd_new = [] # # marks_pcd_new.append(marks_pcd[2].points) # # marks_pcd_new.append(marks_pcd[3].points) # # marks_pcd_new.append(marks_pcd[1].points) # # marks_pcd_new.append(marks_pcd[0].points) # tmmp = [] # for i in range(4): # tmmp.append(marks_pcd[2].points[i]) # marks_pcd_new.append(tmmp) # tmmp = [] # for i in range(4): # tmmp.append(marks_pcd[3].points[i]) # marks_pcd_new.append(tmmp) # tmmp = [] # for i in range(4): # tmmp.append(marks_pcd[1].points[i]) # marks_pcd_new.append(tmmp) # tmmp = [] # for i in range(4): # tmmp.append(marks_pcd[0].points[i]) # marks_pcd_new.append(tmmp) # print("======== 0 left - 1 right =========") # print_diff(marks_aurco[0][LEFT] , marks_aurco[1][RIGHT], marks_pcd_new[0][LEFT], marks_pcd_new[1][RIGHT]) # print("======== 0 left - 2 right =========") # print_diff(marks_aurco[0][LEFT] , marks_aurco[2][RIGHT], marks_pcd_new[0][LEFT], marks_pcd_new[2][RIGHT]) # print("======== 0 left - 3 right =========") # print_diff(marks_aurco[0][LEFT] , marks_aurco[3][RIGHT], marks_pcd_new[0][LEFT], marks_pcd_new[3][RIGHT]) # print("======== 0 right - 1 left =========") # print_diff(marks_aurco[0][RIGHT] , marks_aurco[1][LEFT], marks_pcd_new[0][RIGHT] , marks_pcd_new[1][LEFT]) # print("======== 0 top - 3 bottom =========") # print_diff(marks_aurco[0][TOP] , marks_aurco[3][BOTTOM], marks_pcd_new[0][TOP] , marks_pcd_new[3][BOTTOM]) # print("======== 2 left - 1 right =========") # print_diff(marks_aurco[2][LEFT] , marks_aurco[1][RIGHT], marks_pcd_new[2][LEFT] , marks_pcd_new[1][RIGHT]) # print("======== 2 left - 3 right =========") # print_diff(marks_aurco[2][LEFT] , marks_aurco[3][RIGHT], marks_pcd_new[2][LEFT] , marks_pcd_new[3][RIGHT]) # exit()