Browse Source

1024 for show

zhangkang 9 months ago
parent
commit
a2a63a0d63

BIN
config/cam_params.pkl


BIN
config/cam_params1023.pkl


BIN
config/optimized_param.pkl


BIN
config/optimized_param1021.pkl


BIN
config/screen_params.pkl


BIN
config/stereo_calib.pkl


BIN
config/stereo_calib1.pkl


BIN
config/stereo_calib2.pkl


+ 58 - 31
pmd.py

@@ -4,6 +4,7 @@ import os
4 4
 import sys
5 5
 import pandas as pd
6 6
 
7
+
7 8
 sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
8 9
 import time
9 10
 import glob
@@ -11,9 +12,9 @@ import numpy as np
11 12
 np.random.seed(42)
12 13
 from datetime import datetime
13 14
 import json
14
-from src.utils import get_meshgrid, get_world_points, get_camera_points, get_screen_points, write_point_cloud, get_white_mask, get_meshgrid_contour, post_process, find_notch
15
+from src.utils import get_meshgrid, get_world_points, get_camera_points, get_screen_points, write_point_cloud, get_white_mask, get_meshgrid_contour, post_process, find_notch, post_process_with_grad
15 16
 from src.phase import extract_phase, unwrap_phase
16
-from src.recons import reconstruction_cumsum
17
+from src.recons import reconstruction_cumsum, poisson_recons_with_smoothed_gradients
17 18
 from src.pcl_postproc import smooth_pcl, align2ref
18 19
 import matplotlib.pyplot as plt
19 20
 from src.calibration import calibrate_world, calibrate_screen, map_screen_to_world
@@ -50,16 +51,17 @@ def main(config_path, img_folder):
50 51
     smooth = True
51 52
     align = True
52 53
     denoise = True
53
-    cammera_img_path = 'D:\\data\\four_cam\\calibrate\\calibrate-1008'
54
+    #cammera_img_path = 'D:\\data\\four_cam\\calibrate\\calibrate-1008'
54 55
     screen_img_path = 'D:\\data\\four_cam\\calibrate\\cam3-screen-1008'
55
-    #cammera_img_path = 'D:\\data\\four_cam\\calibrate\\calibration_0913'
56
+    cammera_img_path = 'D:\\data\\four_cam\\calibrate\\calibrate-1016'
56 57
     #screen_img_path = 'D:\\data\\four_cam\\calibrate\\screen0920'
57 58
 
58 59
     print(f"开始执行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
59 60
     print("\n1. 相机标定")
60 61
     preprocess_start = time.time()
61 62
 
62
-    cam_para_path = os.path.join('config', cfg['cam_params'])
63
+    #cam_para_path = os.path.join('config', cfg['cam_params'])
64
+    cam_para_path = 'D:\\code\\pmd-python\\config\\cam_params.pkl'
63 65
     if os.path.exists(cam_para_path):
64 66
     #if False:
65 67
         with open(cam_para_path, 'rb') as pkl_file:
@@ -72,12 +74,12 @@ def main(config_path, img_folder):
72 74
         for i in range(n_cam):
73 75
             cam_img_path = glob.glob(os.path.join(cammera_img_path, camera_subdir[i], "*.bmp"))
74 76
             cam_img_path.sort()
75
-            print('cam_img_path = ', cam_img_path)
77
+            #print('cam_img_path = ', cam_img_path)
76 78
             cam_param_raw = calibrate_world(cam_img_path, i, cfg['world_chessboard_size'], cfg['world_square_size'], debug=0)
77 79
             cam_params.append(cam_param_raw)
78 80
         with open(cam_para_path, 'wb') as pkl_file:
79 81
            pickle.dump(cam_params, pkl_file) 
80
-    
82
+
81 83
     print("\n2. 屏幕标定")
82 84
     screen_cal_start = time.time()
83 85
 
@@ -118,32 +120,33 @@ def main(config_path, img_folder):
118 120
     print("\n4. 获得不同坐标系下点的位置")
119 121
     get_point_start = time.time()
120 122
     total_cloud_point = np.empty((0, 3))
123
+    total_gradient = np.empty((0, 4))
121 124
     total_boundary_point = np.empty((0, 3))
122 125
     for i in range(n_cam):
123 126
         print('cam_id = ', i)
124 127
         contours_point = get_meshgrid_contour(binary_masks[i], save_path, debug=False)
125
-        world_points, world_points_boundary, world_points_boundary_3 = get_world_points(contours_point, cam_params[i], i, grid_spacing, cfg['d'], erosion_pixels=15, debug=0)
128
+        world_points, world_points_boundary, world_points_boundary_3 = get_world_points(contours_point, cam_params[i], i, grid_spacing, cfg['d'], erosion_pixels=2, debug=0)
126 129
         camera_points, u_p, v_p = get_camera_points(world_points, cam_params[i], save_path, i, debug=0)
127 130
         point_data = {'x_w': world_points[:, 0], 'y_w': world_points[:, 1], 'z_w': world_points[:, 2], 
128 131
                     'x_c': camera_points[:, 0], 'y_c': camera_points[:, 1], 'z_c': camera_points[:, 2],  
129 132
                     'u_p': u_p, 'v_p': v_p}
130 133
         screen_points = get_screen_points(point_data, x_uns[i], y_uns[i], screen_params, screen_to_world, cfg, save_path, i, debug=debug)
131 134
         #plot_coords(world_points, camera_points, screen_points)
132
-        z_raw, aligned, smoothed, denoised = reconstruction_cumsum(world_points, camera_points, screen_points, save_path, i, debug=0, smooth=smooth, align=align, denoise=denoise)
135
+        z_raw, aligned, smoothed, denoised, gradient_xy = reconstruction_cumsum(world_points, camera_points, screen_points, save_path, i, debug=0, smooth=smooth, align=align, denoise=denoise)
133 136
         
134 137
         z_raw_xy = np.round(z_raw[:, :2]).astype(int)
135 138
         
136
-        # 创建布尔掩码,初始为 True
137
-        mask = np.ones(len(z_raw_xy), dtype=bool)
139
+        # # 创建布尔掩码,初始为 True
140
+        # mask = np.ones(len(z_raw_xy), dtype=bool)
138 141
 
139
-        # 遍历每个边界点,标记它们在 aligned 中的位置
140
-        for boundary_point in world_points_boundary:
141
-            # 标记与当前边界点相同 xy 坐标的行
142
-            mask &= ~np.all(z_raw_xy == boundary_point[:2], axis=1)
142
+        # # 遍历每个边界点,标记它们在 aligned 中的位置
143
+        # for boundary_point in world_points_boundary:
144
+        #     # 标记与当前边界点相同 xy 坐标的行
145
+        #     mask &= ~np.all(z_raw_xy == boundary_point[:2], axis=1)
143 146
         
144
-        # 使用掩码过滤出非边界点
145
-        non_boundary_points = z_raw[mask]
146
-        non_boundary_aligned, rotation_matrix = align2ref(non_boundary_points)
147
+        # # 使用掩码过滤出非边界点
148
+        # non_boundary_points = z_raw[mask]
149
+        # non_boundary_aligned, rotation_matrix = align2ref(non_boundary_points)
147 150
         
148 151
         # 创建布尔掩码,初始为 True
149 152
         mask = np.ones(len(z_raw_xy), dtype=bool)
@@ -154,15 +157,19 @@ def main(config_path, img_folder):
154 157
             mask &= ~np.all(z_raw_xy == boundary_point[:2], axis=1)
155 158
         
156 159
         # 使用掩码过滤出非边界点
157
-        non_boundary_points = z_raw[mask]
158
-        z_raw_aligned = non_boundary_points @ rotation_matrix.T
159
-        #z_raw_aligned[:,2] = z_raw_aligned[:,2] - np.mean(z_raw_aligned[:, 2])
160
+        non_boundary_points = z_raw[mask] 
161
+        # z_raw_aligned = non_boundary_points @ rotation_matrix.T
162
+        # z_raw_aligned[:,2] = z_raw_aligned[:,2] - np.mean(z_raw_aligned[:, 2])
163
+
164
+        z_raw_aligned = non_boundary_points
160 165
 
161 166
         #non_boundary_points = smoothed
162 167
         write_point_cloud(os.path.join(img_folder, str(i) + '_cloudpoint.txt'), np.round(z_raw_aligned[:, 0]), np.round(z_raw_aligned[:, 1]),  1000*z_raw_aligned[:, 2])
168
+        np.savetxt(os.path.join(img_folder, str(i) + '_gradient.txt'), gradient_xy, fmt='%.10f', delimiter=',')
163 169
         total_cloud_point = np.vstack([total_cloud_point, np.column_stack((z_raw_aligned[:, 0], z_raw_aligned[:, 1], 1000*z_raw_aligned[:, 2]))])
170
+        total_gradient = np.vstack([total_gradient, np.column_stack((gradient_xy[:, 0], gradient_xy[:, 1], gradient_xy[:, 2], gradient_xy[:, 3]))])
164 171
     
165
-    if debug:
172
+    if 0:
166 173
         fig = plt.figure()
167 174
         ax = fig.add_subplot(111, projection='3d')
168 175
 
@@ -178,7 +185,7 @@ def main(config_path, img_folder):
178 185
         ax.set_xlabel('X (mm)')
179 186
         ax.set_ylabel('Y (mm)')
180 187
         ax.set_zlabel('Z (mm)')
181
-        ax.set_title('3D Point Cloud Visualization')
188
+        ax.set_title('3D Point Cloud Visualization gradient')
182 189
         plt.show()
183 190
 
184 191
         # fig = plt.figure()
@@ -186,7 +193,7 @@ def main(config_path, img_folder):
186 193
         # smoothed_total = smooth_pcl(total_cloud_point, 3)
187 194
         #  # 提取 x, y, z 坐标
188 195
         # x_vals = smoothed_total[:, 0]
189
-        # y_vals = smoothed_total[:, 1]
196
+        # y_vals = smoothed_total[:, 1] 
190 197
         # z_vals = smoothed_total[:, 2]
191 198
 
192 199
         # # 绘制3D点云
@@ -207,11 +214,14 @@ def main(config_path, img_folder):
207 214
     post_process_start = time.time()
208 215
     total_cloud_point[:,0] = np.round(total_cloud_point[:,0])
209 216
     total_cloud_point[:,1] = np.round(total_cloud_point[:,1])
210
-    fitted_points = post_process(total_cloud_point, debug=0)
211
-    align_fitted, _= align2ref(fitted_points)
217
+    #fitted_points = post_process(total_cloud_point, debug=0)
218
+    fitted_points = post_process_with_grad(img_folder, 0)
219
+    #fitted_points = total_cloud_point
220
+    #align_fitted, _ = align2ref(fitted_points)
221
+    align_fitted = fitted_points
212 222
     
213 223
     write_point_cloud(os.path.join(img_folder, 'cloudpoint.txt'), np.round(align_fitted[:, 0]-np.mean(align_fitted[:, 0])), np.round(align_fitted[:, 1]-np.mean(align_fitted[:, 1])), align_fitted[:,2]-np.min(align_fitted[:,2]))
214
-    if debug:
224
+    if 0:
215 225
         fig = plt.figure()
216 226
         ax = fig.add_subplot(111, projection='3d')
217 227
 
@@ -248,13 +258,30 @@ def main(config_path, img_folder):
248 258
 
249 259
 
250 260
 
261
+
262
+
251 263
 if __name__ == '__main__':
252 264
     config_path = 'config\\cfg_3freq_wafer.json'
253
-    img_folder = 'D:\\data\\four_cam\\1008_storage\\20241009163255262'
265
+    #img_folder = 'D:\\data\\four_cam\\1008_storage\\pingjing_20241017092315228\\'   #'D:\\data\\four_cam\\betone_1011\\20241011142348762-1'
266
+   # img_folder = 'D:\\huchao\\inspect_server_202409241013_py\\storage\\20241023193453708\\'
267
+    img_folder = 'D:\\data\\four_cam\\betone_1011\\20241011144821292-4\\'
254 268
     json_path = os.path.join(img_folder, 'result.json')
255 269
 
270
+    # 20241011142348762-1  20241011142901251-2   20241011143925746-3   20241011144821292-4 
271
+    #     0.60                0.295                0.221                  0.346
272
+
273
+    # x max =  653.5925
274
+    # y max =  692.1735648
275
+    # x max =  251.0775
276
+    # y max =  293.05856482
277
+    # x max =  184.7275
278
+    # y max =  239.00919669
279
+    # x max =  342.2525
280
+    # y max =  386.92087185
281
+
256 282
     pmdstart(config_path, img_folder)
257
-    point_cloud_path = os.path.join(img_folder, 'cloudpoint.txt')
258
-    theta_notch = 0
259
-    #get_eval_result(point_cloud_path, json_path, theta_notch, 0)
283
+    #fitted_points = post_process_with_grad(img_folder, 1)
284
+
285
+    
286
+    
260 287
     

+ 13 - 5
src/calibration/get_camera_params.py

@@ -7,7 +7,6 @@ from aprilgrid import Detector
7 7
 # 读取文件夹中的所有棋盘格图像
8 8
 # images = glob.glob('calibration/world_calibration/*.bmp')
9 9
 
10
-
11 10
 def show_detect_result(img, detections):
12 11
     colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0)]
