1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564 |
- import cv2
- import os
- from tqdm import tqdm
- import numpy as np
- import scipy.io as sio
- import matplotlib.pyplot as plt
- from scipy.interpolate import griddata
- import glob
- from scipy import stats
- from scipy.optimize import minimize
- from collections import defaultdict
- from scipy.signal import savgol_filter
- from scipy.ndimage import uniform_filter, gaussian_filter, median_filter
- import json
- import pickle
- import shutil
- import pandas as pd
- from sklearn.linear_model import LinearRegression
- from scipy.interpolate import Rbf
- from numpy.linalg import lstsq
- from scipy.optimize import curve_fit
- def read_and_undis(img_path, mtx, dist):
- img = cv2.imread(img_path)
- dst = cv2.undistort(img, mtx, dist, None, mtx)
- gray = cv2.cvtColor(dst, cv2.COLOR_BGR2GRAY)
- return gray
- def write_point_cloud(file_path, x, y, z):
- # 将 x, y, z 列组合成一个二维数组
- point_cloud = np.column_stack((x, y, z))
- # import pdb; pdb.set_trace()
- # 将数组写入文本文件,使用空格作为分隔符
- np.savetxt(file_path, point_cloud, fmt='%.10f', delimiter=',')
- print(f"Point cloud saved to {file_path}")
- def get_baseline_params(filename):
- world_raw = sio.loadmat(filename)
- return world_raw
- def binarize(image, threshold=85):
- # Read and binarize the image
- # image = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
- _, binary_image = cv2.threshold(image, threshold, 255, cv2.THRESH_BINARY)
- num_labels, labels, stats, centroids = cv2.connectedComponentsWithStats(binary_image, connectivity=8)
- # We assume that the largest connected component is the object of interest (the large circle)
- # Identify the label with the largest area, ignoring the background label (label 0)
- largest_label = 1 + np.argmax(stats[1:, cv2.CC_STAT_AREA])
- # Create a mask for the largest connected component
- filtered_image = np.zeros_like(binary_image)
- filtered_image[labels == largest_label] = 255
- return filtered_image
- def get_ellipse_points(binary, contours, grid_spacing, ellipse_center, ellipse_axes, angle_rad):
- image_color = cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR)
- grid_points = []
-
- h, w = binary.shape[:2]
- y_indices, x_indices = np.mgrid[0:h:grid_spacing, 0:w:grid_spacing]
- # 将坐标转换为椭圆坐标系
- x_shifted = x_indices - ellipse_center[0]
- y_shifted = y_indices - ellipse_center[1]
- # 旋转坐标系
- cos_angle = np.cos(angle_rad)
- sin_angle = np.sin(angle_rad)
- x_rotated = x_shifted * cos_angle + y_shifted * sin_angle
- y_rotated = -x_shifted * sin_angle + y_shifted * cos_angle
- # 判断每个点是否在椭圆内部或边界上
- ellipse_eq = (x_rotated**2 / ellipse_axes[0]**2) + (y_rotated**2 / ellipse_axes[1]**2)
- inside_ellipse = ellipse_eq < 1
- # 记录并绘制在椭圆内部或边界上的点
- grid_points = []
- contour_points = []
- count = 0
- for i in range(len(contours)):
- for point in contours[i]:
- # import pdb; pdb.set_trace()
- count += 1
- assert point.shape == (1, 2), point.shape
- if count == 400:
- cv2.circle(image_color, point[0], 1, (0, 255, 0), -1)
- cv2.putText(image_color, str(point[0]), (point[0][0], point[0][1]), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (0, 255, 0), 2)
- else:
- cv2.circle(image_color, point[0], 1, (0, 0, 255), -1)
- contour_points.append(point[0].tolist())
-
- return grid_points, contour_points, image_color
- def fit_circle(contour_points, grid_spacing):
- z_contour_points = contour_points[:, 2]
- points_float = np.array(contour_points[:, :2], dtype=np.float32)
- # import pdb; pdb.set_trace()
- (center, radius) = cv2.minEnclosingCircle(points_float)
- print(f"Fitted Circle Center: {center}")
- print(f"Fitted Circle Radius: {radius}")
- # Create a grid of points that covers the bounding box of the circle
- x_min = int(center[0] - radius)
- x_max = int(center[0] + radius)
- y_min = int(center[1] - radius)
- y_max = int(center[1] + radius)
- # Generate grid points within the bounding box
- x_vals = np.arange(x_min, x_max, grid_spacing)
- y_vals = np.arange(y_min, y_max, grid_spacing)
- x_grid, y_grid = np.meshgrid(x_vals, y_vals)
- grid_points = np.vstack([x_grid.ravel(), y_grid.ravel()]).T
- # Calculate the squared distances of the grid points from the center
- distances_squared = (grid_points[:, 0] - center[0])**2 + (grid_points[:, 1] - center[1])**2
- # Filter the points to keep only those within the circle
- inside_circle = distances_squared <= radius**2
- grid_points_inside_circle = grid_points[inside_circle]
- z_col = np.full((len(grid_points_inside_circle), 1), z_contour_points[0])
- return np.column_stack((grid_points_inside_circle, z_col))
- def fit_sector(contour_points, grid_spacing, erosion_pixels):
- z_contour_points = contour_points[:, 2]
- # 提取 xy 平面上的轮廓点
- contour_xy = contour_points[:, :2]
- # 确保 contour_xy 是 numpy 数组
- contour_xy = np.array(contour_xy, dtype=np.int32)
-
- # 获取轮廓的边界框
- x, y, w, h = cv2.boundingRect(contour_xy)
- # 生成 xy 平面上的网格点
- xx, yy = np.meshgrid(np.arange(x, x + w, grid_spacing), np.arange(y, y + h, grid_spacing))
- grid_points_xy = np.vstack([xx.ravel(), yy.ravel()]).T
-
- # 过滤出轮廓内部的点,并记录它们的索引
- inside_points_xy = []
- inside_indices = []
- for idx, point in enumerate(grid_points_xy):
- # 使用cv2.pointPolygonTest判断点是否在轮廓内部
- if cv2.pointPolygonTest(contour_xy, (float(point[0]), float(point[1])), False) >= 0:
- inside_points_xy.append(point)
- inside_indices.append(idx)
- inside_points_xy = np.array(inside_points_xy)
- # 使用过滤后的索引获得网格点,并为每个点设置 z 值
- grid_points_inside_sector = grid_points_xy[inside_indices]
-
- z_col = np.full((len(grid_points_inside_sector), 1), z_contour_points[0])
-
- # 处理负坐标,将所有坐标平移至非负区域
- min_x, min_y = np.min(contour_xy, axis=0) - 10
- offset = np.array([abs(min(min_x, 0)), abs(min(min_y, 0))])
- shifted_contour = contour_xy + offset # 平移后的轮廓点
- # 获取轮廓的边界框
- x, y, w, h = cv2.boundingRect(shifted_contour)
- # 创建一幅足够大的二值图像来绘制轮廓
- img = np.zeros((y + h + erosion_pixels * 2, x + w + erosion_pixels * 2), dtype=np.uint8)
- # 绘制原始轮廓到二值图像上
- cv2.drawContours(img, [shifted_contour], -1, (255), thickness=cv2.FILLED)
- # 创建腐蚀核并进行腐蚀操作
- kernel = np.ones((erosion_pixels, erosion_pixels), np.uint8)
- eroded_img = cv2.erode(img, kernel, iterations=1)
- # 提取腐蚀后的轮廓
- contours, _ = cv2.findContours(eroded_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
- if len(contours) == 0:
- raise ValueError("腐蚀后没有找到轮廓,请检查腐蚀参数。")
- eroded_contour = contours[0] - offset # 将腐蚀后的轮廓平移回原始坐标系
- # 生成 xy 平面上的网格点
- xx, yy = np.meshgrid(np.arange(x - erosion_pixels, x + w + erosion_pixels, grid_spacing),
- np.arange(y - erosion_pixels, y + h + erosion_pixels, grid_spacing))
- grid_points_xy = np.vstack([xx.ravel(), yy.ravel()]).T - offset # 还原回原坐标系
- # 过滤出介于原轮廓和新轮廓之间的点
- between_points = []
- for point in grid_points_xy:
- # 使用 cv2.pointPolygonTest 判断点是否在原轮廓和腐蚀后的轮廓之间
- in_original_contour = cv2.pointPolygonTest(contour_xy, (float(point[0]), float(point[1])), False) >= 0
- in_eroded_contour = cv2.pointPolygonTest(eroded_contour, (float(point[0]), float(point[1])), False) >= 0
- if in_original_contour and not in_eroded_contour:
- between_points.append(point)
- between_points = np.array(between_points)
- return np.column_stack((grid_points_inside_sector, z_col)), np.column_stack((between_points, np.full((len(between_points), 1), z_contour_points[0])))
- def get_meshgrid_contour(binary, save_path, debug=False):
- contours, _ = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
- max_index, max_contour = max(enumerate(contours), key=lambda x: cv2.boundingRect(x[1])[2] * cv2.boundingRect(x[1])[3])
- output_image = cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR) # 转换为彩色图像用于绘制
- if debug:
- cv2.drawContours(output_image, [max_contour], -1, (255, 255, 0), thickness=2)
- cv2.namedWindow('output_image', 0)
- cv2.imshow('output_image', output_image)
- cv2.waitKey(0)
- cv2.destroyAllWindows()
- #print('max_contour = ',max_contour[0])
- max_contour_lst = []
- for pt in max_contour:
- #print('[pt[0][1],pt[0][0]] = ', [int(pt[0][1]), int(pt[0][0])])
- #print('pt[0].tolist() =', pt[0].tolist())
- #max_contour_lst.append([int(pt[0][1]), int(pt[0][0])])
- max_contour_lst.append(pt[0].tolist())
- #print('max_contour_lst = ',max_contour_lst)
- max_contour_array = np.array(max_contour_lst)
- return max_contour_array
- def get_meshgrid(binary, grid_spacing, save_path, debug=False):
- # img_path = 'imgs/temp/white.bmp'
- # image = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
- # binary = binarize(image)
- # Detect contours
- contours, _ = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
- # 初始化最大范围的变量
- x_min, y_min = float('inf'), float('inf')
- x_max, y_max = 0, 0
- # 计算所有轮廓的最大范围
- for contour in contours:
- x, y, w, h = cv2.boundingRect(contour)
- x_min = min(x_min, x)
- y_min = min(y_min, y)
- x_max = max(x_max, x + w)
- y_max = max(y_max, y + h)
- # 存储采样点的列表
- sampled_points = []
- # 在最大边界框范围内进行采样,步长为 grid_spacing
- for i in range(x_min, x_max, grid_spacing):
- for j in range(y_min, y_max, grid_spacing):
- # 遍历所有轮廓,检查该点是否在任意轮廓内部
- for contour in contours:
- if cv2.pointPolygonTest(contour, (i, j), False) >= 0:
- sampled_points.append((i, j))
- break # 一旦找到在某个轮廓内,就不需要再检查其他轮廓了
- #print('sampled_points ',sampled_points)
- grid_points = np.array((sampled_points))
- grid_points = grid_points[:, ::-1]
- # 将采样点显示在图像上
- output_image = cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR) # 转换为彩色图像用于绘制
- for point in sampled_points:
- cv2.circle(output_image, point, 2, (0, 0, 255), -1) # 画小圆点
- # 显示结果
- cv2.namedWindow('Sampled Points', 0)
- cv2.imshow("Sampled Points", output_image)
- cv2.waitKey(0)
- cv2.destroyAllWindows()
- return grid_points
- # # Initialize ellipse
- # ellipse = None
- # # Check and fit an ellipse
- # if contours:
- # sorted_contours = sorted(contours, key=cv2.contourArea, reverse=True)
- # # import pdb; pdb.set_trace()
- # for contour in sorted_contours:
- # if len(contour) >= 5: # Need at least 5 points to fit an ellipse
- # ellipse = cv2.fitEllipse(contour)
- # break
- # else:
- # print("没有足够的轮廓点来拟合椭圆")
- # else:
- # print("没有检测到足够的轮廓或轮廓点太少")
- # print('ellipse = ', ellipse)
- # # Continue with your grid generation and other tasks if ellipse was successfully fitted
- # if ellipse:
- # # Convert binary image to BGR for color drawing
- # grid_points = []
- # ellipse_center = ellipse[0]
- # ellipse_axes = (ellipse[1][0] / 2, ellipse[1][1] / 2) # Dividing axes by 2 to get semi-axes
- # angle_rad = np.deg2rad(ellipse[2])
- # # Coordinate of the grid points
- # grid_points, contour_points, image_color = get_ellipse_points(binary, contours, grid_spacing, ellipse_center, ellipse_axes, angle_rad)
- # else:
- # raise ValueError("未能拟合椭圆")
- # cv2.namedWindow('meshgrid', )
- # cv2.imshow('meshgrid', binary)
- # cv2.waitKey(0)
- # cv2.destroyAllWindows()
- # if debug:
- # # print('There are {} points in the ellipse'.format(len(grid_points)))
- # # print('Mean: {}'.format(np.mean(np.array(grid_points))))
- # print('There are {} points on the ellipse'.format(len(contour_points)))
- # print('Mean: {}'.format(np.mean(np.array(contour_points))))
- # cv2.imwrite(os.path.join(save_path, "meshgrid.png"), image_color)
- # cv2.imshow('meshgrid', image_color)
- # cv2.waitKey(0)
- # cv2.destroyAllWindows()
-
- # # grid_points = np.array(grid_points)
- # contour_points = np.array(contour_points)
- # # print('x:', grid_points[:, 0].max()-grid_points[:, 0].min())
- # # print('y:', grid_points[:, 1].max()-grid_points[:, 1].min())
- # return grid_points, contour_points
- # # return np.fliplr(grid_points), np.fliplr(contour_points)
- def affine_img2world(grid_points, A, Rc2w, Tc2w, d):
- # 计算交点
- intersection_points_world = []
- for point in grid_points:
- x_img, y_img = point[0], point[1]
- ray_camera_coordinate = np.array([
- (x_img - A[0, 2]) / A[0, 0],
- (y_img - A[1, 2]) / A[1, 1],
- 1
- ])
- ray_camera_coordinate /= np.linalg.norm(ray_camera_coordinate) # 归一化
- ray_world_coordinate = Rc2w @ ray_camera_coordinate
- cameraCenter_world_coordinate = Tc2w
- # 计算世界坐标系中的交点
- t = (-d - cameraCenter_world_coordinate[2]) / ray_world_coordinate[2]
- intersection_point = cameraCenter_world_coordinate.T + t * ray_world_coordinate
- intersection_points_world.append(intersection_point)
- # 输出世界坐标系中的交点
- intersection_points_world = np.array(intersection_points_world)
- intersection_points_world = intersection_points_world.reshape(-1, 3)
- # x_w_data = np.column_stack((grid_points, intersection_points_world[:, 0]))
- # y_w_data = np.column_stack((grid_points, intersection_points_world[:, 1]))
- # z_w_data = np.column_stack((grid_points, intersection_points_world[:, 2]))
- # return [x_w_data, y_w_data, z_w_data]
- return intersection_points_world
- def get_world_points(contour_points, world_mat_contents, cam_id, grid_spacing, d, erosion_pixels, debug=False):
-
- A = world_mat_contents['camera_matrix']
- Rw2c = world_mat_contents['rotation_matrix']
- Rc2w = np.linalg.inv(Rw2c)
- Tw2c = world_mat_contents['translation_vector']
- Tc2w = -np.dot(np.linalg.inv(Rw2c), Tw2c)
- world_points_contours = affine_img2world(contour_points, A, Rc2w, Tc2w, d)
-
- world_points_fit, world_points_boundary = fit_sector(world_points_contours, grid_spacing, erosion_pixels)
- _, world_points_boundary_3 = fit_sector(world_points_contours, grid_spacing, 3)
- # assert np.abs(world_contour_points[:, 0].max()-world_contour_points[:, 0].min()-(world_contour_points[:, 1].max()-world_contour_points[:, 1].min())) < 1
- # import pdb; pdb.set_trace()
- if debug:
- # print('world x:', world_points_fit[:, 0].max()-world_points_fit[:, 0].min())
- # print('world y:', world_points_fit[:, 1].max()-world_points_fit[:, 1].min())
- #print('world contour x:', world_contour_points[:, 0].max()-world_contour_points[:, 0].min())
- #print('world contour y:', world_contour_points[:, 1].max()-world_contour_points[:, 1].min())
- plt.figure()
-
- plt.scatter(world_points_fit[:, 0], world_points_fit[:, 1], color='red', alpha=0.5)
- plt.scatter(world_points_boundary[:, 0], world_points_boundary[:, 1], color='green', alpha=0.5)
- plt.title('world points fit')
- plt.xlabel('X Axis')
- plt.ylabel('Y Axis')
- # 调整子图之间的间距
- plt.tight_layout()
- # 显示图像
- plt.show()
- # import pdb; pdb.set_trace()
- # return np.concatenate((world_points_fit, world_contour_points), axis=0)
- return world_points_fit, world_points_boundary, world_points_boundary_3
- def get_camera_points(world_points, world_mat_contents, save_path, cam_id, debug=False):
-
- x_w_data, y_w_data, z_w_data = world_points[:, 0], world_points[:, 1], world_points[:, 2]
- A = world_mat_contents['camera_matrix']
- Tw2c = world_mat_contents['translation_vector']
- Rw2c = world_mat_contents['rotation_matrix']
- Rc2w = np.linalg.inv(Rw2c)
- Tc2w = -np.dot(Rc2w, Tw2c)
- # 初始化数据数组
- x_c_array, y_c_array, z_c_array, u_p_array, v_p_array = [], [], [], [], []
- # 确保x_w, y_w, z_w具有相同形状
- assert x_w_data.shape == y_w_data.shape == z_w_data.shape
- #print(x_w_data.shape)
- for i in range(x_w_data.shape[0]):
- x_w, y_w, z_w = x_w_data[i], y_w_data[i], z_w_data[i]
- world_point_now = np.array([x_w, y_w, z_w])
- picture_point_now = A @ (Rw2c @ world_point_now[:, np.newaxis] + Tw2c)
- picture_point_now /= picture_point_now[2]
- u_p = np.round(picture_point_now[0]).item() # 使用.item()将单元素数组转换为标量
- v_p = np.round(picture_point_now[1]).item()
- # 计算相机坐标
- x_c, y_c, z_c = Tc2w.ravel()
- x_c_array.append(x_c)
- y_c_array.append(y_c)
- z_c_array.append(z_c)
- u_p_array.append(u_p)
- v_p_array.append(v_p)
-
- x_c_array, y_c_array, z_c_array, u_p_array, v_p_array = np.array(x_c_array), np.array(y_c_array), np.array(z_c_array), np.array(u_p_array), np.array(v_p_array)
- camera_points = np.column_stack((x_c_array, y_c_array, z_c_array))
- if debug:
- print('u_p max min:', u_p_array.max(),u_p_array.min())
- print('v_p max min:', v_p_array.max(), v_p_array.min())
- print('u_p:', u_p_array.max()-u_p_array.min())
- print('v_p:', v_p_array.max()-v_p_array.min())
- plt.figure()
- plt.scatter(u_p_array, v_p_array, alpha=0.5)
- plt.title('camera points')
- plt.xlabel('X Axis')
- plt.ylabel('Y Axis')
- # 调整子图之间的间距
- plt.tight_layout()
- #plt.savefig(os.path.join(save_path, str(cam_id) + "_camera_points.png"))
- # 显示图像
- plt.show()
- return camera_points, u_p_array, v_p_array
- def get_screen_points(point_data, x_phase_unwrapped, y_phase_unwrapped, screen_params, screen_to_world, cfg, save_path, cam_id, debug=False):
-
- # x_phase_unwrapped = x_phase_unwrapped.T
- # y_phase_unwrapped = y_phase_unwrapped.T
- # import pdb; pdb.set_trace()
- screenPixelSize = np.array(cfg['screenPixelSize'])
- Axis_conversion = np.array(cfg['Axis_conversion'][cam_id])
- screenorigin_point = np.array(cfg['screenorigin_point'])
- # 提取旋转矩阵和平移向量
- Rs2w = screen_to_world['screen_to_world_rotation']
- Ts2w = screen_to_world['screen_to_world_translation']
- # 计算 x_phase_unwrapped 中所有非 NaN 值的平均值
- average_x_phase_unwrapped = np.nanmean(x_phase_unwrapped)
- average_y_phase_unwrapped = np.nanmean(y_phase_unwrapped)
- # import pdb; pdb.set_trace()
- # 计算 value_map 并找到最小值
- value_map = np.abs(x_phase_unwrapped - average_x_phase_unwrapped) + np.abs(y_phase_unwrapped - average_y_phase_unwrapped)
- min_index = np.unravel_index(np.nanargmin(value_map), value_map.shape)
-
- # import pdb; pdb.set_trace()
- # 选定一个中心相位值作为绝对相位值
- # abs_phasepoint_picture = np.array(min_index)
- abs_phasepoint_x = x_phase_unwrapped[min_index]
- abs_phasepoint_y = y_phase_unwrapped[min_index]
- abs_phasepoint = np.array([abs_phasepoint_x, abs_phasepoint_y])
- # print('----------------------------------------')
- # print("abs_phasepoint", np.mean(np.isnan(abs_phasepoint)))
- # 根据相位值求解在屏幕上的像素点位置
- abs_phasepoint_screen = (abs_phasepoint / (2 * np.pi)) * (screenPixelSize/ cfg['k'])
- # print("abs_phasepoint_screen", np.mean(np.isnan(abs_phasepoint_screen)))
- # 利用相对位置信息确定绝对相位点的世界坐标系位置
- arrow_abs_mm = np.array([Axis_conversion[0] * cfg['pixelSize_mm'] * (abs_phasepoint_screen[0] - screenorigin_point[0]),
- Axis_conversion[1] * cfg['pixelSize_mm'] * (abs_phasepoint_screen[1] - screenorigin_point[1]),
- 0])
- abs_phasepoint_world = np.dot(Rs2w, arrow_abs_mm) + Ts2w.T
-
- # print('abs_phasepoint 绝对相位值:')
- # print(abs_phasepoint)
- # print('abs_phasepoint_world 绝对相位值对应的世界坐标系的位置:')
- # print(abs_phasepoint_world)
- # print('----------------------------------------')
- x_data, y_data, z_data = [], [], []
- for idx in range(len(point_data['u_p'])):
- # i, j, u_now = point_data['u_p'][idx]
- u_now = point_data['u_p'][idx]
- v_now = point_data['v_p'][idx]
- u_now, v_now = int(u_now), int(v_now)
- #print('x_phase_unwrapped shape = ',x_phase_unwrapped.shape, y_phase_unwrapped.shape)
- #print('v_now, u_now = ', v_now, u_now)
-
- phase_x_now = x_phase_unwrapped[v_now, u_now] # 注意这里的索引要按照Python的行列顺序,与MATLAB相反 raw
- phase_y_now = y_phase_unwrapped[v_now, u_now]
- #phase_x_now = x_phase_unwrapped[u_now, v_now]
- #phase_y_now = y_phase_unwrapped[u_now, v_now]
- # import pdb; pdb.set_trace()
- derta_phase_x = phase_x_now - abs_phasepoint[0]
- derta_phase_y = phase_y_now - abs_phasepoint[1]
- #assert not np.isnan(derta_phase_x).any(), "derta_phase_x"
- #assert not np.isnan(derta_phase_y).any(), "derta_phase_y"
- derta_mm = (np.array([derta_phase_x, derta_phase_y]) / (2 * np.pi)) * (screenPixelSize / cfg['k']) * cfg['pixelSize_mm'] * Axis_conversion
- #assert not np.isnan(derta_mm).any(), "derta_mm"
- arrow_mm = np.array([derta_mm[0], derta_mm[1], 0]) + arrow_abs_mm
- #assert not np.isnan(arrow_mm).any(), "arrow_mm"
- screen_point_now = Rs2w.dot(arrow_mm) + Ts2w.T
- # import pdb; pdb.set_trace()
- x_data.append([screen_point_now[0]])
- y_data.append([screen_point_now[1]])
- z_data.append([screen_point_now[2]])
- x_data = np.array(x_data)
- y_data = np.array(y_data)
- z_data = np.array(z_data)
- screen_points = np.column_stack((x_data, y_data, z_data))
- if debug:
- print(" "*100)
- print('*'*100)
- print('screen x:', np.nanmax(x_data), np.nanmin(x_data), np.nanmax(x_data) - np.nanmin(x_data))
- print('screen y:', np.nanmax(y_data), np.nanmin(y_data), np.nanmax(y_data)-np.nanmin(y_data))
- print('screen z:', np.nanmax(z_data), np.nanmin(z_data), np.nanmax(z_data)-np.nanmin(z_data))
- print('*'*100)
- plt.figure()
- plt.scatter(x_data, y_data, alpha=0.5)
- plt.title('screen points')
- plt.xlabel('X Axis')
- plt.ylabel('Y Axis')
- # 调整子图之间的间距
- plt.tight_layout()
- plt.savefig(os.path.join(save_path, str(cam_id) + "_screen_points.png"))
- # 显示图像
- plt.show()
- # import pdb; pdb.set_trace()
- return screen_points
- def get_white_mask(white_path, bin_thresh=15, debug=False):
- gray = cv2.imread(white_path, 0)
-
- mask = np.zeros_like(gray)
-
- _, thresh_img = cv2.threshold(gray, bin_thresh, 255, cv2.THRESH_BINARY)
- kernel = np.ones((8, 2), np.uint8) # 垂直核大小为 5x1,表示垂直方向腐蚀
- erode_thresh_img = cv2.erode(thresh_img, kernel, iterations=1)
- img_height, img_width = erode_thresh_img.shape[:2]
-
-
- contours, _ = cv2.findContours(erode_thresh_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
- valid_contours = [
- contour for contour in contours
- if cv2.boundingRect(contour)[2] < 0.8*img_width and cv2.boundingRect(contour)[3] < 0.8 * img_height
- ]
- # 确保有有效的轮廓
- if valid_contours:
- max_index, max_contour = max(enumerate(valid_contours), key=lambda x: cv2.boundingRect(x[1])[2] * cv2.boundingRect(x[1])[3])
- cv2.drawContours(mask, [max_contour], -1, (255, 255, 255), thickness=cv2.FILLED)
- if debug:
- cv2.namedWindow('erode_thresh_img',0)
- cv2.imshow('erode_thresh_img', erode_thresh_img)
- cv2.waitKey(0)
-
- cv2.namedWindow('mask',0)
- cv2.imshow('mask', mask)
- cv2.waitKey(0)
- return mask
- # def get_world_points_from_mask(binary_image, world_mat_contents, d, save_path, debug=False):
- # # Get the coordinates of non-zero pixels (points inside the mask)
- # mask_points = np.array(np.column_stack(np.where(binary_image > 0)))
- # # Print the number of sampled points
- # num_points = len(mask_points)
- # # print(f"Number of sampled points: {num_points}")
-
- # A = world_mat_contents['camera_matrix']
- # Rw2c = world_mat_contents['rotation_matrix']
- # Rc2w = np.linalg.inv(Rw2c)
- # Tw2c = world_mat_contents['translation_vector']
- # Tc2w = -np.dot(np.linalg.inv(Rw2c), Tw2c)
- # # import pdb; pdb.set_trace()
- # # world_points = affine_img2world(grid_points, A, Rc2w, Tc2w, d)
- # world_points = affine_img2world(mask_points, A, Rc2w, Tc2w, d)
- # if debug:
- # # Plot the original mask and sampled points
- # plt.scatter(world_points[:, 1], world_points[:, 0], color='red', s=1, label="Sampled Points")
- # plt.title(f'Sampled Points from Mask')
- # plt.legend()
- # plt.show()
- # return world_points
- def get_world_points_from_mask(grid_points, world_mat_contents, d, save_path, debug=False):
- # Get the coordinates of non-zero pixels (points inside the mask)
- #mask_points = np.array(np.column_stack(np.where(binary_image > 0)))
- # Print the number of sampled points
- # print(f"Number of sampled points: {num_points}")
-
- A = world_mat_contents['camera_matrix']
- Rw2c = world_mat_contents['rotation_matrix']
- Rc2w = np.linalg.inv(Rw2c)
- Tw2c = world_mat_contents['translation_vector']
- Tc2w = -np.dot(np.linalg.inv(Rw2c), Tw2c)
- # import pdb; pdb.set_trace()
- world_points = affine_img2world(grid_points, A, Rc2w, Tc2w, d)
- if debug:
- # Plot the original mask and sampled points
- plt.scatter(world_points[:, 1], world_points[:, 0], color='red', s=1, label="Sampled Points")
- plt.title(f'Sampled Points from Mask')
- plt.legend()
- plt.show()
- return world_points
- # 定义旋转矩阵 (例如:绕z轴旋转theta角)
- def rotation_matrix_z(theta):
- return np.array([
- [np.cos(theta), -np.sin(theta), 0],
- [np.sin(theta), np.cos(theta), 0],
- [0, 0, 1]
- ])
- # 定义平移向量
- def translation_vector(tx, ty, tz):
- return np.array([tx, ty, tz])
- # 对点云应用旋转和平移
- def transform_point_cloud(point_cloud, rotation_matrix, translation_vector):
- # 应用旋转
- rotated_point_cloud = np.dot(point_cloud, rotation_matrix.T) # 转置矩阵用于点的正确旋转
- # 应用平移
- translated_point_cloud = rotated_point_cloud + translation_vector
- return translated_point_cloud
- # 定义拟合圆的函数
- def calc_radius(x, y, xc, yc):
- """计算每个点到圆心的距离"""
- return np.sqrt((x - xc)**2 + (y - yc)**2)
- def fit_circle_post(x, y):
- """拟合一个圆并返回圆心和半径"""
- # 初始猜测圆心为数据的平均值
- x_m = np.mean(x)
- y_m = np.mean(y)
-
- def optimize_func(params):
- xc, yc = params
- radii = calc_radius(x, y, xc, yc)
- return np.var(radii) # 最小化半径的方差,使得所有点接近正圆
-
- result = minimize(optimize_func, [x_m, y_m])
- xc, yc = result.x
- radii = calc_radius(x, y, xc, yc)
- radius_mean = np.mean(radii)
- return xc, yc, radius_mean
- def remove_repeat_z(total_cloud_point):
- # 计算所有 z 的全局均值
- global_mean_z = np.mean(total_cloud_point[:, 2])
- # 使用字典来记录每个 (x, y) 对应的 z 值
- xy_dict = defaultdict(list)
- # 将数据分组存入字典,键为 (x, y),值为所有对应的 z 值
- for x_val, y_val, z_val in total_cloud_point:
- xy_dict[(x_val, y_val)].append(z_val)
- # 构建新的点云数据
- new_data = []
- # 对每个 (x, y),选择距离全局均值最近的 z 值
- for (x_val, y_val), z_vals in xy_dict.items():
- if len(z_vals) > 1:
- # 多个 z 值时,选择与全局均值最接近的 z
- closest_z = min(z_vals, key=lambda z: abs(z - global_mean_z))
- else:
- # 只有一个 z 值时,直接使用
- closest_z = z_vals[0]
-
- # 将结果保存为新的点
- new_data.append([x_val, y_val, closest_z])
- # 将新点云数据转化为 NumPy 数组
- new_data = np.array(new_data)
- return new_data
- def make_circle(data):
-
- # 计算所有 z 的全局均值
- global_mean_z = np.mean(data[:, 2])
- # Step 1: 确定最小外接圆
- # 使用 xy 坐标计算最小外接圆
- xy_vals = data[:, :2].astype(np.float32)
- (x_center, y_center), radius = cv2.minEnclosingCircle(xy_vals)
- # 获取最小外接圆的中心和半径
- x_center, y_center, radius = int(x_center), int(y_center), int(radius)
- # Step 2: 使用 IQR 进行异常 z 值检测和替换
- # 计算 z 的四分位数
- Q1 = np.percentile(data[:, 2], 25)
- Q3 = np.percentile(data[:, 2], 75)
- IQR = Q3 - Q1
- # 将异常的 z 值替换为均值
- #data[:, 2] = np.where((data[:, 2] < lower_bound) | (data[:, 2] > upper_bound), global_mean_z, data[:, 2])
- # Step 3: 填补最小外接圆内的空缺点
- # 创建一个用于存储已存在的 (x, y) 坐标的集合
- existing_xy_set = set((int(x), int(y)) for x, y in data[:, :2])
- # 遍历最小外接圆范围内的所有 (x, y) 坐标
- filled_data = []
- for x in range(x_center - radius, x_center + radius + 1):
- for y in range(y_center - radius, y_center + radius + 1):
- # 判断 (x, y) 是否在圆内
- if np.sqrt((x - x_center)**2 + (y - y_center)**2) <= radius:
- if (x, y) in existing_xy_set:
- # 如果 (x, y) 已存在,则保留原始数据
- z_val = data[(data[:, 0] == x) & (data[:, 1] == y), 2][0]
- else:
- # 如果 (x, y) 不存在,填充 z 的全局均值
- z_val = global_mean_z
- filled_data.append([x, y, z_val])
- # 转换为 NumPy 数组
- filled_data = np.array(filled_data)
- return filled_data
-
- def point_filter(data):
- data[:, 0] = np.round(data[:, 0]).astype(int)
- data[:, 1] = np.round(data[:, 1]).astype(int)
- # 构建平面网格,假设 grid_x 和 grid_y 已经生成
- grid_size = 100 # 网格大小根据你的数据情况设定
- grid_x, grid_y = np.meshgrid(np.linspace(min(data[:, 0]), max(data[:, 0]), grid_size),
- np.linspace(min(data[:, 1]), max(data[:, 1]), grid_size))
- # 插值,将 (x, y, z) 数据转换为平面网格
- grid_z = griddata((data[:, 0], data[:, 1]), data[:, 2], (grid_x, grid_y), method='linear')
- print('grid_x = ', grid_x)
- print('grid_y = ', grid_y)
- print('grid_z = ', grid_z)
- smoothed_z = uniform_filter(grid_z, size=5)
- print('smoothed_z range = ',np.max(smoothed_z) - np.min(smoothed_z))
- fig = plt.figure(figsize=(10, 8))
- ax = fig.add_subplot(111, projection='3d')
- ax.plot_surface(grid_x, grid_y, smoothed_z, cmap='viridis', alpha=0.7)
- plt.show()
- return smoothed_z
- # 定义高斯曲面方程
- def gaussian_surface(X, A, x0, y0, sigma):
- x, y = X
- return A * np.exp(-((x - x0)**2 + (y - y0)**2) / (2 * sigma**2))
- def curve_fit_gaussian(points):
- x = points[:, 0]
- y = points[:, 1]
- z = points[:, 2]
- # 使用curve_fit来拟合高斯曲面
- # 初始猜测参数 A, x0, y0, sigma
- initial_guess = [1, np.mean(x), np.mean(y), 1]
-
- # 使用 curve_fit 进行非线性最小二乘拟合
- popt, pcov = curve_fit(gaussian_surface, (x, y), z, p0=initial_guess)
-
- # 拟合的参数
- A, x0, y0, sigma = popt
- print(f"拟合参数: A={A}, x0={x0}, y0={y0}, sigma={sigma}")
-
- # 根据拟合的高斯曲面计算新的 z 值
- z_fitted = gaussian_surface((x, y), A, x0, y0, sigma)
-
- # 生成新的点云
- new_points = np.column_stack((x, y, z_fitted))
- # 可视化原始点云和拟合的高斯曲面
- # fig = plt.figure()
- # ax = fig.add_subplot(111, projection='3d')
- # # 绘制原始点云
- # #ax.scatter(x, y, z, c='r', label='Original Points')
- # # 绘制拟合的高斯曲面点云
- # ax.scatter(x, y, z_fitted, c='b', label='Fitted Gaussian Surface')
- # ax.set_title('3D Point Cloud and Fitted Gaussian Surface')
- # ax.set_xlabel('X')
- # ax.set_ylabel('Y')
- # ax.set_zlabel('Z')
- # ax.legend()
- # plt.show()
-
- return new_points
- def curve_fit_2(points):
- x = points[:,0]
- y = points[:,1]
- z = points[:,2]
- A = np.column_stack((x**2, y**2, x*y, x, y, np.ones_like(x)))
- # 使用最小二乘法拟合二次曲面的系数
- coefficients, _, _, _ = lstsq(A, z, rcond=None)
- # 拟合的系数
- a, b, c, d, e, f = coefficients
- print("拟合的系数:", coefficients)
- # 根据拟合的曲面方程计算新的 z 值
- z_fitted = a * x**2 + b * y**2 + c * x * y + d * x + e * y + f
- # 生成新的点云
- new_points = np.column_stack((x, y, z_fitted))
- # # 可视化原始点云和拟合的曲面
- fig = plt.figure()
- ax = fig.add_subplot(111, projection='3d')
- # 绘制原始点云
- #ax.scatter(x, y, z, c='r', label='Original Points')
- # 绘制拟合的曲面点云
- ax.scatter(x, y, z_fitted, c='b', label='Fitted Surface')
- ax.set_title('3D Point Cloud and Fitted Quadratic Surface')
- ax.set_xlabel('X')
- ax.set_ylabel('Y')
- ax.set_zlabel('Z')
- ax.legend()
- plt.show()
- return new_points
- def curve_fit_3(points):
- x = points[:,0]
- y = points[:,1]
- z = points[:,2]
- # 构建三次曲面的设计矩阵
- # 曲面模型:z = ax^3 + by^3 + cx^2y + dxy^2 + ex^2 + fy^2 + gxy + hx + iy + j
- A = np.column_stack((x**3, y**3, x**2*y, x*y**2, x**2, y**2, x*y, x, y, np.ones_like(x)))
- # 使用最小二乘法拟合三次曲面的系数
- coefficients, _, _, _ = lstsq(A, z, rcond=None)
- # 拟合的系数
- a, b, c, d, e, f, g, h, i, j = coefficients
- #print("拟合的系数:", coefficients)
- # 根据拟合的曲面方程计算新的 z 值
- z_fitted = a * x**3 + b * y**3 + c * x**2 * y + d * x * y**2 + e * x**2 + f * y**2 + g * x * y + h * x + i * y + j
- # 生成新的点云
- new_points = np.column_stack((x, y, z_fitted))
- # # # 可视化原始点云和拟合的曲面
- # fig = plt.figure()
- # ax = fig.add_subplot(111, projection='3d')
- # # 绘制原始点云
- # #ax.scatter(x, y, z, c='r', label='Original Points')
- # # 绘制拟合的曲面点云
- # ax.scatter(x, y, z_fitted, c='b', label='Fitted Surface')
- # ax.set_title('3D Point Cloud and Fitted Quadratic Surface')
- # ax.set_xlabel('X')
- # ax.set_ylabel('Y')
- # ax.set_zlabel('Z')
- # ax.legend()
- # plt.show()
- return new_points
- def remove_duplicates(points):
- # 使用字典来去重,键为 (x, y),值为 z
- unique_points = {}
-
- for point in points:
- x, y, z = point
-
- if (x, y) not in unique_points:
- unique_points[(x, y)] = z # 如果 x, y 坐标未出现过,保留该点
-
- # 将去重后的点云转换为 numpy 数组
- new_points = np.array([[x, y, z] for (x, y), z in unique_points.items()])
-
- return new_points
- def post_process(points, debug):
- uniq_points = remove_duplicates(points)
-
- x = uniq_points[:,0]
- y = uniq_points[:,1]
- z = uniq_points[:,2]
- close_point = remove_duplicate_points(points)
- smoothed_point = moving_average_filter_z(close_point, 20)
- fitted_points = curve_fit_gaussian(smoothed_point)
-
- return fitted_points
-
- plane_points = fit_plane_and_adjust(close_point, 10)
-
- z_outliers_removed = remove_outliers(smoothed_z, threshold=30.0)
- z_final = smooth_z_with_std(z_outliers_removed, 5)
-
- if debug:
- # 绘制3D点云
- fig = plt.figure()
- ax = fig.add_subplot(111, projection='3d')
- ax.scatter(x, y, z, c=z, cmap='viridis', marker='o', s=2)
- ax.set_title('3D Point Cloud with raw Points')
- fig = plt.figure()
- ax = fig.add_subplot(111, projection='3d')
- ax.scatter(x, y, smoothed_z, c=smoothed_z, cmap='viridis', marker='o', s=2)
- ax.set_title('3D Point Cloud with smoothed_z Points')
-
- fig = plt.figure()
- ax = fig.add_subplot(111, projection='3d')
- ax.scatter(x, y, z_outliers_removed, c=z_outliers_removed, cmap='viridis')
- ax.set_title('3D Point Cloud with remove_outliers Points')
- fig = plt.figure()
- ax = fig.add_subplot(111, projection='3d')
- ax.scatter(x, y, z_final, c=z_final, cmap='viridis')
- ax.set_title('3D Point Cloud with z_final Points')
- ax.set_zlim(0,10)
-
-
- plt.show()
- return z_final
-
- def plot_camera(ax, rotation_matrix, translation_vector, camera_id, scale=1.0, color='r'):
- """
- 绘制单个相机的三维表示:位置、方向和视野
- R: 3x3旋转矩阵 (从世界坐标系到相机坐标系)
- t: 3x1平移向量 (相机位置)
- scale: 控制相机大小的比例因子
- color: 相机视野的颜色
- """
- # 相机的中心
- camera_center = -rotation_matrix.T @ translation_vector
- print('camera_center = ', camera_center)
- camera_center = np.squeeze(camera_center) # 确保 camera_center 是 1D 数组
- #print('camera_center = ', camera_center)
- # 定义相机的四个角点在相机坐标系中的位置
- camera_points = np.array([
- [0, 0, 0], # 相机中心
- [1, 1, 2], # 视野的左上角
- [1, -1, 2], # 视野的左下角
- [-1, -1, 2], # 视野的右下角
- [-1, 1, 2] # 视野的右上角
- ]) * scale
- # 将相机坐标系下的点转换到世界坐标系
- camera_points_world = (rotation_matrix.T @ camera_points.T).T + camera_center.T
- # 绘制相机位置(红色圆点表示相机中心)
- ax.scatter(camera_center[0], camera_center[1], camera_center[2], color=color, marker='o', s=100)
-
- # 绘制相机的 X、Y、Z 坐标轴
- axis_length = scale * 3 # 坐标轴箭头长度
- x_axis = rotation_matrix[:, 0] # X 轴方向
- y_axis = rotation_matrix[:, 1] # Y 轴方向
- z_axis = rotation_matrix[:, 2] # Z 轴方向
- # X 轴箭头(红色)
- ax.quiver(camera_center[0], camera_center[1], camera_center[2],
- x_axis[0], x_axis[1], x_axis[2],
- length=axis_length, color='r', arrow_length_ratio=0.5, label='X axis')
- # Y 轴箭头(绿色)
- ax.quiver(camera_center[0], camera_center[1], camera_center[2],
- y_axis[0], y_axis[1], y_axis[2],
- length=axis_length, color='g', arrow_length_ratio=0.5, label='Y axis')
- # Z 轴箭头(蓝色)
- ax.quiver(camera_center[0], camera_center[1], camera_center[2],
- z_axis[0], z_axis[1], z_axis[2],
- length=axis_length, color='b', arrow_length_ratio=0.5, label='Z axis')
- # 绘制相机中心
- ax.scatter(camera_center[0], camera_center[1], camera_center[2], color=color, marker='o', s=100, label="Camera")
- ax.text(camera_center[0], camera_center[1], camera_center[2], f'Cam {camera_id}', color='black', fontsize=12)
- #绘制相机视野四个角点与相机中心的连线
- for i in range(1, 5):
- ax.plot([camera_center[0], camera_points_world[i, 0]],
- [camera_center[1], camera_points_world[i, 1]],
- [camera_center[2], camera_points_world[i, 2]], color=color)
- # 绘制相机的四边形视野
- ax.plot([camera_points_world[1, 0], camera_points_world[2, 0], camera_points_world[3, 0], camera_points_world[4, 0], camera_points_world[1, 0]],
- [camera_points_world[1, 1], camera_points_world[2, 1], camera_points_world[3, 1], camera_points_world[4, 1], camera_points_world[1, 1]],
- [camera_points_world[1, 2], camera_points_world[2, 2], camera_points_world[3, 2], camera_points_world[4, 2], camera_points_world[1, 2]], color=color)
- # # 添加相机方向箭头
- # ax.quiver(camera_center[0], camera_center[1], camera_center[2],
- # rotation_matrix[0, 2], rotation_matrix[1, 2], rotation_matrix[2, 2], length=scale, color='g', arrow_length_ratio=1, label="Direction")
- def transform_image_to_cam0(img, K_cam_i, R_cam_i, T_cam_i, K_cam0, R_cam0, T_cam0, depth_map):
- """
- 将相机 i 的图像转换到相机 0 的坐标系下,并重新投影到 cam0 的图像平面
- img: 相机 i 拍摄的图像 (H, W, 3)
- K_cam_i: 相机 i 的内参矩阵 (3, 3)
- R_cam_i: 相机 i 的旋转矩阵 (3, 3)
- T_cam_i: 相机 i 的平移向量 (3,)
- K_cam0: 相机 cam0 的内参矩阵 (3, 3)
- R_cam0: 相机 cam0 的旋转矩阵 (3, 3)
- T_cam0: 相机 cam0 的平移向量 (3,)
- depth_map: 相机 i 的深度图 (H, W) 或由立体视觉计算得到
- 返回:对齐到 cam0 的图像
- """
- H, W = img.shape[:2]
-
- # 生成像素坐标网格
- u, v = np.meshgrid(np.arange(W), np.arange(H))
-
- # 将图像坐标转换为齐次坐标
- img_pts = np.stack([u.ravel(), v.ravel(), np.ones_like(u).ravel()], axis=1).T # (3, N)
- # 反投影到三维点,计算相机 i 坐标系下的三维点
- depth_vals = depth_map.ravel()
- X_cam_i = np.linalg.inv(K_cam_i) @ (img_pts * depth_vals) # (3, N)
- # 将三维点从相机 i 坐标系转换到 cam0 坐标系
- R_inv_cam_i = np.linalg.inv(R_cam_i)
- X_cam0 = R_cam0 @ R_inv_cam_i @ (X_cam_i - T_cam_i.reshape(3, 1)) + T_cam0.reshape(3, 1)
- # 将三维点投影回 cam0 图像平面
- img_pts_cam0 = K_cam0 @ X_cam0
- img_pts_cam0 /= img_pts_cam0[2, :] # 归一化
-
- # 获取重新投影后的像素位置
- u_cam0 = img_pts_cam0[0, :].reshape(H, W).astype(np.int32)
- v_cam0 = img_pts_cam0[1, :].reshape(H, W).astype(np.int32)
-
- # 创建输出图像
- output_img = np.zeros_like(img)
-
- # 将原图像插值到新的像素位置
- mask = (u_cam0 >= 0) & (u_cam0 < W) & (v_cam0 >= 0) & (v_cam0 < H)
- output_img[v_cam0[mask], u_cam0[mask]] = img[v[mask], u[mask]]
- return output_img
- def camera_calibrate_vis():
- img_folder = 'D:\\data\\four_cam\\0927_storage\\20240927161124014'
- config_path = 'D:\\code\\pmd-python\\config\\cfg_3freq_wafer.json'
- cfg = json.load(open(config_path, 'r'))
- with open(os.path.join('D:\\code\\pmd-python\\config\\', cfg['cam_params']), 'rb') as pkl_file:
- cam_params = pickle.load(pkl_file)
- fig = plt.figure()
- ax = fig.add_subplot(111, projection='3d')
- # 绘制每个相机
- for ii in range(4):
- plot_camera(ax, cam_params[ii]['rotation_matrix'], cam_params[ii]['translation_vector'], ii, scale=40, color='r')
- # 绘制物体 (假设是一个简单的平面物体,位于 z = 0 平面上)
- plane_x, plane_y = np.meshgrid(np.linspace(-500, 500, 10), np.linspace(-500, 500, 10))
- plane_z = np.zeros_like(plane_x) # z = 0 的平面
- ax.plot_surface(plane_x, plane_y, plane_z, color='blue', alpha=0.3)
- # 设置轴的范围和标签
- ax.set_xlabel('X axis')
- ax.set_ylabel('Y axis')
- ax.set_zlabel('Z axis')
- ax.set_xlim([-50, 300])
- ax.set_ylim([-50, 300])
- ax.set_zlim([0, 500])
- K_cam0 = cam_params[0]['camera_matrix']
- R_cam0 = cam_params[0]['rotation_matrix']
- T_cam0 = cam_params[0]['translation_vector']
- for i in range(4):
- img_path = img_folder + '\\' + str(i) + '_frame_24.bmp'
- img = cv2.imread(img_path, 0)
- print('max gray = ', np.max(img))
- if i > 0:
- img = cv2.imread(img_path, 0)
- K_cam_i = cam_params[i]['camera_matrix']
- R_cam_i = cam_params[i]['rotation_matrix']
- T_cam_i = cam_params[i]['translation_vector']
- # 示例图像和深度图
- depth_map = img
- # 将图像转换到 cam0 坐标系下
- #aligned_img = transform_image_to_cam0(img, K_cam_i, R_cam_i, T_cam_i, K_cam0, R_cam0, T_cam0, depth_map)
-
- # cv2.namedWindow('Aligned Image', 0)
- # cv2.imshow('Aligned Image', aligned_img)
- # cv2.waitKey(0)
- # cv2.destroyAllWindows()
-
- plt.show()
- def rename_gamma():
- img_path = 'D:\\code\\Gamma\\Gamma\\cam3_original_pictures\\'
- i = 0
- for img in os.listdir(img_path):
- print(img)
- new_name = img_path + '3_frame_' + str(i) + '.bmp'
- i += 1
- print(new_name)
- shutil.move(img_path + img, new_name)
- # 函数:将点映射到拟合椭圆上的对应点
- def map_point_to_ellipse(contour_point, center, axes, angle):
- # 计算轮廓点相对于中心的偏移
- x_diff = contour_point[0][0] - center[0]
- y_diff = contour_point[0][1] - center[1]
- # 将轮廓点旋转至与椭圆一致的角度
- angle_rad = np.deg2rad(angle)
- x_rot = x_diff * np.cos(angle_rad) + y_diff * np.sin(angle_rad)
- y_rot = -x_diff * np.sin(angle_rad) + y_diff * np.cos(angle_rad)
- # 将点缩放到椭圆的长轴和短轴上
- x_ellipse = x_rot / axes[0]
- y_ellipse = y_rot / axes[1]
- # 映射点到椭圆上
- scaling_factor = 1 / np.sqrt(x_ellipse**2 + y_ellipse**2)
- x_ellipse_scaled = x_ellipse * scaling_factor * axes[0]
- y_ellipse_scaled = y_ellipse * scaling_factor * axes[1]
- # 旋转回原来的角度
- x_final = x_ellipse_scaled * np.cos(-angle_rad) + y_ellipse_scaled * np.sin(-angle_rad)
- y_final = -x_ellipse_scaled * np.sin(-angle_rad) + y_ellipse_scaled * np.cos(-angle_rad)
- # 返回映射后的椭圆点
- return (x_final + center[0], y_final + center[1])
- def find_best_circle(image, min_thresh, max_thresh):
- gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
- blurred = cv2.GaussianBlur(gray, (5, 5), 0)
- final_ellipse, final_thresh_img, final_contour = None, None, None
- final_error = 100
- final_bin_thresh = 100
- for bin_thresh in range(min_thresh, max_thresh, 5):
-
- _, thresh_img = cv2.threshold(blurred, bin_thresh, 255, cv2.THRESH_BINARY)
- contours, _ = cv2.findContours(thresh_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
- max_contour = max(contours, key=lambda contour: cv2.boundingRect(contour)[2] * cv2.boundingRect(contour)[3])
- ellipse = cv2.fitEllipse(max_contour)
- center = (int(ellipse[0][0]), int(ellipse[0][1]))
- axes = (int(ellipse[1][0] / 2), int(ellipse[1][1] / 2))
- angle = ellipse[2]
- errors = []
- for point in max_contour:
- ellipse_point = map_point_to_ellipse(point, center, axes, angle)
- distance = np.linalg.norm(np.array(point) - np.array(ellipse_point))
-
- errors.append(distance)
- average_error = np.mean(errors)
-
- if average_error < final_error:
- final_error = average_error
- final_ellipse = ellipse
- final_thresh_img = thresh_img
- final_bin_thresh = bin_thresh
- final_contour = max_contour
- return final_ellipse, final_thresh_img, final_bin_thresh, final_contour
- def find_notch(white_path, cam_num):
- image = cv2.imread(white_path)
- if cam_num == 1:
- ellipse, thresh_img, bin_thresh, max_contour_ellipse = find_best_circle(image, 5, 50)
-
- background = np.zeros_like(thresh_img)
- cv2.ellipse(background, ellipse, (255, 255, 255), 1)
- ellipse_points = np.column_stack(np.where(background == 255))
- (center_x, center_y), (axis_major, axis_minor), angle = ellipse
-
- height, width = thresh_img.shape
- min_gray = 10000
- min_pt = [0,0]
- for pt in ellipse_points:
- x, y = pt[1], pt[0]
- #cv2.circle(image, (pt[1], pt[0]), 3, (0,255,255), 2)
- x_start = max(0, x - 5)
- x_end = min(width - 1, x + 5)
- y_start = max(0, y - 5)
- y_end = min(height - 1, y + 5)
- neighborhood = thresh_img[y_start:y_end+1, x_start:x_end+1]
- mean_value = np.mean(neighborhood)
- if mean_value < min_gray:
- min_gray = mean_value
- min_pt = (x, y)
- angle_rad = np.arctan2(min_pt[1] - center_y, min_pt[0] - center_x)
- thresh_img = cv2.drawContours(background, [max_contour_ellipse], -1, (255, 255, 255), thickness=cv2.FILLED)
-
- cv2.circle(image, min_pt, 50, (0,255,255), 2)
- #print(min_pt)
- cv2.namedWindow('thresh_img', 0)
- cv2.imshow('thresh_img', thresh_img)
- cv2.namedWindow('contours', 0)
- cv2.imshow('contours', image)
-
- cv2.waitKey(0)
- return angle_rad, thresh_img
- else:
- return 0
- def apply_median_filter(z_reconstructed, size=3):
- # 应用中值滤波,size 定义了邻域的大小
- z_filtered = median_filter(z_reconstructed, size=size)
- return z_filtered
- def apply_gaussian_filter(z_reconstructed, sigma=1.0):
- # 应用高斯滤波,sigma 控制平滑的程度
- z_filtered = gaussian_filter(z_reconstructed, sigma=sigma)
- return z_filtered
- def detect_outliers(z_reconstructed, threshold=3.0):
- mean_z = np.mean(z_reconstructed)
- std_z = np.std(z_reconstructed)
-
- # 计算每个点的 z 值与全局均值的差距
- z_diff = np.abs(z_reconstructed - mean_z)
-
- # 检测超过阈值的点
- outliers = z_diff > (threshold * std_z)
-
- return outliers
- def remove_outliers(z_reconstructed, threshold=3.0):
- # 检测异常点
- outliers = detect_outliers(z_reconstructed, threshold=threshold)
-
- # 将异常点替换为邻域的平均值
- z_filtered = np.copy(z_reconstructed)
- z_filtered[outliers] = np.median(z_reconstructed[~outliers])
-
- return z_filtered
- def apply_local_average_smoothing(z_reconstructed, window_size=3):
- padded_z = np.pad(z_reconstructed, pad_width=window_size//2, mode='reflect')
- smoothed_z = np.copy(z_reconstructed)
- # 对每个点计算邻域内的平均值
- for i in range(z_reconstructed.shape[0]):
- for j in range(z_reconstructed.shape[1]):
- window = padded_z[i:i+window_size, j:j+window_size]
- smoothed_z[i, j] = np.mean(window)
-
- return smoothed_z
- def average_duplicate_points(points):
- """
- 处理点云,计算每个 (x, y) 坐标中所有 z 值的均值。
-
- 参数:
- points: (n, 3) 形状的 NumPy 数组,表示 (x, y, z) 格式的点云数据
-
- 返回:
- result: 去重后的点云数据,每个 (x, y) 对应的 z 值是所有重叠 z 值的均值
- """
- # 将点云数据放入 DataFrame 中,方便处理
- df = pd.DataFrame(points, columns=['x', 'y', 'z'])
-
- # 按照 (x, y) 进行分组,并计算 z 值的均值
- unique_points = df.groupby(['x', 'y'], as_index=False).mean()
-
- return unique_points[['x', 'y', 'z']].to_numpy()
- def remove_duplicate_points(points):
- """
- 处理点云,保留每个 (x, y) 坐标中与全局 z 值均值最近的一个 z 值。
-
- 参数:
- points: (n, 3) 形状的 NumPy 数组,表示 (x, y, z) 格式的点云数据
-
- 返回:
- result: 去重后的点云数据,只保留每个 (x, y) 对应的一个 z 值
- """
- # 将点云数据放入 DataFrame 中,方便处理
- df = pd.DataFrame(points, columns=['x', 'y', 'z'])
-
- # 计算全局 z 值的均值
- global_z_mean = df['z'].mean()
-
- # 按照 (x, y) 对每个点进行分组
- def select_closest_z(group):
- # 找到 z 值与全局均值最近的那个
- closest_index = np.abs(group['z'] - global_z_mean).idxmin()
- return group.loc[closest_index]
-
- # 应用分组操作,找到每个 (x, y) 中 z 值与全局均值最近的一个
- unique_points = df.groupby(['x', 'y'], as_index=False).apply(select_closest_z)
-
- # 重置索引并返回结果
- unique_points = unique_points.reset_index(drop=True)
- return unique_points[['x', 'y', 'z']].to_numpy()
- def smooth_z_with_std(z_values, threshold=2.0):
- """
- 使用均值和标准差去除 z 值中的离散点。
-
- 参数:
- z_values: 点云的 z 值数组
- threshold: 超过标准差的倍数,作为离散点的判断标准
-
- 返回:
- smoothed_z: 平滑后的 z 值
- """
- # 计算 z 值的均值和标准差
- mean_z = np.mean(z_values)
- std_z = np.std(z_values)
-
- # 创建一个副本,避免直接修改原始数据
- smoothed_z = np.copy(z_values)
-
- # 查找离散点(z 值偏离均值超过 threshold * 标准差的点)
- outliers = np.abs(z_values - mean_z) > threshold * std_z
-
- # 将离散点替换为全局均值或其他平滑方法
- smoothed_z[outliers] = mean_z # 可以选择其他平滑值,如邻居的均值
-
- return smoothed_z
- def fit_plane_and_adjust(points, threshold):
- """
- 拟合一个平面,调整距离平面距离大于阈值的点的 z 值,使其在拟合平面上。
-
- 参数:
- points: (n, 3) 形状的 NumPy 数组,表示 (x, y, z) 格式的点云数据
- threshold: 距离平面的最大允许偏差,超过此距离的点将被设置为平面上的点
-
- 返回:
- result: 处理后的点云数据
- """
- # 将点云数据放入 DataFrame 中,方便处理
- df = pd.DataFrame(points, columns=['x', 'y', 'z'])
-
- # 使用 x 和 y 拟合平面 z = ax + by + c
- X = df[['x', 'y']].values
- Z = df['z'].values
- model = LinearRegression().fit(X, Z)
-
- # 获取拟合平面方程的系数 a, b 和截距 c
- a, b = model.coef_
- c = model.intercept_
-
- # 计算每个点在拟合平面上的 z 值
- z_fit = a * df['x'] + b * df['y'] + c
-
- # 计算每个点到拟合平面的距离
- distances = np.abs(df['z'] - z_fit)
-
- # 将距离超过阈值的点的 z 值设置为平面上的 z 值
- df.loc[distances > threshold, 'z'] = z_fit[distances > threshold]
-
- return df[['x', 'y', 'z']].to_numpy()
- def laplacian_smoothing(points, z_values, iterations=10, alpha=0.1):
- """
- 使用拉普拉斯平滑来平滑点云 z 值。
-
- 参数:
- points: (n, 2) 形状的 NumPy 数组,表示 (x, y) 坐标的点
- z_values: 点云的 z 值数组
- iterations: 平滑的迭代次数
- alpha: 平滑系数,控制更新的幅度
-
- 返回:
- smoothed_z: 平滑后的 z 值
- """
- smoothed_z = np.copy(z_values)
-
- for _ in range(iterations):
- for i in range(len(points)):
- # 找到所有的邻居
- distances = np.linalg.norm(points - points[i], axis=1)
- neighbors = distances < 1.5 # 假设距离小于一定值的是邻居
-
- # 计算邻居 z 值的平均值
- neighbor_z = np.mean(smoothed_z[neighbors])
-
- # 更新当前点的 z 值
- smoothed_z[i] = (1 - alpha) * smoothed_z[i] + alpha * neighbor_z
-
- return smoothed_z
- def bilinear_interpolation(points, z_values, grid_size=(100, 100)):
- """
- 使用双线性插值对点云 z 值进行平滑。
-
- 参数:
- points: (n, 2) 形状的 NumPy 数组,表示 (x, y) 坐标的点
- z_values: 点云的 z 值数组
- grid_size: 定义插值后生成的网格大小
-
- 返回:
- grid_z: 插值后的平滑 z 值
- """
- # 定义插值网格
- x_min, x_max = points[:, 0].min(), points[:, 0].max()
- y_min, y_max = points[:, 1].min(), points[:, 1].max()
- grid_x, grid_y = np.mgrid[x_min:x_max:grid_size[0]*1j, y_min:y_max:grid_size[1]*1j]
- # 使用双线性插值
- grid_z = griddata(points, z_values, (grid_x, grid_y), method='linear')
-
- return grid_x, grid_y, grid_z
- def cubic_interpolation(points, z_values, grid_size=(100, 100)):
- """
- 使用三次样条插值对点云 z 值进行平滑。
-
- 参数:
- points: (n, 2) 形状的 NumPy 数组,表示 (x, y) 坐标的点
- z_values: 点云的 z 值数组
- grid_size: 定义插值后生成的网格大小
-
- 返回:
- grid_z: 插值后的平滑 z 值
- """
- # 定义插值网格
- x_min, x_max = points[:, 0].min(), points[:, 0].max()
- y_min, y_max = points[:, 1].min(), points[:, 1].max()
- grid_x, grid_y = np.mgrid[x_min:x_max:grid_size[0]*1j, y_min:y_max:grid_size[1]*1j]
- smoothed_z = gaussian_filter(z_values.reshape(grid_size), sigma=1)
- # 使用三次样条插值
- grid_z = griddata(points, smoothed_z, (grid_x, grid_y), method='cubic')
-
- return grid_x, grid_y, grid_z
- def moving_average_filter_z(points, window_size=3):
- """
- 使用移动平均法对 z 值进行平滑,减少异常值。
-
- 参数:
- z_values: 点云的 z 值数组
- window_size: 移动窗口的大小
-
- 返回:
- smoothed_z: 平滑后的 z 值
- """
- z_values = points[:,2]
- smoothed_z = np.copy(z_values)
- for i in range(len(z_values)):
- # 找到窗口内的邻居
- start_idx = max(0, i - window_size // 2)
- end_idx = min(len(z_values), i + window_size // 2 + 1)
- smoothed_z[i] = np.mean(z_values[start_idx:end_idx])
-
- return np.column_stack((points[:,0], points[:,1], smoothed_z))
- def rbf_interpolation(points, z_values, grid_size=(100, 100)):
- """
- 使用径向基函数 (RBF) 对点云 z 值进行插值和平滑。
-
- 参数:
- points: (n, 2) 形状的 NumPy 数组,表示 (x, y) 坐标的点
- z_values: 点云的 z 值数组
- grid_size: 定义插值后生成的网格大小
-
- 返回:
- grid_z: 插值后的平滑 z 值
- """
- # 定义插值网格
- x_min, x_max = points[:, 0].min(), points[:, 0].max()
- y_min, y_max = points[:, 1].min(), points[:, 1].max()
- grid_x, grid_y = np.mgrid[x_min:x_max:grid_size[0]*1j, y_min:y_max:grid_size[1]*1j]
- # 使用径向基函数进行插值
- rbf = Rbf(points[:, 0], points[:, 1], z_values, function='linear')
- grid_z = rbf(grid_x, grid_y)
-
- return grid_x, grid_y, grid_z
- def show_cloud_point(pointcloud_path):
- points = np.loadtxt(os.path.join(pointcloud_path), delimiter=',')
-
-
- if __name__ == '__main__':
- # white_path = 'D:\\data\\four_cam\\1008_storage\\'
- # cam_num = 1
- # #find_notch(white_path, cam_num)
- # for img in os.listdir(white_path):
- # if img.endswith('.bmp'):
- # get_white_mask(white_path + img, bin_thresh=12)
- #camera_calibrate_vis()
- img_folder = 'D:\\data\\four_cam\\1008_storage\\20241008210412543'
- point_cloud_path = os.path.join(img_folder, 'cloudpoint.txt')
- show_cloud_point(point_cloud_path)
|