|
@@ -6,6 +6,15 @@ import scipy.io as sio
|
6
|
6
|
import matplotlib.pyplot as plt
|
7
|
7
|
from scipy.interpolate import griddata
|
8
|
8
|
import glob
|
|
9
|
+from scipy import stats
|
|
10
|
+from scipy.optimize import minimize
|
|
11
|
+from collections import defaultdict
|
|
12
|
+from scipy.signal import savgol_filter
|
|
13
|
+from scipy.ndimage import uniform_filter, gaussian_filter
|
|
14
|
+import json
|
|
15
|
+import pickle
|
|
16
|
+from aprilgrid import Detector
|
|
17
|
+
|
9
|
18
|
|
10
|
19
|
|
11
|
20
|
def read_and_undis(img_path, mtx, dist):
|
|
@@ -127,6 +136,7 @@ def fit_sector(contour_points, grid_spacing):
|
127
|
136
|
# 生成 xy 平面上的网格点
|
128
|
137
|
xx, yy = np.meshgrid(np.arange(x, x + w, grid_spacing), np.arange(y, y + h, grid_spacing))
|
129
|
138
|
grid_points_xy = np.vstack([xx.ravel(), yy.ravel()]).T
|
|
139
|
+
|
130
|
140
|
|
131
|
141
|
# 过滤出轮廓内部的点,并记录它们的索引
|
132
|
142
|
inside_points_xy = []
|
|
@@ -191,7 +201,7 @@ def get_meshgrid_contour(binary, grid_spacing, save_path, debug=False):
|
191
|
201
|
# image = cv2.imread(img_path, cv2.IMREAD_GRAYSCALE)
|
192
|
202
|
# binary = binarize(image)
|
193
|
203
|
# Detect contours
|
194
|
|
- kernel = np.ones((5, 5), np.uint8)
|
|
204
|
+ kernel = np.ones((1, 1), np.uint8)
|
195
|
205
|
|
196
|
206
|
# 应用腐蚀操作
|
197
|
207
|
erosion = cv2.erode(binary, kernel, iterations=1)
|
|
@@ -201,11 +211,14 @@ def get_meshgrid_contour(binary, grid_spacing, save_path, debug=False):
|
201
|
211
|
max_index, max_contour = max(enumerate(contours), key=lambda x: cv2.boundingRect(x[1])[2] * cv2.boundingRect(x[1])[3])
|
202
|
212
|
# max_index, max_contour1 = max(enumerate(contours1), key=lambda x: cv2.boundingRect(x[1])[2] * cv2.boundingRect(x[1])[3])
|
203
|
213
|
|
204
|
|
- # output_image = cv2.cvtColor(erosion, cv2.COLOR_GRAY2BGR) # 转换为彩色图像用于绘制
|
205
|
|
- # cv2.drawContours(output_image, [max_contour1], -1, (255, 255, 0), thickness=2)
|
206
|
|
- # cv2.namedWindow('output_image', 0)
|
207
|
|
- # cv2.imshow('output_image', output_image)
|
208
|
|
- # cv2.waitKey(0)
|
|
214
|
+ output_image = cv2.cvtColor(erosion, cv2.COLOR_GRAY2BGR) # 转换为彩色图像用于绘制
|
|
215
|
+
|
|
216
|
+ if debug:
|
|
217
|
+ cv2.drawContours(output_image, [max_contour], -1, (255, 255, 0), thickness=2)
|
|
218
|
+ cv2.namedWindow('output_image', 0)
|
|
219
|
+ cv2.imshow('output_image', output_image)
|
|
220
|
+ cv2.waitKey(0)
|
|
221
|
+ cv2.destroyAllWindows()
|
209
|
222
|
#print('max_contour = ',max_contour[0])
|
210
|
223
|
max_contour_lst = []
|
211
|
224
|
for pt in max_contour:
|
|
@@ -391,7 +404,7 @@ def affine_img2world(grid_points, A, Rc2w, Tc2w, d):
|
391
|
404
|
# return [x_w_data, y_w_data, z_w_data]
|
392
|
405
|
return intersection_points_world
|
393
|
406
|
|
394
|
|
-def get_world_points(contour_points, world_mat_contents, grid_spacing, d, save_path, debug=False):
|
|
407
|
+def get_world_points(contour_points, world_mat_contents, cam_id, grid_spacing, d, save_path, debug=False):
|
395
|
408
|
|
396
|
409
|
A = world_mat_contents['camera_matrix']
|
397
|
410
|
Rw2c = world_mat_contents['rotation_matrix']
|
|
@@ -424,7 +437,7 @@ def get_world_points(contour_points, world_mat_contents, grid_spacing, d, save_p
|
424
|
437
|
|
425
|
438
|
# 调整子图之间的间距
|
426
|
439
|
plt.tight_layout()
|
427
|
|
- plt.savefig(os.path.join(save_path, "world_points.png"))
|
|
440
|
+ plt.savefig(os.path.join(save_path, str(cam_id) + "_world_points.png"))
|
428
|
441
|
# 显示图像
|
429
|
442
|
plt.show()
|
430
|
443
|
# import pdb; pdb.set_trace()
|
|
@@ -545,7 +558,10 @@ def get_screen_points(point_data, x_phase_unwrapped, y_phase_unwrapped, screen_p
|
545
|
558
|
u_now = point_data['u_p'][idx]
|
546
|
559
|
v_now = point_data['v_p'][idx]
|
547
|
560
|
u_now, v_now = int(u_now), int(v_now)
|
548
|
|
-
|
|
561
|
+ #print('x_phase_unwrapped shape = ',x_phase_unwrapped.shape, y_phase_unwrapped.shape)
|
|
562
|
+ # print('v_now, u_now = ', v_now, u_now)
|
|
563
|
+ # if u_now > x_phase_unwrapped.shape[1]-1:
|
|
564
|
+ # u_now = x_phase_unwrapped.shape[1] - 2
|
549
|
565
|
phase_x_now = x_phase_unwrapped[v_now, u_now] # 注意这里的索引要按照Python的行列顺序,与MATLAB相反 raw
|
550
|
566
|
phase_y_now = y_phase_unwrapped[v_now, u_now]
|
551
|
567
|
|
|
@@ -597,10 +613,23 @@ def get_screen_points(point_data, x_phase_unwrapped, y_phase_unwrapped, screen_p
|
597
|
613
|
def get_white_mask(white_path, bin_thresh=12):
|
598
|
614
|
gray = cv2.imread(white_path, cv2.COLOR_BGR2GRAY)
|
599
|
615
|
mask = np.zeros_like(gray)
|
|
616
|
+
|
600
|
617
|
_, thresh_img = cv2.threshold(gray, bin_thresh, 255, cv2.THRESH_BINARY)
|
601
|
|
- contours, _ = cv2.findContours(thresh_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
602
|
|
- max_index, max_contour = max(enumerate(contours), key=lambda x: cv2.boundingRect(x[1])[2] * cv2.boundingRect(x[1])[3])
|
603
|
|
- cv2.drawContours(mask, [max_contour], -1, (255, 255, 255), thickness=cv2.FILLED)
|
|
618
|
+ kernel = np.ones((8, 1), np.uint8) # 垂直核大小为 5x1,表示垂直方向腐蚀
|
|
619
|
+ erode_thresh_img = cv2.erode(thresh_img, kernel, iterations=1)
|
|
620
|
+ img_height, img_width = erode_thresh_img.shape[:2]
|
|
621
|
+ contours, _ = cv2.findContours(erode_thresh_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
622
|
+ valid_contours = [
|
|
623
|
+ contour for contour in contours
|
|
624
|
+ if cv2.boundingRect(contour)[2] < 0.8*img_width and cv2.boundingRect(contour)[3] < 0.8 * img_height
|
|
625
|
+ ]
|
|
626
|
+ # 确保有有效的轮廓
|
|
627
|
+ if valid_contours:
|
|
628
|
+ max_index, max_contour = max(enumerate(valid_contours), key=lambda x: cv2.boundingRect(x[1])[2] * cv2.boundingRect(x[1])[3])
|
|
629
|
+ cv2.drawContours(mask, [max_contour], -1, (255, 255, 255), thickness=cv2.FILLED)
|
|
630
|
+ # cv2.namedWindow('mask',0)
|
|
631
|
+ # cv2.imshow('mask', mask)
|
|
632
|
+ # cv2.waitKey(0)
|
604
|
633
|
return mask
|
605
|
634
|
|
606
|
635
|
# def get_world_points_from_mask(binary_image, world_mat_contents, d, save_path, debug=False):
|
|
@@ -674,9 +703,125 @@ def transform_point_cloud(point_cloud, rotation_matrix, translation_vector):
|
674
|
703
|
translated_point_cloud = rotated_point_cloud + translation_vector
|
675
|
704
|
return translated_point_cloud
|
676
|
705
|
|
|
706
|
+# 定义拟合圆的函数
|
|
707
|
+def calc_radius(x, y, xc, yc):
|
|
708
|
+ """计算每个点到圆心的距离"""
|
|
709
|
+ return np.sqrt((x - xc)**2 + (y - yc)**2)
|
|
710
|
+
|
|
711
|
+def fit_circle_post(x, y):
|
|
712
|
+ """拟合一个圆并返回圆心和半径"""
|
|
713
|
+ # 初始猜测圆心为数据的平均值
|
|
714
|
+ x_m = np.mean(x)
|
|
715
|
+ y_m = np.mean(y)
|
|
716
|
+
|
|
717
|
+ def optimize_func(params):
|
|
718
|
+ xc, yc = params
|
|
719
|
+ radii = calc_radius(x, y, xc, yc)
|
|
720
|
+ return np.var(radii) # 最小化半径的方差,使得所有点接近正圆
|
|
721
|
+
|
|
722
|
+ result = minimize(optimize_func, [x_m, y_m])
|
|
723
|
+ xc, yc = result.x
|
|
724
|
+ radii = calc_radius(x, y, xc, yc)
|
|
725
|
+ radius_mean = np.mean(radii)
|
|
726
|
+ return xc, yc, radius_mean
|
|
727
|
+
|
|
728
|
+
|
|
729
|
+def remove_repeat_z(total_cloud_point):
|
|
730
|
+ # 计算所有 z 的全局均值
|
|
731
|
+ global_mean_z = np.mean(total_cloud_point[:, 2])
|
|
732
|
+
|
|
733
|
+ # 使用字典来记录每个 (x, y) 对应的 z 值
|
|
734
|
+ xy_dict = defaultdict(list)
|
|
735
|
+
|
|
736
|
+ # 将数据分组存入字典,键为 (x, y),值为所有对应的 z 值
|
|
737
|
+ for x_val, y_val, z_val in total_cloud_point:
|
|
738
|
+ xy_dict[(x_val, y_val)].append(z_val)
|
|
739
|
+
|
|
740
|
+ # 构建新的点云数据
|
|
741
|
+ new_data = []
|
|
742
|
+
|
|
743
|
+ # 对每个 (x, y),选择距离全局均值最近的 z 值
|
|
744
|
+ for (x_val, y_val), z_vals in xy_dict.items():
|
|
745
|
+ if len(z_vals) > 1:
|
|
746
|
+ # 多个 z 值时,选择与全局均值最接近的 z
|
|
747
|
+ closest_z = min(z_vals, key=lambda z: abs(z - global_mean_z))
|
|
748
|
+ else:
|
|
749
|
+ # 只有一个 z 值时,直接使用
|
|
750
|
+ closest_z = z_vals[0]
|
|
751
|
+
|
|
752
|
+ # 将结果保存为新的点
|
|
753
|
+ new_data.append([x_val, y_val, closest_z])
|
677
|
754
|
|
|
755
|
+ # 将新点云数据转化为 NumPy 数组
|
|
756
|
+ new_data = np.array(new_data)
|
|
757
|
+ return new_data
|
678
|
758
|
|
679
|
759
|
|
|
760
|
+def make_circle(data):
|
|
761
|
+
|
|
762
|
+ # 计算所有 z 的全局均值
|
|
763
|
+ global_mean_z = np.mean(data[:, 2])
|
|
764
|
+
|
|
765
|
+ # Step 1: 确定最小外接圆
|
|
766
|
+ # 使用 xy 坐标计算最小外接圆
|
|
767
|
+ xy_vals = data[:, :2].astype(np.float32)
|
|
768
|
+ (x_center, y_center), radius = cv2.minEnclosingCircle(xy_vals)
|
|
769
|
+
|
|
770
|
+ # 获取最小外接圆的中心和半径
|
|
771
|
+ x_center, y_center, radius = int(x_center), int(y_center), int(radius)
|
|
772
|
+
|
|
773
|
+ # Step 2: 使用 IQR 进行异常 z 值检测和替换
|
|
774
|
+ # 计算 z 的四分位数
|
|
775
|
+ Q1 = np.percentile(data[:, 2], 25)
|
|
776
|
+ Q3 = np.percentile(data[:, 2], 75)
|
|
777
|
+ IQR = Q3 - Q1
|
|
778
|
+ # 将异常的 z 值替换为均值
|
|
779
|
+ #data[:, 2] = np.where((data[:, 2] < lower_bound) | (data[:, 2] > upper_bound), global_mean_z, data[:, 2])
|
|
780
|
+
|
|
781
|
+ # Step 3: 填补最小外接圆内的空缺点
|
|
782
|
+ # 创建一个用于存储已存在的 (x, y) 坐标的集合
|
|
783
|
+ existing_xy_set = set((int(x), int(y)) for x, y in data[:, :2])
|
|
784
|
+
|
|
785
|
+ # 遍历最小外接圆范围内的所有 (x, y) 坐标
|
|
786
|
+ filled_data = []
|
|
787
|
+ for x in range(x_center - radius, x_center + radius + 1):
|
|
788
|
+ for y in range(y_center - radius, y_center + radius + 1):
|
|
789
|
+ # 判断 (x, y) 是否在圆内
|
|
790
|
+ if np.sqrt((x - x_center)**2 + (y - y_center)**2) <= radius:
|
|
791
|
+ if (x, y) in existing_xy_set:
|
|
792
|
+ # 如果 (x, y) 已存在,则保留原始数据
|
|
793
|
+ z_val = data[(data[:, 0] == x) & (data[:, 1] == y), 2][0]
|
|
794
|
+ else:
|
|
795
|
+ # 如果 (x, y) 不存在,填充 z 的全局均值
|
|
796
|
+ z_val = global_mean_z
|
|
797
|
+ filled_data.append([x, y, z_val])
|
|
798
|
+
|
|
799
|
+ # 转换为 NumPy 数组
|
|
800
|
+ filled_data = np.array(filled_data)
|
|
801
|
+ return filled_data
|
|
802
|
+
|
|
803
|
+def point_filter(data):
|
|
804
|
+ data[:, 0] = np.round(data[:, 0]).astype(int)
|
|
805
|
+ data[:, 1] = np.round(data[:, 1]).astype(int)
|
|
806
|
+ # 构建平面网格,假设 grid_x 和 grid_y 已经生成
|
|
807
|
+ grid_size = 100 # 网格大小根据你的数据情况设定
|
|
808
|
+ grid_x, grid_y = np.meshgrid(np.linspace(min(data[:, 0]), max(data[:, 0]), grid_size),
|
|
809
|
+ np.linspace(min(data[:, 1]), max(data[:, 1]), grid_size))
|
|
810
|
+
|
|
811
|
+ # 插值,将 (x, y, z) 数据转换为平面网格
|
|
812
|
+ grid_z = griddata((data[:, 0], data[:, 1]), data[:, 2], (grid_x, grid_y), method='linear')
|
|
813
|
+ print('grid_x = ', grid_x)
|
|
814
|
+ print('grid_y = ', grid_y)
|
|
815
|
+ print('grid_z = ', grid_z)
|
|
816
|
+ smoothed_z = uniform_filter(grid_z, size=5)
|
|
817
|
+ print('smoothed_z range = ',np.max(smoothed_z) - np.min(smoothed_z))
|
|
818
|
+
|
|
819
|
+ fig = plt.figure(figsize=(10, 8))
|
|
820
|
+ ax = fig.add_subplot(111, projection='3d')
|
|
821
|
+ ax.plot_surface(grid_x, grid_y, smoothed_z, cmap='viridis', alpha=0.7)
|
|
822
|
+ plt.show()
|
|
823
|
+ return smoothed_z
|
|
824
|
+
|
680
|
825
|
def post_process(txt_path, debug):
|
681
|
826
|
#txt_path = 'D:\\file\\20240913-data\\20240913105139948\\'
|
682
|
827
|
total_cloud_point = np.empty((0, 3))
|
|
@@ -728,13 +873,29 @@ def post_process(txt_path, debug):
|
728
|
873
|
# print('i = min y = ',i , np.min(data[i][:, 1]))
|
729
|
874
|
data[i][:, 2] = data[i][:, 2] - np.mean(data[i][:, 2])
|
730
|
875
|
total_cloud_point = np.vstack([total_cloud_point, np.column_stack((data[i][:, 0], data[i][:, 1], data[i][:, 2]))])
|
|
876
|
+
|
|
877
|
+ # 将 xy 坐标四舍五入为整数
|
|
878
|
+ total_cloud_point[:, 0] = np.round(total_cloud_point[:, 0]).astype(int)
|
|
879
|
+ total_cloud_point[:, 1] = np.round(total_cloud_point[:, 1]).astype(int)
|
|
880
|
+
|
|
881
|
+ new_data = remove_repeat_z(total_cloud_point)
|
|
882
|
+ print('new_data range = ',np.max(new_data[:, 2]) - np.min(new_data[:, 2]))
|
|
883
|
+ filter_data = make_circle(new_data)
|
|
884
|
+ print('filter_data range = ',np.max(filter_data[:, 2]) - np.min(filter_data[:, 2]))
|
|
885
|
+ filter_data[:, 2] = np.where(filter_data[:, 2] > 4, 4, filter_data[:, 2])
|
|
886
|
+ filter_data[:, 2] = np.where(filter_data[:, 2] < -2, -2, filter_data[:, 2])
|
|
887
|
+ #print('filter_data = ', filter_data)
|
|
888
|
+ #smoothed_data = point_filter(filter_data)
|
|
889
|
+
|
|
890
|
+
|
|
891
|
+
|
731
|
892
|
if debug:
|
732
|
893
|
fig = plt.figure()
|
733
|
894
|
ax = fig.add_subplot(111, projection='3d')
|
734
|
895
|
# 提取 x, y, z 坐标
|
735
|
|
- x_vals = total_cloud_point[:, 0]
|
736
|
|
- y_vals = total_cloud_point[:, 1]
|
737
|
|
- z_vals = total_cloud_point[:, 2]
|
|
896
|
+ x_vals = filter_data[:,0]
|
|
897
|
+ y_vals = filter_data[:,1]
|
|
898
|
+ z_vals = filter_data[:,2]
|
738
|
899
|
|
739
|
900
|
# 绘制3D点云
|
740
|
901
|
ax.scatter(x_vals, y_vals, z_vals, c=z_vals, cmap='viridis', marker='o')
|
|
@@ -744,5 +905,185 @@ def post_process(txt_path, debug):
|
744
|
905
|
ax.set_ylabel('Y (mm)')
|
745
|
906
|
ax.set_zlabel('Z (um)')
|
746
|
907
|
ax.set_title('3D Point Cloud Visualization')
|
|
908
|
+ plt.show()
|
|
909
|
+ return filter_data
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+def plot_camera(ax, rotation_matrix, translation_vector, camera_id, scale=1.0, color='r'):
|
|
914
|
+ """
|
|
915
|
+ 绘制单个相机的三维表示:位置、方向和视野
|
|
916
|
+ R: 3x3旋转矩阵 (从世界坐标系到相机坐标系)
|
|
917
|
+ t: 3x1平移向量 (相机位置)
|
|
918
|
+ scale: 控制相机大小的比例因子
|
|
919
|
+ color: 相机视野的颜色
|
|
920
|
+ """
|
|
921
|
+ # 相机的中心
|
|
922
|
+ camera_center = -rotation_matrix.T @ translation_vector
|
|
923
|
+ print('camera_center = ', camera_center)
|
|
924
|
+ camera_center = np.squeeze(camera_center) # 确保 camera_center 是 1D 数组
|
|
925
|
+ #print('camera_center = ', camera_center)
|
|
926
|
+
|
|
927
|
+ # 定义相机的四个角点在相机坐标系中的位置
|
|
928
|
+ camera_points = np.array([
|
|
929
|
+ [0, 0, 0], # 相机中心
|
|
930
|
+ [1, 1, 2], # 视野的左上角
|
|
931
|
+ [1, -1, 2], # 视野的左下角
|
|
932
|
+ [-1, -1, 2], # 视野的右下角
|
|
933
|
+ [-1, 1, 2] # 视野的右上角
|
|
934
|
+ ]) * scale
|
|
935
|
+
|
|
936
|
+ # 将相机坐标系下的点转换到世界坐标系
|
|
937
|
+ camera_points_world = (rotation_matrix.T @ camera_points.T).T + camera_center.T
|
|
938
|
+ # 绘制相机位置(红色圆点表示相机中心)
|
|
939
|
+ ax.scatter(camera_center[0], camera_center[1], camera_center[2], color=color, marker='o', s=100)
|
|
940
|
+
|
|
941
|
+ # 绘制相机的 X、Y、Z 坐标轴
|
|
942
|
+ axis_length = scale * 3 # 坐标轴箭头长度
|
|
943
|
+ x_axis = rotation_matrix[:, 0] # X 轴方向
|
|
944
|
+ y_axis = rotation_matrix[:, 1] # Y 轴方向
|
|
945
|
+ z_axis = rotation_matrix[:, 2] # Z 轴方向
|
|
946
|
+
|
|
947
|
+ # X 轴箭头(红色)
|
|
948
|
+ ax.quiver(camera_center[0], camera_center[1], camera_center[2],
|
|
949
|
+ x_axis[0], x_axis[1], x_axis[2],
|
|
950
|
+ length=axis_length, color='r', arrow_length_ratio=0.5, label='X axis')
|
|
951
|
+
|
|
952
|
+ # Y 轴箭头(绿色)
|
|
953
|
+ ax.quiver(camera_center[0], camera_center[1], camera_center[2],
|
|
954
|
+ y_axis[0], y_axis[1], y_axis[2],
|
|
955
|
+ length=axis_length, color='g', arrow_length_ratio=0.5, label='Y axis')
|
|
956
|
+
|
|
957
|
+ # Z 轴箭头(蓝色)
|
|
958
|
+ ax.quiver(camera_center[0], camera_center[1], camera_center[2],
|
|
959
|
+ z_axis[0], z_axis[1], z_axis[2],
|
|
960
|
+ length=axis_length, color='b', arrow_length_ratio=0.5, label='Z axis')
|
|
961
|
+
|
|
962
|
+ # 绘制相机中心
|
|
963
|
+ ax.scatter(camera_center[0], camera_center[1], camera_center[2], color=color, marker='o', s=100, label="Camera")
|
|
964
|
+ ax.text(camera_center[0], camera_center[1], camera_center[2], f'Cam {camera_id}', color='black', fontsize=12)
|
|
965
|
+
|
|
966
|
+ #绘制相机视野四个角点与相机中心的连线
|
|
967
|
+ for i in range(1, 5):
|
|
968
|
+ ax.plot([camera_center[0], camera_points_world[i, 0]],
|
|
969
|
+ [camera_center[1], camera_points_world[i, 1]],
|
|
970
|
+ [camera_center[2], camera_points_world[i, 2]], color=color)
|
|
971
|
+
|
|
972
|
+ # 绘制相机的四边形视野
|
|
973
|
+ 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]],
|
|
974
|
+ [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]],
|
|
975
|
+ [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)
|
|
976
|
+
|
|
977
|
+ # # 添加相机方向箭头
|
|
978
|
+ # ax.quiver(camera_center[0], camera_center[1], camera_center[2],
|
|
979
|
+ # rotation_matrix[0, 2], rotation_matrix[1, 2], rotation_matrix[2, 2], length=scale, color='g', arrow_length_ratio=1, label="Direction")
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+def transform_image_to_cam0(img, K_cam_i, R_cam_i, T_cam_i, K_cam0, R_cam0, T_cam0, depth_map):
|
|
983
|
+ """
|
|
984
|
+ 将相机 i 的图像转换到相机 0 的坐标系下,并重新投影到 cam0 的图像平面
|
|
985
|
+ img: 相机 i 拍摄的图像 (H, W, 3)
|
|
986
|
+ K_cam_i: 相机 i 的内参矩阵 (3, 3)
|
|
987
|
+ R_cam_i: 相机 i 的旋转矩阵 (3, 3)
|
|
988
|
+ T_cam_i: 相机 i 的平移向量 (3,)
|
|
989
|
+ K_cam0: 相机 cam0 的内参矩阵 (3, 3)
|
|
990
|
+ R_cam0: 相机 cam0 的旋转矩阵 (3, 3)
|
|
991
|
+ T_cam0: 相机 cam0 的平移向量 (3,)
|
|
992
|
+ depth_map: 相机 i 的深度图 (H, W) 或由立体视觉计算得到
|
|
993
|
+ 返回:对齐到 cam0 的图像
|
|
994
|
+ """
|
|
995
|
+ H, W = img.shape[:2]
|
|
996
|
+
|
|
997
|
+ # 生成像素坐标网格
|
|
998
|
+ u, v = np.meshgrid(np.arange(W), np.arange(H))
|
|
999
|
+
|
|
1000
|
+ # 将图像坐标转换为齐次坐标
|
|
1001
|
+ img_pts = np.stack([u.ravel(), v.ravel(), np.ones_like(u).ravel()], axis=1).T # (3, N)
|
|
1002
|
+
|
|
1003
|
+ # 反投影到三维点,计算相机 i 坐标系下的三维点
|
|
1004
|
+ depth_vals = depth_map.ravel()
|
|
1005
|
+ X_cam_i = np.linalg.inv(K_cam_i) @ (img_pts * depth_vals) # (3, N)
|
747
|
1006
|
|
748
|
|
- plt.show()
|
|
1007
|
+ # 将三维点从相机 i 坐标系转换到 cam0 坐标系
|
|
1008
|
+ R_inv_cam_i = np.linalg.inv(R_cam_i)
|
|
1009
|
+ X_cam0 = R_cam0 @ R_inv_cam_i @ (X_cam_i - T_cam_i.reshape(3, 1)) + T_cam0.reshape(3, 1)
|
|
1010
|
+
|
|
1011
|
+ # 将三维点投影回 cam0 图像平面
|
|
1012
|
+ img_pts_cam0 = K_cam0 @ X_cam0
|
|
1013
|
+ img_pts_cam0 /= img_pts_cam0[2, :] # 归一化
|
|
1014
|
+
|
|
1015
|
+ # 获取重新投影后的像素位置
|
|
1016
|
+ u_cam0 = img_pts_cam0[0, :].reshape(H, W).astype(np.int32)
|
|
1017
|
+ v_cam0 = img_pts_cam0[1, :].reshape(H, W).astype(np.int32)
|
|
1018
|
+
|
|
1019
|
+ # 创建输出图像
|
|
1020
|
+ output_img = np.zeros_like(img)
|
|
1021
|
+
|
|
1022
|
+ # 将原图像插值到新的像素位置
|
|
1023
|
+ mask = (u_cam0 >= 0) & (u_cam0 < W) & (v_cam0 >= 0) & (v_cam0 < H)
|
|
1024
|
+ output_img[v_cam0[mask], u_cam0[mask]] = img[v[mask], u[mask]]
|
|
1025
|
+
|
|
1026
|
+ return output_img
|
|
1027
|
+
|
|
1028
|
+
|
|
1029
|
+
|
|
1030
|
+def camera_calibrate_vis():
|
|
1031
|
+ img_folder = 'D:\\data\\four_cam\\0927_storage\\20240927161124014'
|
|
1032
|
+ config_path = 'D:\\code\\pmd-python\\config\\cfg_3freq_wafer.json'
|
|
1033
|
+ cfg = json.load(open(config_path, 'r'))
|
|
1034
|
+ with open(os.path.join('D:\\code\\pmd-python\\config\\', cfg['cam_params']), 'rb') as pkl_file:
|
|
1035
|
+ cam_params = pickle.load(pkl_file)
|
|
1036
|
+ fig = plt.figure()
|
|
1037
|
+ ax = fig.add_subplot(111, projection='3d')
|
|
1038
|
+
|
|
1039
|
+ # 绘制每个相机
|
|
1040
|
+ for ii in range(4):
|
|
1041
|
+ plot_camera(ax, cam_params[ii]['rotation_matrix'], cam_params[ii]['translation_vector'], ii, scale=40, color='r')
|
|
1042
|
+
|
|
1043
|
+ # 绘制物体 (假设是一个简单的平面物体,位于 z = 0 平面上)
|
|
1044
|
+ plane_x, plane_y = np.meshgrid(np.linspace(-500, 500, 10), np.linspace(-500, 500, 10))
|
|
1045
|
+ plane_z = np.zeros_like(plane_x) # z = 0 的平面
|
|
1046
|
+ ax.plot_surface(plane_x, plane_y, plane_z, color='blue', alpha=0.3)
|
|
1047
|
+
|
|
1048
|
+ # 设置轴的范围和标签
|
|
1049
|
+ ax.set_xlabel('X axis')
|
|
1050
|
+ ax.set_ylabel('Y axis')
|
|
1051
|
+ ax.set_zlabel('Z axis')
|
|
1052
|
+ ax.set_xlim([-50, 300])
|
|
1053
|
+ ax.set_ylim([-50, 300])
|
|
1054
|
+ ax.set_zlim([0, 500])
|
|
1055
|
+
|
|
1056
|
+ K_cam0 = cam_params[0]['camera_matrix']
|
|
1057
|
+ R_cam0 = cam_params[0]['rotation_matrix']
|
|
1058
|
+ T_cam0 = cam_params[0]['translation_vector']
|
|
1059
|
+
|
|
1060
|
+ for i in range(4):
|
|
1061
|
+ img_path = img_folder + '\\' + str(i) + '_frame_24.bmp'
|
|
1062
|
+ img = cv2.imread(img_path, 0)
|
|
1063
|
+ print('max gray = ', np.max(img))
|
|
1064
|
+ if i > 0:
|
|
1065
|
+ img = cv2.imread(img_path, 0)
|
|
1066
|
+ K_cam_i = cam_params[i]['camera_matrix']
|
|
1067
|
+ R_cam_i = cam_params[i]['rotation_matrix']
|
|
1068
|
+ T_cam_i = cam_params[i]['translation_vector']
|
|
1069
|
+ # 示例图像和深度图
|
|
1070
|
+ depth_map = img
|
|
1071
|
+
|
|
1072
|
+ # 将图像转换到 cam0 坐标系下
|
|
1073
|
+ #aligned_img = transform_image_to_cam0(img, K_cam_i, R_cam_i, T_cam_i, K_cam0, R_cam0, T_cam0, depth_map)
|
|
1074
|
+
|
|
1075
|
+ # cv2.namedWindow('Aligned Image', 0)
|
|
1076
|
+ # cv2.imshow('Aligned Image', aligned_img)
|
|
1077
|
+ # cv2.waitKey(0)
|
|
1078
|
+ # cv2.destroyAllWindows()
|
|
1079
|
+
|
|
1080
|
+ plt.show()
|
|
1081
|
+
|
|
1082
|
+
|
|
1083
|
+
|
|
1084
|
+
|
|
1085
|
+if __name__ == '__main__':
|
|
1086
|
+ camera_calibrate_vis()
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
|