13 12
     for detection in detections:
@@ -26,6 +25,18 @@ def show_detect_result(img, detections):
26 25
     cv2.waitKey(0)
27 26
     cv2.destroyAllWindows()
28 27
 
28
+
29
+def validate_corners(corners, image_shape):
30
+    """确保角点在图像有效范围内"""
31
+    h, w = image_shape
32
+    for corner in corners:
33
+        x, y = corner.ravel()
34
+        if not (0 <= x < w and 0 <= y < h):
35
+            print(f"Invalid corner: ({x}, {y}) is out of image bounds.")
36
+            return False
37
+    return True
38
+
39
+
29 40
 # 生成 AprilGrid 标定板的 3D 物体坐标,并考虑标签之间的间隙
30 41
 def generate_3d_points(grid_size, tag_size, tag_spacing):
31 42
     """
@@ -81,10 +92,7 @@ def calibrate_world(calibration_images_path, cam_id, world_chessboard_size=(6,6)
81 92
         ax.set_zlabel('Z (mm)')
82 93
         ax.set_title('3D Visualization of AprilGrid Points')
83 94
         plt.show()
84
-    #print('object_point_single = ', object_point_single)
85
-    # print('cam_id = ', calibration_path + 'cam_id_' + str(cam_id) + '*.bmp')
86
-    # calibration_images_path = glob.glob(calibration_path + 'cam_id_' + str(cam_id) + '*.bmp')
87
-    # print(calibration_path + 'cam_id_' + str(cam_id)+'*.bmp')
95
+    
88 96
     # 用于保存所有图像的检测到的角点和 ID
89 97
     all_corners = []
90 98
     all_object_points = []

+ 2 - 2
src/calibration/get_screen_params.py

@@ -24,8 +24,8 @@ def calibrate_screen(screen_img_path, cam_mtx, cam_dist, chessboard_size, square
24 24
         img = cv2.imdecode(image_data, cv2.IMREAD_COLOR)
25 25
        
26 26
         # import pdb; pdb.set_trace()
27
-        #dst = cv2.undistort(img, cam_mtx, cam_dist, None, None)
28
-        gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
27
+        dst = cv2.undistort(img, cam_mtx, cam_dist, None, None)
28
+        gray = cv2.cvtColor(dst, cv2.COLOR_BGR2GRAY)
29 29
         # alpha = 3  # 对比度保持不变
30 30
         # beta = 0    # 增加亮度
31 31
         # bright_image = cv2.convertScaleAbs(gray, alpha=alpha, beta=beta)

+ 2 - 1
src/eval.py

@@ -207,7 +207,8 @@ def get_eval_result(pointcloud_path, json_path, theta_notch, debug):
207 207
     
208 208
     x = data[:, 0]
209 209
     y = data[:, 1]
210
-    z = data[:, 2]
210
+    z = 1000 * data[:, 2]
211
+    
211 212
     max_z = max(z)
212 213
     min_z = min(z)
213 214
     pv = max_z - min_z

+ 26 - 9
src/recons.py

@@ -325,8 +325,8 @@ def reconstruction_cumsum(world_points, camera_points, screen_points, save_path,
325 325
 
326 326
     # 计算反射光线向量
327 327
     reflection_vector = np.array([world_points[:, 0] - screen_points[:, 0],
328
-                                world_points[:, 1] - screen_points[:, 1],
329
-                                world_points[:, 2] - screen_points[:, 2]])
328
+                                  world_points[:, 1] - screen_points[:, 1],
329
+                                  world_points[:, 2] - screen_points[:, 2]])
330 330
     
331 331
     reflection_vector = reflection_vector / np.linalg.norm(reflection_vector, axis=0)
332 332
 
@@ -335,13 +335,20 @@ def reconstruction_cumsum(world_points, camera_points, screen_points, save_path,
335 335
     normal_vector = normal_vector / np.linalg.norm(normal_vector, axis=0)
336 336
  
337 337
     normal_vector = np.nan_to_num(normal_vector, nan=0.0)
338
+
339
+    #print('normal_vector = ', normal_vector[0])
340
+
341
+
338 342
     # 计算法向量在X方向的梯度
339 343
     gradient_x = normal_vector[0]
340 344
 
341 345
     # 计算法向量在Y方向的梯度
342 346
     gradient_y = normal_vector[1]
343
-    # gradient_x = np.zeros(gradient_x.shape)
344
-    # gradient_y = np.zeros(gradient_x.shape)
347
+    #gradient_x = np.zeros(gradient_x.shape)
348
+    #gradient_y = np.zeros(gradient_x.shape)
349
+
350
+    #gradient_x = gradient_x - np.mean(gradient_x)
351
+    #gradient_y = gradient_y - np.mean(gradient_y)
345 352
     # import pdb
346 353
     # pdb.set_trace()
347 354
     # 计算z值,假设初始z值为0
@@ -350,6 +357,10 @@ def reconstruction_cumsum(world_points, camera_points, screen_points, save_path,
350 357
     
351 358
     #z_raw = poisson_recons(x, y, gradient_x, gradient_y)
352 359
     z_raw = poisson_recons_with_smoothed_gradients(x, y, gradient_x, gradient_y)
360
+
361
+    #print('gradient_x  = ', x.shape, y.shape, gradient_x.shape, gradient_y.shape)
362
+
363
+    gradient_xy = np.column_stack((x, y, gradient_x, gradient_y))
353 364
     
354 365
        # z_raw = least_squares_recons(x, y, gradient_x, gradient_y)
355 366
         # import pdb; pdb.set_trace()
@@ -365,15 +376,17 @@ def reconstruction_cumsum(world_points, camera_points, screen_points, save_path,
365 376
         #print('-- nanmean after alignment: mean - {}; std - {}'.format(np.nanmean(points_aligned[:, 2]), np.nanstd(points_aligned[:, 2])))
366 377
     if smooth:
367 378
         #print("-- point cloud is being smoothed.")
368
-        points_smoothed = smooth_pcl(points_aligned, 3)
379
+        points_smoothed = None
380
+        #points_smoothed = smooth_pcl(points_aligned, 3)
369 381
         # print('raw_shape = ', raw_data.shape)
370 382
         # print('smoothed_shape = ', points_smoothed.shape)
371 383
         # print('raw min max = ', np.min(raw_data[:,0]), np.max(raw_data[:,0]), np.min(raw_data[:,1]), np.max(raw_data[:,1]))
372 384
         # print('smoothed min max = ', np.min(points_smoothed[:,0]), np.max(points_smoothed[:,0]), np.min(points_smoothed[:,1]), np.max(points_smoothed[:,1]))
373 385
     if denoise:
374
-        z = points_aligned[:, 2]
375
-        a, b, c = fit_plane(x, y, z)
376
-        points_denoise = remove_noise(x, y, z, a, b, c, thresh=0.01)
386
+        points_denoise = None
387
+        # z = points_aligned[:, 2]
388
+        # a, b, c = fit_plane(x, y, z)
389
+        # points_denoise = remove_noise(x, y, z, a, b, c, thresh=0.01)
377 390
 
378 391
     if debug:
379 392
         #fig, axes = plt.subplots(1, 2, figsize=(12, 6))
@@ -438,4 +451,8 @@ def reconstruction_cumsum(world_points, camera_points, screen_points, save_path,
438 451
 
439 452
    
440 453
     
441
-    return np.column_stack((x, y, z_raw)), points_aligned, points_smoothed, points_denoise, 
454
+    return np.column_stack((x, y, z_raw)), points_aligned, points_smoothed, points_denoise, gradient_xy
455
+
456
+
457
+
458
+

+ 511 - 185
src/utils.py

@@ -19,7 +19,12 @@ from sklearn.linear_model import LinearRegression
19 19
 from scipy.interpolate import Rbf
20 20
 from numpy.linalg import lstsq
21 21
 from scipy.optimize import curve_fit
22
-
22
+from aprilgrid import Detector
23
+from scipy.sparse.linalg import spsolve
24
+from scipy.spatial import Delaunay
25
+from scipy.sparse.linalg import lsqr
26
+from collections import defaultdict
27
+from scipy.sparse import lil_matrix
23 28
 
24 29
 
25 30
 
@@ -179,6 +184,7 @@ def fit_sector(contour_points, grid_spacing, erosion_pixels):
179 184
     kernel = np.ones((erosion_pixels, erosion_pixels), np.uint8)
180 185
     eroded_img = cv2.erode(img, kernel, iterations=1)
181 186
 
187
+
182 188
     # 提取腐蚀后的轮廓
183 189
     contours, _ = cv2.findContours(eroded_img, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
184 190
     if len(contours) == 0:
@@ -576,9 +582,7 @@ def get_white_mask(white_path, bin_thresh=15, debug=False):
576 582
         max_index, max_contour = max(enumerate(valid_contours), key=lambda x: cv2.boundingRect(x[1])[2] * cv2.boundingRect(x[1])[3]) 
577 583
         cv2.drawContours(mask, [max_contour], -1, (255, 255, 255), thickness=cv2.FILLED)
578 584
     if debug:
579
-        cv2.namedWindow('erode_thresh_img',0)
580
-        cv2.imshow('erode_thresh_img', erode_thresh_img)
581
-        cv2.waitKey(0)
585
+       
582 586
    
583 587
         cv2.namedWindow('mask',0)
584 588
         cv2.imshow('mask', mask)
@@ -788,23 +792,58 @@ def curve_fit_gaussian(points):
788 792
     y = points[:, 1]
789 793
     z = points[:, 2]
790 794
 
791
-    # 使用curve_fit来拟合高斯曲面
792
-    # 初始猜测参数 A, x0, y0, sigma
793
-    initial_guess = [1, np.mean(x), np.mean(y), 1]
794
-    
795
-    # 使用 curve_fit 进行非线性最小二乘拟合
796
-    popt, pcov = curve_fit(gaussian_surface, (x, y), z, p0=initial_guess)
797
-    
798
-    # 拟合的参数
795
+    # 初始参数猜测:A=max(z), x0=mean(x), y0=mean(y), sigma=较大初始值
796
+    initial_guess = [np.max(z), np.mean(x), np.mean(y), np.std(x) / 2]
797
+
798
+    # 进行非线性最小二乘拟合
799
+    popt, _ = curve_fit(gaussian_surface, (x, y), z, p0=initial_guess)
800
+
801
+    # 提取拟合参数
799 802
     A, x0, y0, sigma = popt
800
-    print(f"拟合参数: A={A}, x0={x0}, y0={y0}, sigma={sigma}")
801 803
     
802
-    # 根据拟合的高斯曲面计算新的 z 值
803
-    z_fitted = gaussian_surface((x, y), A, x0, y0, sigma)
804
+    #print(f"拟合参数: A={A}, x0={x0}, y0={y0}, sigma={sigma}")
805
+    x_mean = np.mean(x)
806
+    y_mean = np.mean(y)
807
+    x0 = x_mean
808
+    y0 = y_mean
809
+    sigma = 2 * sigma
810
+    #print(f"拟合参数1: A={A}, x0={x0}, y0={y0}, sigma={sigma}")
804 811
     
812
+
813
+    # 生成平滑的 z_fitted
814
+    z_fitted = gaussian_surface((x, y), A, x0, y0, sigma)
815
+
816
+    # 调整 z_fitted,使其最大值与原始 z 的最大值一致
817
+    z_fitted = z_fitted / np.max(z_fitted) * np.max(z)
818
+    #print('np.max(z_fitted) = ', np.max(z_fitted))
819
+
805 820
     # 生成新的点云
806 821
     new_points = np.column_stack((x, y, z_fitted))
807 822
 
823
+    return new_points
824
+
825
+
826
+
827
+
828
+    # # 使用curve_fit来拟合高斯曲面
829
+    # # 初始猜测参数 A, x0, y0, sigma
830
+    # initial_guess = [1, np.mean(x), np.mean(y), 1]
831
+    
832
+    # # 使用 curve_fit 进行非线性最小二乘拟合
833
+    # popt, pcov = curve_fit(gaussian_surface, (x, y), z, p0=initial_guess)
834
+    
835
+    # # 拟合的参数
836
+    # A, x0, y0, sigma = popt
837
+    # print(f"拟合参数: A={A}, x0={x0}, y0={y0}, sigma={sigma}")
838
+    
839
+    # # 根据拟合的高斯曲面计算新的 z 值
840
+    # z_fitted = gaussian_surface((x, y), A, x0, y0, sigma)
841
+    
842
+    # # 生成新的点云
843
+    # new_points = np.column_stack((x, y, z_fitted))
844
+
845
+    # return new_points
846
+
808 847
     # 可视化原始点云和拟合的高斯曲面
809 848
     # fig = plt.figure()
810 849
     # ax = fig.add_subplot(111, projection='3d')
@@ -822,8 +861,9 @@ def curve_fit_gaussian(points):
822 861
     # ax.legend()
823 862
 
824 863
     # plt.show()
864
+    #return new_points
865
+    
825 866
     
826
-    return new_points
827 867
 
828 868
 
829 869
 def curve_fit_2(points):
@@ -921,6 +961,7 @@ def remove_duplicates(points):
921 961
     return new_points
922 962
 
923 963
 def post_process(points, debug):
964
+
924 965
     uniq_points = remove_duplicates(points)
925 966
     
926 967
     x = uniq_points[:,0]
@@ -935,111 +976,331 @@ def post_process(points, debug):
935 976
    
936 977
     return fitted_points
937 978
 
979
+
980
+
981
+def get_neighbors(tri, index):
982
+    """Helper function to get the neighbors of a point in the Delaunay triangulation."""
983
+    neighbors = set()
984
+    for simplex in tri.simplices:
985
+        if index in simplex:
986
+            neighbors.update(simplex)
987
+    neighbors.remove(index)  # Remove the point itself from its neighbors
988
+    return list(neighbors)
989
+
990
+
991
+def expand_to_rectangle_with_unit_spacing(x, y, x_gradient, y_gradient):
992
+    # 1. 计算最小外接矩形的边界
993
+    x_min, x_max = np.min(x), np.max(x)
994
+    y_min, y_max = np.min(y), np.max(y)
995
+
996
+    # 2. 根据边界计算矩形的宽度和高度,间隔为1
997
+    width = int(np.ceil(x_max - x_min)) + 1  # 确保包含所有点,并加1保证整数
998
+    height = int(np.ceil(y_max - y_min)) + 1
999
+
1000
+    # 3. 创建规则网格,间隔为1
1001
+    x_grid = np.arange(x_min, x_max + 1, 1)  # x坐标从x_min到x_max,间隔为1
1002
+    y_grid = np.arange(y_min, y_max + 1, 1)  # y坐标从y_min到y_max,间隔为1
1003
+    x_expand, y_expand = np.meshgrid(x_grid, y_grid)
1004
+
1005
+    # 4. 初始化梯度矩阵,填充为 0
1006
+    x_grad_expand = np.zeros_like(x_expand)
1007
+    y_grad_expand = np.zeros_like(y_expand)
1008
+
1009
+    # 5. 找到每个点在网格中的位置,插入对应的梯度
1010
+    for i in range(len(x)):
1011
+        # 找到对应的网格索引
1012
+        x_idx = int(x[i] - x_min)
1013
+        y_idx = int(y[i] - y_min)
1014
+        
1015
+        # 将对应点的梯度填入矩阵中
1016
+        x_grad_expand[y_idx, x_idx] = x_gradient[i]
1017
+        y_grad_expand[y_idx, x_idx] = y_gradient[i]
1018
+
1019
+    return x_grad_expand, y_grad_expand, x_expand, y_expand
1020
+
1021
+
1022
+
1023
+def remove_duplicates_4d_with_std_neighbor(points, std_multiplier=2):
1024
+    """
1025
+    去重并处理z1和z2值,如果z1或z2与全局均值差异大于标准差的倍数,则替换为相邻点的值。
1026
+    
1027
+    Parameters:
1028
+    points : np.ndarray
1029
+        四维点集 (x, y, z1, z2)。
1030
+    std_multiplier : float
1031
+        用于判断异常值的标准差倍数,默认为2倍。
1032
+    
1033
+    Returns:
1034
+    new_points : np.ndarray
1035
+        处理后的去重点集。
1036
+    """
938 1037
     
939
-    plane_points = fit_plane_and_adjust(close_point, 10)
1038
+    # 使用 defaultdict 来保存 (x, y) 键,并存储 z1 和 z2 的列表
1039
+    unique_points = defaultdict(lambda: [[], []])  # 第一个列表存 z1,第二个列表存 z2
940 1040
     
941
-    z_outliers_removed = remove_outliers(smoothed_z, threshold=30.0)
942
-    z_final = smooth_z_with_std(z_outliers_removed, 5)
1041
+    # 遍历点,按 (x, y) 键存储 z1 和 z2 的值
1042
+    for point in points:
1043
+        x, y, z1, z2 = point
1044
+        unique_points[(x, y)][0].append(z1)  # 将 z1 加入列表
1045
+        unique_points[(x, y)][1].append(z2)  # 将 z2 加入列表
1046
+    
1047
+    # 计算全局 z1 和 z2 的均值和标准差
1048
+    all_z1 = points[:, 2]
1049
+    all_z2 = points[:, 3]
1050
+    z1_mean_global = np.mean(all_z1)
1051
+    z2_mean_global = np.mean(all_z2)
1052
+    z1_std_global = np.std(all_z1)
1053
+    z2_std_global = np.std(all_z2)
1054
+
1055
+    # 准备存储新点
1056
+    new_points = []
1057
+    
1058
+    # 初始化相邻点值为全局均值,保证第一次有默认值
1059
+    previous_z1 = z1_mean_global
1060
+    previous_z2 = z2_mean_global
1061
+    
1062
+    # 对每个 (x, y) 进行处理
1063
+    for (x, y), (z1_list, z2_list) in unique_points.items():
1064
+        # 初始化最小距离和对应的 z1, z2
1065
+        min_distance = float('inf')
1066
+        best_z1 = None
1067
+        best_z2 = None
1068
+        
1069
+        # 遍历该 (x, y) 的所有 z1 和 z2 值
1070
+        for z1, z2 in zip(z1_list, z2_list):
1071
+            # 检查 z1 和 z2 是否超出标准差的倍数,若超出则替换为相邻点的值
1072
+            if abs(z1 - z1_mean_global) > std_multiplier * z1_std_global:
1073
+                z1 = previous_z1  # 用相邻点的 z1 值替换
1074
+            if abs(z2 - z2_mean_global) > std_multiplier * z2_std_global:
1075
+                z2 = previous_z2  # 用相邻点的 z2 值替换
1076
+            
1077
+            # 计算与全局均值的距离
1078
+            distance = np.sqrt((z1 - z1_mean_global)**2 + (z2 - z2_mean_global)**2)
1079
+            
1080
+            # 找到与全局均值最接近的点
1081
+            if distance < min_distance:
1082
+                min_distance = distance
1083
+                best_z1 = z1
1084
+                best_z2 = z2
1085
+        
1086
+        # 更新相邻点值
1087
+        previous_z1 = best_z1
1088
+        previous_z2 = best_z2
1089
+        
1090
+        # 添加该点到新点列表
1091
+        new_points.append([x, y, best_z1, best_z2])
1092
+    
1093
+    # 转换为 numpy 数组
1094
+    new_points = np.array(new_points)
1095
+    
1096
+    return new_points
943 1097
 
1098
+
1099
+
1100
+def southwell_integrate_smooth(x_grad_expand, y_grad_expand, x_expand, y_expand, reference_point=(0, 0)):
1101
+    """
1102
+    Southwell 积分方法,用于根据梯度场重建标量场,加入平滑处理以保证结果连续。
1103
+    
1104
+    Parameters:
1105
+    x_grad_expand: numpy.ndarray
1106
+        x方向的梯度矩阵。
1107
+    y_grad_expand: numpy.ndarray
1108
+        y方向的梯度矩阵。
1109
+    x_expand: numpy.ndarray
1110
+        网格的x坐标矩阵。
1111
+    y_expand: numpy.ndarray
1112
+        网格的y坐标矩阵。
1113
+    reference_point: tuple
1114
+        起始积分点的坐标索引,默认为(0, 0)。
1115
+    
1116
+    Returns:
1117
+    Z_reconstructed: numpy.ndarray
1118
+        重建的标量场。
1119
+    """
1120
+   
1121
+    # 检查是否为二维数组
1122
+    H, W = x_grad_expand.shape
944 1123
     
945
-    if debug:
946
-        # 绘制3D点云
947
-        fig = plt.figure()
948
-        ax = fig.add_subplot(111, projection='3d')
949
-        ax.scatter(x, y, z, c=z, cmap='viridis', marker='o', s=2)
950
-        ax.set_title('3D Point Cloud with raw Points')
1124
+    # 初始化标量场,设为零
1125
+    Z_reconstructed = np.zeros((H, W))
1126
+    
1127
+    # 创建一个布尔矩阵,跟踪已处理的点
1128
+    visited = np.zeros((H, W), dtype=bool)
1129
+    
1130
+    # 从参考点开始,标量值为0
1131
+    ref_y, ref_x = reference_point
1132
+    visited[ref_y, ref_x] = True
1133
+    
1134
+    # 创建队列,用于遍历
1135
+    queue = [(ref_y, ref_x)]
1136
+    
1137
+    # 遍历队列中的点
1138
+    while queue:
1139
+        current_y, current_x = queue.pop(0)
1140
+        current_z = Z_reconstructed[current_y, current_x]
1141
+        
1142
+        # 处理四个方向的邻居 (上,下,左,右)
1143
+        neighbors = [
1144
+            (current_y - 1, current_x, y_grad_expand[current_y - 1, current_x] if current_y > 0 else 0),  # 上
1145
+            (current_y + 1, current_x, y_grad_expand[current_y, current_x] if current_y < H - 1 else 0),  # 下
1146
+            (current_y, current_x - 1, x_grad_expand[current_y, current_x - 1] if current_x > 0 else 0),  # 左
1147
+            (current_y, current_x + 1, x_grad_expand[current_y, current_x] if current_x < W - 1 else 0)   # 右
1148
+        ]
1149
+        
1150
+        # 遍历邻居点
1151
+        for ny, nx, grad in neighbors:
1152
+            if 0 <= ny < H and 0 <= nx < W and not visited[ny, nx]:
1153
+                # 通过邻居梯度方向的平均值来平滑积分
1154
+                if ny != current_y:  # y方向积分
1155
+                    neighbor_grad = (y_grad_expand[current_y, current_x] + grad) / 2  # 平滑处理
1156
+                    Z_reconstructed[ny, nx] = current_z + neighbor_grad * (y_expand[ny, nx] - y_expand[current_y, current_x])
1157
+                else:  # x方向积分
1158
+                    neighbor_grad = (x_grad_expand[current_y, current_x] + grad) / 2  # 平滑处理
1159
+                    Z_reconstructed[ny, nx] = current_z + neighbor_grad * (x_expand[ny, nx] - x_expand[current_y, current_x])
1160
+                
1161
+                # 标记为已访问
1162
+                visited[ny, nx] = True
1163
+                
1164
+                # 将邻居点加入队列
1165
+                queue.append((ny, nx))
1166
+    
1167
+    return Z_reconstructed
1168
+
1169
+
1170
+
1171
+def compute_divergence(x_grad_expand, y_grad_expand):
1172
+    """
1173
+    计算梯度场的散度(div_G),用于泊松重建。
1174
+    
1175
+    Parameters:
1176
+    x_grad_expand: numpy.ndarray
1177
+        x方向的梯度矩阵。
1178
+    y_grad_expand: numpy.ndarray
1179
+        y方向的梯度矩阵。
1180
+    
1181
+    Returns:
1182
+    div_G: numpy.ndarray
1183
+        梯度场的散度矩阵。
1184
+    """
1185
+    H, W = x_grad_expand.shape
1186
+    div_G = np.zeros((H, W))
951 1187
 
952
-        fig = plt.figure()
953
-        ax = fig.add_subplot(111, projection='3d')
954
-        ax.scatter(x, y, smoothed_z, c=smoothed_z, cmap='viridis', marker='o', s=2)
955
-        ax.set_title('3D Point Cloud with smoothed_z Points')
1188
+    # 计算 x 方向的梯度差分
1189
+    div_G[:, 1:] += x_grad_expand[:, 1:] - x_grad_expand[:, :-1]
956 1190
     
957
-        fig = plt.figure()
958
-        ax = fig.add_subplot(111, projection='3d')
959
-        ax.scatter(x, y, z_outliers_removed, c=z_outliers_removed, cmap='viridis')
960
-        ax.set_title('3D Point Cloud with remove_outliers Points')
1191
+    # 计算 y 方向的梯度差分
1192
+    div_G[1:, :] += y_grad_expand[1:, :] - y_grad_expand[:-1, :]
1193
+    
1194
+    return div_G
961 1195
 
962
-        fig = plt.figure()
963
-        ax = fig.add_subplot(111, projection='3d')
964
-        ax.scatter(x, y, z_final, c=z_final, cmap='viridis')
965
-        ax.set_title('3D Point Cloud with z_final Points')
966
-        ax.set_zlim(0,10)
967
-        
1196
+def poisson_reconstruct(x_grad_expand, y_grad_expand):
1197
+    """
1198
+    使用泊松重建方法重建标量场。
968 1199
     
969
-        plt.show()
1200
+    Parameters:
1201
+    x_grad_expand: numpy.ndarray
1202
+        x方向的梯度矩阵。
1203
+    y_grad_expand: numpy.ndarray
1204
+        y方向的梯度矩阵。
1205
+    
1206
+    Returns:
1207
+    Z_reconstructed: numpy.ndarray
1208
+        重建的标量场。
1209
+    """
1210
+    H, W = x_grad_expand.shape
1211
+    
1212
+    # 计算散度
1213
+    div_G = compute_divergence(x_grad_expand, y_grad_expand)
1214
+    
1215
+    # 构建泊松方程的离散化矩阵(稀疏矩阵)
1216
+    A = lil_matrix((H * W, H * W))
1217
+    b = div_G.flatten()
1218
+
1219
+    for y in range(H):
1220
+        for x in range(W):
1221
+            index = x + y * W
1222
+            
1223
+            if x == 0 or x == W - 1 or y == 0 or y == H - 1:
1224
+                # 边界点,直接固定为 0(Dirichlet 边界条件)
1225
+                A[index, index] = 1
1226
+                b[index] = 0
1227
+            else:
1228
+                # 内部点,应用泊松方程的离散形式
1229
+                A[index, index] = -4
1230
+                A[index, index - 1] = 1  # 左边的点
1231
+                A[index, index + 1] = 1  # 右边的点
1232
+                A[index, index - W] = 1  # 上边的点
1233
+                A[index, index + W] = 1  # 下边的点
970 1234
 
971
-    return z_final
1235
+    # 使用 scipy 的稀疏求解器来解泊松方程
1236
+    Z_flat = spsolve(A.tocsr(), b)
1237
+    Z_reconstructed = Z_flat.reshape((H, W))
972 1238
     
1239
+    return Z_reconstructed
973 1240
 
974 1241
 
975 1242
 
976
-def plot_camera(ax, rotation_matrix, translation_vector, camera_id, scale=1.0, color='r'):
977
-    """
978
-    绘制单个相机的三维表示:位置、方向和视野
979
-    R: 3x3旋转矩阵 (从世界坐标系到相机坐标系)
980
-    t: 3x1平移向量 (相机位置)
981
-    scale: 控制相机大小的比例因子
982
-    color: 相机视野的颜色
983
-    """
984
-    # 相机的中心
985
-    camera_center = -rotation_matrix.T @ translation_vector
986
-    print('camera_center = ', camera_center)
987
-    camera_center = np.squeeze(camera_center)  # 确保 camera_center 是 1D 数组
988
-    #print('camera_center = ', camera_center)
1243
+def post_process_with_grad(img_folder, debug=False):
1244
+    total_point_grad = np.empty((0, 4))
1245
+    for i in range(4):
1246
+        txt_file = img_folder + str(i) + '_gradient.txt'
1247
+        total_point_grad = np.vstack([total_point_grad, np.loadtxt(os.path.join(txt_file), delimiter=',')])
1248
+     
1249
+    print(total_point_grad.shape)
989 1250
 
990
-    # 定义相机的四个角点在相机坐标系中的位置
991
-    camera_points = np.array([
992
-        [0, 0, 0],  # 相机中心
993
-        [1, 1, 2],  # 视野的左上角
994
-        [1, -1, 2],  # 视野的左下角
995
-        [-1, -1, 2],  # 视野的右下角
996
-        [-1, 1, 2]  # 视野的右上角
997
-    ]) * scale
1251
+    total_point_grad = remove_duplicates_4d_with_std_neighbor(total_point_grad, 3)
998 1252
 
999
-    # 将相机坐标系下的点转换到世界坐标系
1000
-    camera_points_world = (rotation_matrix.T @ camera_points.T).T + camera_center.T
1001
-    # 绘制相机位置(红色圆点表示相机中心)
1002
-    ax.scatter(camera_center[0], camera_center[1], camera_center[2], color=color, marker='o', s=100)
1253
+    print(total_point_grad.shape) 
1003 1254
     
1004
-    # 绘制相机的 X、Y、Z 坐标轴
1005
-    axis_length = scale * 3  # 坐标轴箭头长度
1006
-    x_axis = rotation_matrix[:, 0]  # X 轴方向
1007
-    y_axis = rotation_matrix[:, 1]  # Y 轴方向
1008
-    z_axis = rotation_matrix[:, 2]  # Z 轴方向
1255
+    x = total_point_grad[:,0]
1256
+    y = total_point_grad[:,1]
1009 1257
 
1010
-    # X 轴箭头(红色)
1011
-    ax.quiver(camera_center[0], camera_center[1], camera_center[2],
1012
-              x_axis[0], x_axis[1], x_axis[2],
1013
-              length=axis_length, color='r', arrow_length_ratio=0.5, label='X axis')
1258
+    x_gradient = total_point_grad[:,2]
1259
+    y_gradient = total_point_grad[:,3]
1014 1260
 
1015
-    # Y 轴箭头(绿色)
1016
-    ax.quiver(camera_center[0], camera_center[1], camera_center[2],
1017
-              y_axis[0], y_axis[1], y_axis[2],
1018
-              length=axis_length, color='g', arrow_length_ratio=0.5, label='Y axis')
1261
+    mean_x_grad = np.mean(x_gradient)
1262
+    mean_y_grad = np.mean(y_gradient)
1019 1263
 
1020
-    # Z 轴箭头(蓝色)
1021
-    ax.quiver(camera_center[0], camera_center[1], camera_center[2],
1022
-              z_axis[0], z_axis[1], z_axis[2],
1023
-              length=axis_length, color='b', arrow_length_ratio=0.5, label='Z axis')
1264
+    x_gradient =  (x_gradient - mean_x_grad)
1265
+    y_gradient =  (y_gradient - mean_y_grad)
1266
+    
1267
+    # 使用最小外接矩形,并填充梯度
1268
+    x_grad_expand, y_grad_expand, x_expand, y_expand = expand_to_rectangle_with_unit_spacing(x, y, x_gradient, y_gradient)   
1269
+    #print(x_grad_expand.shape, y_grad_expand.shape, x_expand.shape, y_expand.shape)
1270
+    # 调用 Southwell 重建函数
1271
+    #Z_reconstructed = southwell_integrate_smooth(x_grad_expand, y_grad_expand, x_expand, y_expand, (0, 0))
1024 1272
 
1025
-    # 绘制相机中心
1026
-    ax.scatter(camera_center[0], camera_center[1], camera_center[2], color=color, marker='o', s=100, label="Camera")
1027
-    ax.text(camera_center[0], camera_center[1], camera_center[2], f'Cam {camera_id}', color='black', fontsize=12)
1273
+    Z_reconstructed = poisson_reconstruct(x_grad_expand, y_grad_expand)
1274
+    
1275
+    z1_lookup = {(x1, y1): z1 for x1, y1, z1 in np.column_stack((x_expand.reshape(-1,1), y_expand.reshape(-1,1), Z_reconstructed.reshape(-1,1)))}
1028 1276
 
1029
-    #绘制相机视野四个角点与相机中心的连线
1030
-    for i in range(1, 5):
1031
-        ax.plot([camera_center[0], camera_points_world[i, 0]],
1032
-                [camera_center[1], camera_points_world[i, 1]],
1033
-                [camera_center[2], camera_points_world[i, 2]], color=color)
1277
+    z1_values = np.array([z1_lookup.get((x, y), np.nan) for x, y in np.column_stack((x, y))])
1278
+    fitted_points = post_process(np.column_stack((x,y,z1_values)), debug=0)
1034 1279
 
1035
-    # 绘制相机的四边形视野
1036
-    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]],
1037
-            [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]],
1038
-            [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)
1280
+    # 可视化梯度场
1281
+    if debug:
1282
+        fig = plt.figure()
1283
+        ax = fig.add_subplot(111, projection='3d')
1284
+
1285
+        #提取 x, y, z 坐标
1286
+        x_vals = fitted_points[:,0]
1287
+        y_vals = fitted_points[:,1]
1288
+        z_vals = fitted_points[:,2]
1289
+        # x_vals = x
1290
+        # y_vals = y
1291
+        # z_vals = z1_values
1292
+        # 绘制3D点云
1293
+        ax.scatter(x_vals, y_vals, z_vals, c=z_vals, cmap='viridis', marker='o')
1294
+
1295
+        # 设置轴标签和标题
1296
+        ax.set_xlabel('X (mm)') 
1297
+        ax.set_ylabel('Y (mm)')
1298
+        ax.set_zlabel('Z (mm)')
1299
+        ax.set_title('post_process 3D Point Cloud Visualization')
1300
+        plt.show()
1301
+    
1302
+    return fitted_points
1039 1303
 
1040
-    # # 添加相机方向箭头
1041
-    # ax.quiver(camera_center[0], camera_center[1], camera_center[2],
1042
-    #           rotation_matrix[0, 2], rotation_matrix[1, 2], rotation_matrix[2, 2], length=scale, color='g', arrow_length_ratio=1, label="Direction")
1043 1304
 
1044 1305
 
1045 1306
 def transform_image_to_cam0(img, K_cam_i, R_cam_i, T_cam_i, K_cam0, R_cam0, T_cam0, depth_map):
@@ -1089,60 +1350,6 @@ def transform_image_to_cam0(img, K_cam_i, R_cam_i, T_cam_i, K_cam0, R_cam0, T_ca
1089 1350
     return output_img
1090 1351
 
1091 1352
 
1092
-
1093
-def camera_calibrate_vis():
1094
-    img_folder = 'D:\\data\\four_cam\\0927_storage\\20240927161124014'
1095
-    config_path = 'D:\\code\\pmd-python\\config\\cfg_3freq_wafer.json'
1096
-    cfg = json.load(open(config_path, 'r'))
1097
-    with open(os.path.join('D:\\code\\pmd-python\\config\\', cfg['cam_params']), 'rb') as pkl_file:
1098
-        cam_params = pickle.load(pkl_file)
1099
-    fig = plt.figure()
1100
-    ax = fig.add_subplot(111, projection='3d')
1101
-
1102
-    # 绘制每个相机
1103
-    for ii in range(4):
1104
-        plot_camera(ax, cam_params[ii]['rotation_matrix'], cam_params[ii]['translation_vector'], ii, scale=40, color='r')
1105
-
1106
-    # 绘制物体 (假设是一个简单的平面物体,位于 z = 0 平面上)
1107
-    plane_x, plane_y = np.meshgrid(np.linspace(-500, 500, 10), np.linspace(-500, 500, 10))
1108
-    plane_z = np.zeros_like(plane_x)  # z = 0 的平面
1109
-    ax.plot_surface(plane_x, plane_y, plane_z, color='blue', alpha=0.3)
1110
-
1111
-    # 设置轴的范围和标签
1112
-    ax.set_xlabel('X axis')
1113
-    ax.set_ylabel('Y axis')
1114
-    ax.set_zlabel('Z axis')
1115
-    ax.set_xlim([-50, 300])
1116
-    ax.set_ylim([-50, 300])
1117
-    ax.set_zlim([0, 500])
1118
-
1119
-    K_cam0 = cam_params[0]['camera_matrix']
1120
-    R_cam0 = cam_params[0]['rotation_matrix']
1121
-    T_cam0 = cam_params[0]['translation_vector']
1122
-
1123
-    for i in range(4):
1124
-        img_path = img_folder + '\\' + str(i) + '_frame_24.bmp'
1125
-        img = cv2.imread(img_path, 0)
1126
-        print('max gray = ', np.max(img))
1127
-        if i > 0:
1128
-            img = cv2.imread(img_path, 0)
1129
-            K_cam_i = cam_params[i]['camera_matrix']
1130
-            R_cam_i = cam_params[i]['rotation_matrix']
1131
-            T_cam_i = cam_params[i]['translation_vector']
1132
-            # 示例图像和深度图
1133
-            depth_map = img
1134
-
1135
-            # 将图像转换到 cam0 坐标系下
1136
-            #aligned_img = transform_image_to_cam0(img, K_cam_i, R_cam_i, T_cam_i, K_cam0, R_cam0, T_cam0, depth_map)
1137
-            
1138
-            # cv2.namedWindow('Aligned Image', 0)
1139
-            # cv2.imshow('Aligned Image', aligned_img)
1140
-            # cv2.waitKey(0)
1141
-            # cv2.destroyAllWindows()
1142
-    
1143
-    plt.show()
1144
-
1145
-
1146 1353
 def rename_gamma():
1147 1354
     img_path = 'D:\\code\\Gamma\\Gamma\\cam3_original_pictures\\'
1148 1355
     i = 0
@@ -1303,8 +1510,6 @@ def apply_local_average_smoothing(z_reconstructed, window_size=3):
1303 1510
 
1304 1511
 
1305 1512
 
1306
-
1307
-
1308 1513
 def average_duplicate_points(points):
1309 1514
     """
1310 1515
     处理点云,计算每个 (x, y) 坐标中所有 z 值的均值。
@@ -1323,6 +1528,7 @@ def average_duplicate_points(points):
1323 1528
     
1324 1529
     return unique_points[['x', 'y', 'z']].to_numpy()
1325 1530
 
1531
+
1326 1532
 def remove_duplicate_points(points):
1327 1533
     """
1328 1534
     处理点云,保留每个 (x, y) 坐标中与全局 z 值均值最近的一个 z 值。
@@ -1446,6 +1652,135 @@ def laplacian_smoothing(points, z_values, iterations=10, alpha=0.1):
1446 1652
 
1447 1653
 
1448 1654
 
1655
+def project_points_to_camera(P_world, K, R, t):
1656
+    """
1657
+    将世界坐标系中的 3D 点投影到相机的图像平面。
1658
+    
1659
+    Parameters:
1660
+    P_world: numpy.ndarray
1661
+        世界坐标系中的 3D 点,形状为 (N, 3)。
1662
+    K: numpy.ndarray
1663
+        相机内参矩阵,形状为 (3, 3)。
1664
+    R: numpy.ndarray
1665
+        相机外参的旋转矩阵,形状为 (3, 3)。
1666
+    t: numpy.ndarray
1667
+        相机外参的平移向量,形状为 (3, 1)。
1668
+    
1669
+    Returns:
1670
+    p_image: numpy.ndarray
1671
+        投影到图像平面的 2D 点,形状为 (N, 2)。
1672
+    """
1673
+    # 将世界坐标系中的点转换为相机坐标系
1674
+    P_cam = R @ P_world.T + t
1675
+    P_cam = P_cam.T
1676
+    
1677
+    # 将相机坐标系中的点投影到图像平面
1678
+    P_image_homogeneous = K @ P_cam.T
1679
+    P_image_homogeneous = P_image_homogeneous.T
1680
+    
1681
+    # 转换为 2D 图像坐标
1682
+    p_image = P_image_homogeneous[:, :2] / P_image_homogeneous[:, 2][:, np.newaxis]
1683
+    
1684
+    return p_image
1685
+
1686
+def cross_validate_camera_pair(P_world, K_A, R_A, t_A, K_B, R_B, t_B, p_B_measured):
1687
+    """
1688
+    验证相机 A 的标定点在相机 B 中的投影误差。
1689
+    
1690
+    Parameters:
1691
+    P_world: numpy.ndarray
1692
+        标定板上的 3D 点的世界坐标,形状为 (N, 3)。
1693
+    K_A, R_A, t_A: numpy.ndarray
1694
+        相机 A 的内参矩阵、旋转矩阵和平移向量。
1695
+    K_B, R_B, t_B: numpy.ndarray
1696
+        相机 B 的内参矩阵、旋转矩阵和平移向量。
1697
+    p_B_measured: numpy.ndarray
1698
+        相机 B 实际测量到的标定点,形状为 (N, 2)。
1699
+    
1700
+    Returns:
1701
+    reprojection_error: float
1702
+        计算得到的投影误差。
1703
+    """
1704
+    # 将 P_world 投影到相机 B 的图像平面
1705
+    R_BA = R_B @ np.linalg.inv(R_A)  # 相机 A 和相机 B 的旋转矩阵关系
1706
+    t_BA = t_B - R_B @ np.linalg.inv(R_A) @ t_A  # 相机 A 和相机 B 的平移向量关系
1707
+    p_B_projected = project_points_to_camera(P_world, K_B, R_BA, t_BA)
1708
+
1709
+    # 计算投影误差
1710
+    reprojection_error = np.mean(np.linalg.norm(p_B_projected - p_B_measured, axis=1))
1711
+    
1712
+    return reprojection_error
1713
+
1714
+def plot_camera(ax, rotation_matrix, translation_vector, camera_id, scale=1.0, color='r'):
1715
+    """
1716
+    绘制单个相机的三维表示:位置、方向和视野
1717
+    R: 3x3旋转矩阵 (从世界坐标系到相机坐标系)
1718
+    t: 3x1平移向量 (相机位置)
1719
+    scale: 控制相机大小的比例因子
1720
+    color: 相机视野的颜色
1721
+    """
1722
+    # 相机的中心
1723
+    camera_center = -rotation_matrix.T @ translation_vector
1724
+    #print('camera_center = ', camera_center)
1725
+    camera_center = np.squeeze(camera_center)  # 确保 camera_center 是 1D 数组
1726
+    #print('camera_center = ', camera_center)
1727
+
1728
+    # 定义相机的四个角点在相机坐标系中的位置
1729
+    camera_points = np.array([
1730
+        [0, 0, 0],  # 相机中心
1731
+        [1, 1, 2],  # 视野的左上角
1732
+        [1, -1, 2],  # 视野的左下角
1733
+        [-1, -1, 2],  # 视野的右下角
1734
+        [-1, 1, 2]  # 视野的右上角
1735
+    ]) * scale
1736
+
1737
+    # 将相机坐标系下的点转换到世界坐标系
1738
+    camera_points_world = (rotation_matrix.T @ camera_points.T).T + camera_center.T
1739
+    # 绘制相机位置(红色圆点表示相机中心)
1740
+    ax.scatter(camera_center[0], camera_center[1], camera_center[2], color=color, marker='o', s=100)
1741
+    
1742
+    # 绘制相机的 X、Y、Z 坐标轴
1743
+    axis_length = scale * 3  # 坐标轴箭头长度
1744
+    x_axis = rotation_matrix[:, 0]  # X 轴方向
1745
+    y_axis = rotation_matrix[:, 1]  # Y 轴方向
1746
+    z_axis = rotation_matrix[:, 2]  # Z 轴方向
1747
+
1748
+    # X 轴箭头(红色)
1749
+    ax.quiver(camera_center[0], camera_center[1], camera_center[2],
1750
+              x_axis[0], x_axis[1], x_axis[2],
1751
+              length=axis_length, color='r', arrow_length_ratio=0.5, label='X axis')
1752
+
1753
+    # Y 轴箭头(绿色)
1754
+    ax.quiver(camera_center[0], camera_center[1], camera_center[2],
1755
+              y_axis[0], y_axis[1], y_axis[2],
1756
+              length=axis_length, color='g', arrow_length_ratio=0.5, label='Y axis')
1757
+
1758
+    # Z 轴箭头(蓝色)
1759
+    ax.quiver(camera_center[0], camera_center[1], camera_center[2],
1760
+              z_axis[0], z_axis[1], z_axis[2],
1761
+              length=axis_length, color='b', arrow_length_ratio=0.5, label='Z axis')
1762
+
1763
+    # 绘制相机中心
1764
+    ax.scatter(camera_center[0], camera_center[1], camera_center[2], color=color, marker='o', s=100, label="Camera")
1765
+    ax.text(camera_center[0], camera_center[1], camera_center[2], f'Cam {camera_id}', color='black', fontsize=12)
1766
+
1767
+    #绘制相机视野四个角点与相机中心的连线
1768
+    for i in range(1, 5):
1769
+        ax.plot([camera_center[0], camera_points_world[i, 0]],
1770
+                [camera_center[1], camera_points_world[i, 1]],
1771
+                [camera_center[2], camera_points_world[i, 2]], color=color)
1772
+
1773
+    # 绘制相机的四边形视野
1774
+    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]],
1775
+            [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]],
1776
+            [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)
1777
+
1778
+    # # 添加相机方向箭头
1779
+    # ax.quiver(camera_center[0], camera_center[1], camera_center[2],
1780
+    #           rotation_matrix[0, 2], rotation_matrix[1, 2], rotation_matrix[2, 2], length=scale, color='g', arrow_length_ratio=1, label="Direction")
1781
+
1782
+
1783
+
1449 1784
 def bilinear_interpolation(points, z_values, grid_size=(100, 100)):
1450 1785
     """
1451 1786
     使用双线性插值对点云 z 值进行平滑。
@@ -1515,49 +1850,40 @@ def moving_average_filter_z(points, window_size=3):
1515 1850
     return np.column_stack((points[:,0], points[:,1], smoothed_z))
1516 1851
 
1517 1852
 
1518
-
1519
-def rbf_interpolation(points, z_values, grid_size=(100, 100)):
1520
-    """
1521
-    使用径向基函数 (RBF) 对点云 z 值进行插值和平滑。
1522
-    
1523
-    参数:
1524
-    points: (n, 2) 形状的 NumPy 数组,表示 (x, y) 坐标的点
1525
-    z_values: 点云的 z 值数组
1526
-    grid_size: 定义插值后生成的网格大小
1527
-    
1528
-    返回:
1529
-    grid_z: 插值后的平滑 z 值
1530
-    """
1531
-    # 定义插值网格
1532
-    x_min, x_max = points[:, 0].min(), points[:, 0].max()
1533
-    y_min, y_max = points[:, 1].min(), points[:, 1].max()
1534
-    grid_x, grid_y = np.mgrid[x_min:x_max:grid_size[0]*1j, y_min:y_max:grid_size[1]*1j]
1535
-
1536
-    # 使用径向基函数进行插值
1537
-    rbf = Rbf(points[:, 0], points[:, 1], z_values, function='linear')
1538
-    grid_z = rbf(grid_x, grid_y)
1539
-    
1540
-    return grid_x, grid_y, grid_z
1541
-
1542
-
1543 1853
 def show_cloud_point(pointcloud_path):
1544 1854
     points = np.loadtxt(os.path.join(pointcloud_path), delimiter=',')
1545 1855
     
1546
-    
1547 1856
 
1548 1857
 
1858
+def img_diff():
1859
+    img_dir1 = 'D:\\code\\Gamma\\Gamma\\3_pattern\\'
1860
+    img_dir2 = 'D:\\code\\Gamma\\Gamma\\patterns\\'
1861
+    for img in os.listdir(img_dir1):
1862
+        img_path1 = img_dir1 + img
1863
+        img_path2 = img_dir2 + img
1864
+        src1 = cv2.imread(img_path1)
1865
+        src2 = cv2.imread(img_path2)
1866
+        diff = 50 * cv2.absdiff(src1, src2)
1867
+        print(np.mean(diff))
1868
+        cv2.namedWindow('diff', 0)
1869
+        cv2.imshow('diff', diff)
1870
+        cv2.waitKey(0)
1871
+        
1872
+        
1873
+
1549 1874
 
1550 1875
 
1551 1876
 
1552 1877
 if __name__ == '__main__':
1878
+
1553 1879
     # white_path = 'D:\\data\\four_cam\\1008_storage\\'
1554 1880
     # cam_num = 1
1555
-    # #find_notch(white_path, cam_num)
1881
+    #find_notch(white_path, cam_num)
1556 1882
     # for img in os.listdir(white_path):
1557 1883
     #     if img.endswith('.bmp'):
1558 1884
     #         get_white_mask(white_path + img, bin_thresh=12)
1559
-    #camera_calibrate_vis()
1560
-    img_folder = 'D:\\data\\four_cam\\1008_storage\\20241008210412543'
1561
-    point_cloud_path = os.path.join(img_folder, 'cloudpoint.txt')
1562
-    show_cloud_point(point_cloud_path)
1885
+    # img_folder = 'D:\\data\\four_cam\\1008_storage\\20241008210412543'
1886
+    # point_cloud_path = os.path.join(img_folder, 'cloudpoint.txt')
1887
+    # show_cloud_point(point_cloud_path)
1888
+    img_diff()
1563 1889
 

File diff suppressed because it is too large
+ 1165 - 0
unit_test.py