Browse Source

fix get white mask

zhangkang 10 months ago
parent
commit
aca591d96b
9 changed files with 589 additions and 302 deletions
  1. 1 1
      config/cfg_3freq_wafer.json
  2. BIN
      config/screen_params.pkl
  3. 0 204
      main.py
  4. 51 26
      pmd.py
  5. 9 7
      src/calibration/get_camera_params.py
  6. 71 23
      src/eval.py
  7. 3 4
      src/phase.py
  8. 97 21
      src/recons.py
  9. 357 16
      src/utils.py

+ 1 - 1
config/cfg_3freq_wafer.json

@@ -7,7 +7,7 @@
7 7
     "num_freq": 3,
8 8
     "screenPixelSize": [1600, 1200],
9 9
     "screenorigin_point": [548, 1050],
10
-    "Axis_conversion": [[1, 1],[1, 1],[-1, -1],[-1, -1]],
10
+    "Axis_conversion": [[-1, -1],[-1, -1],[-1, -1],[-1, -1]],
11 11
     "k": 32.0,
12 12
     "pixelSize_mm": 0.253125,
13 13
     "grid_spacing":1,

BIN
config/screen_params.pkl


+ 0 - 204
main.py

@@ -1,204 +0,0 @@
1
-import cv2
2
-import os
3
-import sys
4
-sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
5
-import time
6
-import glob
7
-import json
8
-import argparse
9
-import pickle
10
-import matplotlib.pyplot as plt
11
-
12
-from datetime import datetime
13
-import numpy as np
14
-import scipy.io as sio
15
-from scipy.io import savemat, loadmat
16
-np.random.seed(42)
17
-
18
-from src.utils import binarize, get_meshgrid, get_world_points, get_camera_points, get_screen_points, write_point_cloud, \
19
-              get_white_mask, get_world_points_from_mask, get_meshgrid_contour, post_process
20
-from src.phase import extract_phase, unwrap_phase
21
-from src.recons import reconstruction_cumsum
22
-from src.calibration import calibrate_world, calibrate_screen, map_screen_to_world
23
-from src.vis import plot_coords
24
-from src.eval import get_eval_result, find_notch
25
-
26
-def list_directories(path):
27
-    # List all items in the given directory and filter only directories
28
-    return [item for item in os.listdir(path) if os.path.isdir(os.path.join(path, item))]
29
-
30
-def parse_args():
31
-
32
-    parser = argparse.ArgumentParser(description="")
33
-    parser.add_argument("--img_path", type=str, default='D:\\file\\20240913-data\\20240913105234292')  
34
-    parser.add_argument("--camera_path", type=str, default='D:\\file\\标定数据\\标定数据0913\\calibration_0913')
35
-    parser.add_argument("--screen_path", type=str, default='D:\\file\\screen0920')
36
-    parser.add_argument("--cfg", type=str, default="config/cfg_3freq_wafer.json")
37
-    #parser.add_argument("--cfg", type=str, default="D:\code\pmdrecons-python\config\cfg_3freq_wafer.json")
38
-    parser.add_argument("--save_path", type=str, default="debug")
39
-    parser.add_argument("--grid_spacing", type=int, default=1)  
40
-    parser.add_argument("--debug", action='store_true')
41
-    parser.add_argument("--smooth", action='store_true')
42
-    parser.add_argument("--align", action='store_true')
43
-    parser.add_argument("--denoise", action='store_true')
44
-    args = parser.parse_args()
45
-    return args 
46
-
47
-
48
-def main(cfg, img_folder, camera_path, screen_path, save_path, grid_spacing, debug):
49
-    n_cam = 4
50
-    num_freq = cfg['num_freq']
51
-    start_time = time.time()
52
-    #debug = True
53
-    print(f"开始执行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
54
-    print("\n1. 相机标定")
55
-    preprocess_start = time.time()
56
-    
57
-    camera_subdir = list_directories(camera_path)
58
-    camera_subdir.sort()
59
-    assert len(camera_subdir) == 4, f"found {len(camera_subdir)} cameras, should be 4"
60
-    cam_para_path = os.path.join(camera_path, "cam_params.pkl")
61
-
62
-    if os.path.exists(cam_para_path):
63
-        with open(cam_para_path, 'rb') as pkl_file:
64
-            cam_params = pickle.load(pkl_file)
65
-    else:
66
-        cam_params = []
67
-        for i in range(n_cam):
68
-            cam_img_path = glob.glob(os.path.join(camera_path, camera_subdir[i], "*.bmp"))
69
-            cam_img_path.sort()
70
-            cam_param_raw = calibrate_world(cam_img_path, i, cfg['world_chessboard_size'], cfg['world_square_size'], debug=False)
71
-            cam_params.append(cam_param_raw)
72
-        with open(cam_para_path, 'wb') as pkl_file:
73
-           pickle.dump(cam_params, pkl_file)
74
-   
75
-    preprocess_end = time.time()
76
-    print(f"   完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
77
-    print(f"   耗时: {preprocess_end - preprocess_start:.2f} 秒") 
78
-    # import pdb; pdb.set_trace()
79
- 
80
-    print("\n2. 屏幕标定")
81
-    screen_cal_start = time.time()
82
-    screen_img_path = glob.glob(os.path.join(screen_path, "*.bmp"))
83
-    screen_para_path = os.path.join(screen_path, "screen_params.pkl")    
84
-    if os.path.exists(screen_para_path):
85
-        with open(screen_para_path, 'rb') as pkl_file:
86
-            screen_params = pickle.load(pkl_file)[0]
87
-    else:
88
-        screen_params = calibrate_screen(screen_img_path, cam_params[0]['camera_matrix'], cam_params[0]['distortion_coefficients'], cfg['screen_chessboard_size'], cfg['screen_square_size'], debug=True)
89
-        with open(screen_para_path, 'wb') as pkl_file:
90
-            pickle.dump([screen_params], pkl_file)
91
-
92
-    screen_to_world = map_screen_to_world(screen_params, cam_params[0])  
93
-    screen_cal_end = time.time()
94
-    print(f"   完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
95
-    print(f"   耗时: {screen_cal_end - screen_cal_start:.2f} 秒")
96
-   
97
-    print("\n3. 相位提取,相位展开")
98
-    phase_start = time.time()
99
-    x_uns, y_uns = [], []
100
-    binary_masks = []
101
-    for cam_id in range(n_cam):
102
-        white_path = os.path.join(img_folder, f'{cam_id}_frame_24.bmp')
103
-        binary = get_white_mask(white_path)
104
-        binary_masks.append(binary)
105
-        phases = extract_phase(img_folder, cam_id, binary, cam_params[cam_id]['camera_matrix'], cam_params[cam_id]['distortion_coefficients'], num_freq=num_freq)
106
-        x_un, y_un = unwrap_phase(phases, save_path, num_freq, debug=False)
107
-        x_uns.append(x_un)
108
-        y_uns.append(y_un)
109
-    
110
-    phase_end = time.time()
111
-    print(f"   完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
112
-    print(f"   耗时: {phase_end - phase_start:.2f} 秒")
113
- 
114
-    print("\n4. 获得不同坐标系下点的位置")
115
-    get_point_start = time.time()
116
-    total_cloud_point = np.empty((0, 3))
117
-    for i in range(n_cam):
118
-        if i > 5:
119
-            continue
120
-        contours_point = get_meshgrid_contour(binary_masks[i], grid_spacing, save_path, debug=False)
121
-        world_points = get_world_points(contours_point, cam_params[i], grid_spacing, cfg['d'], save_path, debug=debug)
122
-        camera_points, u_p, v_p = get_camera_points(world_points, cam_params[i], save_path, i, debug=debug)
123
-        
124
-        point_data = {'x_w': world_points[:, 0], 'y_w': world_points[:, 1], 'z_w': world_points[:, 2], 
125
-                    'x_c': camera_points[:, 0], 'y_c': camera_points[:, 1], 'z_c': camera_points[:, 2],  
126
-                    'u_p': u_p, 'v_p': v_p}
127
-        
128
-        screen_points = get_screen_points(point_data, x_uns[i], y_uns[i], screen_params, screen_to_world, cfg, save_path, i, debug=debug)
129
-        #plot_coords(world_points, camera_points, screen_points)
130
-        z, smoothed, aligned, denoise = reconstruction_cumsum(world_points, camera_points, screen_points, save_path, i, debug=debug, smooth=args.smooth, align=args.align, denoise=args.denoise)
131
-        write_point_cloud(os.path.join(img_folder, str(i) + '_cloudpoint.txt'), world_points[:, 0], world_points[:, 1], 1000 * denoise[:,2])
132
-
133
-        total_cloud_point = np.vstack([total_cloud_point, np.column_stack((denoise[:, 0], denoise[:, 1], 1000 * denoise[:,2]))])
134
-        print('point cloud has been written in file')
135
-
136
-    write_point_cloud(os.path.join(img_folder, 'cloudpoint.txt'), total_cloud_point[:, 0], total_cloud_point[:, 1], total_cloud_point[:,2])
137
-    
138
-    if debug:
139
-        fig = plt.figure()
140
-        ax = fig.add_subplot(111, projection='3d')
141
-
142
-        # 提取 x, y, z 坐标
143
-        x_vals = total_cloud_point[:, 0]
144
-        y_vals = total_cloud_point[:, 1]
145
-        z_vals = total_cloud_point[:, 2]
146
-
147
-        # 绘制3D点云
148
-        ax.scatter(x_vals, y_vals, z_vals, c=z_vals, cmap='viridis', marker='o')
149
-
150
-        # 设置轴标签和标题
151
-        ax.set_xlabel('X (mm)')
152
-        ax.set_ylabel('Y (mm)')
153
-        ax.set_zlabel('Z (mm)')
154
-        ax.set_title('3D Point Cloud Visualization')
155
-
156
-        plt.show()
157
-    get_point_end = time.time()
158
-    print(f"   完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
159
-    print(f"   耗时: {get_point_end - get_point_start:.2f} 秒")
160
-    
161
-    print("\n5. 后处理")
162
-    post_process_start = time.time()
163
-    post_process(args.img_path, debug)
164
-    post_process_end = time.time()
165
-    print(f"   完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
166
-    print(f"   耗时: {post_process_end - post_process_start:.2f} 秒")
167
-
168
-    print("\n6. 评估")
169
-    eval_start = time.time()
170
-    point_cloud_path = os.path.join(img_folder, str(i) + '_cloudpoint.txt')
171
-    json_path = os.path.join(img_folder, str(i) + 'result.json')
172
-    theta_notch = 0
173
-    get_eval_result(point_cloud_path, json_path, theta_notch)
174
-    post_process(args.img_path, debug)
175
-    eval_end = time.time()
176
-    print(f"   完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
177
-    print(f"   耗时: {eval_end - eval_start:.2f} 秒")
178
-
179
-
180
-    return True
181
-        
182
-
183
-if __name__ == '__main__': 
184
-
185
-    start_time = time.time()
186
-    
187
-    args = parse_args()
188
-    img_folder = args.img_path
189
-    
190
-    
191
-    cfg = json.load(open(args.cfg, 'r'))
192
-    if not os.path.exists(args.save_path):
193
-        os.makedirs(args.save_path)
194
-    
195
-    args.smooth = True
196
-    args.align = True
197
-    args.denoise = True
198
-    args.debug = 0
199
-
200
-    main(cfg, img_folder, args.camera_path, args.screen_path, args.save_path, args.grid_spacing, args.debug)
201
-
202
-    end_time = time.time()
203
-    print(f"总运行时间: {end_time - start_time:.2f} 秒")
204
-    

+ 51 - 26
pmd.py

@@ -1,6 +1,8 @@
1
+#encoding=utf-8
1 2
 import time
2 3
 import os
3 4
 import sys
5
+
4 6
 sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
5 7
 import time
6 8
 import glob
@@ -8,7 +10,6 @@ import numpy as np
8 10
 np.random.seed(42)
9 11
 from datetime import datetime
10 12
 import json
11
-from scipy.io import savemat, loadmat
12 13
 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
13 14
 from src.phase import extract_phase, unwrap_phase
14 15
 from src.recons import reconstruction_cumsum
@@ -33,26 +34,55 @@ def pmdstart(config_path, img_folder):
33 34
 
34 35
 
35 36
 def main(config_path, img_folder):
37
+
36 38
     cfg = json.load(open(config_path, 'r'))
37 39
     n_cam = 4
38 40
     num_freq = cfg['num_freq']
39
-    save_path = None
41
+    save_path = 'debug'
40 42
     debug = False
41 43
     grid_spacing = cfg['grid_spacing']
42 44
     num_freq = cfg['num_freq']
43 45
     smooth = True
44 46
     align = True
45 47
     denoise = True
48
+    cammera_img_path = 'D:\\data\\four_cam\\calibrate\\calibration_0913'
49
+    screen_img_path = 'D:\\data\\four_cam\\calibrate\\screen0920'
46 50
 
47 51
     print(f"开始执行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
48 52
     print("\n1. 相机标定")
49 53
     preprocess_start = time.time()
50 54
 
51
-    with open(os.path.join('config', cfg['cam_params']), 'rb') as pkl_file:
52
-        cam_params = pickle.load(pkl_file)
53
-    with open(os.path.join('config', cfg['screen_params']), 'rb') as pkl_file:
54
-            screen_params = pickle.load(pkl_file)[0]
55
+    cam_para_path = os.path.join('config', cfg['cam_params'])
56
+    if os.path.exists(cam_para_path):
57
+    #if False:
58
+        with open(cam_para_path, 'rb') as pkl_file:
59
+            cam_params = pickle.load(pkl_file)
60
+    else:
61
+        cam_params = []
62
+        camera_subdir = [item for item in os.listdir(cammera_img_path) if os.path.isdir(os.path.join(cammera_img_path, item))]
63
+        camera_subdir.sort()
64
+        assert len(camera_subdir) == 4, f"found {len(camera_subdir)} cameras, should be 4"
65
+        for i in range(n_cam):
66
+            cam_img_path = glob.glob(os.path.join(cammera_img_path, camera_subdir[i], "*.bmp"))
67
+            cam_img_path.sort()
68
+            cam_param_raw = calibrate_world(cam_img_path, i, cfg['world_chessboard_size'], cfg['world_square_size'], debug=debug)
69
+            cam_params.append(cam_param_raw)
70
+        with open(cam_para_path, 'wb') as pkl_file:
71
+           pickle.dump(cam_params, pkl_file) 
72
+
73
+    
55 74
     
75
+    screen_img_path = glob.glob(os.path.join(screen_img_path, "*.bmp"))
76
+    screen_para_path = os.path.join('config', cfg['screen_params'])   
77
+    if os.path.exists(screen_para_path):
78
+    #if False:
79
+        with open(screen_para_path, 'rb') as pkl_file:
80
+            screen_params = pickle.load(pkl_file)[0]
81
+    else:
82
+        screen_params = calibrate_screen(screen_img_path, cam_params[0]['camera_matrix'], cam_params[0]['distortion_coefficients'], cfg['screen_chessboard_size'], cfg['screen_square_size'], debug=True)
83
+        with open(screen_para_path, 'wb') as pkl_file:
84
+            pickle.dump([screen_params], pkl_file)
85
+
56 86
     preprocess_end = time.time()
57 87
     print(f"   完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
58 88
     print(f"   耗时: {preprocess_end - preprocess_start:.2f} 秒") 
@@ -71,7 +101,8 @@ def main(config_path, img_folder):
71 101
     x_uns, y_uns = [], []
72 102
     binary_masks = []
73 103
     for cam_id in range(n_cam):
74
-        white_path = os.path.join(img_folder, f'{cam_id}_frame_24.bmp')
104
+        print('cam_id = ', cam_id)
105
+        white_path = os.path.join(img_folder, f'{cam_id}_frame_24.bmp') 
75 106
         binary = get_white_mask(white_path)
76 107
         binary_masks.append(binary)
77 108
         phases = extract_phase(img_folder, cam_id, binary, cam_params[cam_id]['camera_matrix'], cam_params[cam_id]['distortion_coefficients'], num_freq=num_freq)
@@ -79,6 +110,7 @@ def main(config_path, img_folder):
79 110
         x_uns.append(x_un)
80 111
         y_uns.append(y_un)
81 112
     
113
+
82 114
     phase_end = time.time()
83 115
     print(f"   完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
84 116
     print(f"   耗时: {phase_end - phase_start:.2f} 秒")
@@ -87,26 +119,19 @@ def main(config_path, img_folder):
87 119
     get_point_start = time.time()
88 120
     total_cloud_point = np.empty((0, 3))
89 121
     for i in range(n_cam):
90
-        if i > 5:
91
-            continue
92 122
         contours_point = get_meshgrid_contour(binary_masks[i], grid_spacing, save_path, debug=False)
93
-        world_points = get_world_points(contours_point, cam_params[i], grid_spacing, cfg['d'], save_path, debug=debug)
123
+        world_points = get_world_points(contours_point, cam_params[i], i, grid_spacing, cfg['d'], save_path, debug=debug)
94 124
         camera_points, u_p, v_p = get_camera_points(world_points, cam_params[i], save_path, i, debug=debug)
95
-        
96 125
         point_data = {'x_w': world_points[:, 0], 'y_w': world_points[:, 1], 'z_w': world_points[:, 2], 
97 126
                     'x_c': camera_points[:, 0], 'y_c': camera_points[:, 1], 'z_c': camera_points[:, 2],  
98 127
                     'u_p': u_p, 'v_p': v_p}
99
-        
100 128
         screen_points = get_screen_points(point_data, x_uns[i], y_uns[i], screen_params, screen_to_world, cfg, save_path, i, debug=debug)
101 129
         #plot_coords(world_points, camera_points, screen_points)
102
-        z, smoothed, aligned, denoised = reconstruction_cumsum(world_points, camera_points, screen_points, save_path, i, debug=debug, smooth=smooth, align=align, denoise=denoise)
103
-        write_point_cloud(os.path.join(img_folder, str(i) + '_cloudpoint.txt'), world_points[:, 0], world_points[:, 1], 1000 * denoised[:,2])
104
-
105
-        total_cloud_point = np.vstack([total_cloud_point, np.column_stack((denoised[:, 0], denoised[:, 1], 1000 * denoised[:,2]))])
106
-
107
-    write_point_cloud(os.path.join(img_folder, 'cloudpoint.txt'), total_cloud_point[:, 0], total_cloud_point[:, 1], total_cloud_point[:,2])
130
+        z, smoothed, aligned, denoised = reconstruction_cumsum(world_points, camera_points, screen_points, save_path, i, debug=False, smooth=smooth, align=align, denoise=denoise)
131
+        write_point_cloud(os.path.join(img_folder, str(i) + '_cloudpoint.txt'), world_points[:, 0], world_points[:, 1], 1000 * z)
132
+        total_cloud_point = np.vstack([total_cloud_point, np.column_stack((world_points[:, 0], world_points[:, 1], 1000 * z))])
108 133
     
109
-    if debug:
134
+    if 1:
110 135
         fig = plt.figure()
111 136
         ax = fig.add_subplot(111, projection='3d')
112 137
 
@@ -131,26 +156,26 @@ def main(config_path, img_folder):
131 156
     
132 157
     print("\n5. 后处理")
133 158
     post_process_start = time.time()
134
-    post_process(img_folder, debug)
159
+    #total_cloud_point = post_process(img_folder, debug=True)
160
+    #write_point_cloud(os.path.join(img_folder, 'cloudpoint.txt'), total_cloud_point[:, 0], total_cloud_point[:, 1], total_cloud_point[:,2])
135 161
     post_process_end = time.time()
136 162
     print(f"   完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
137 163
     print(f"   耗时: {post_process_end - post_process_start:.2f} 秒")
138 164
 
139 165
     print("\n6. 评估")
140 166
     eval_start = time.time()
141
-    point_cloud_path = os.path.join(img_folder, str(i) + '_cloudpoint.txt')
142
-    json_path = os.path.join(img_folder, str(i) + 'result.json')
167
+    point_cloud_path = os.path.join(img_folder, 'cloudpoint.txt')
168
+    json_path = os.path.join(img_folder, 'result.json')
143 169
     theta_notch = 0
144
-    get_eval_result(point_cloud_path, json_path, theta_notch)
170
+    get_eval_result(point_cloud_path, json_path, theta_notch, 0)
145 171
     eval_end = time.time()
146 172
     print(f"   完成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
147 173
     print(f"   耗时: {eval_end - eval_start:.2f} 秒")
148 174
 
149
-
150 175
     return True
151 176
 
152 177
 if __name__ == '__main__':
153
-    config_path = 'D:\\code\\pmdrecons-fourcam\\config\\cfg_3freq_wafer.json'
154
-    img_folder = 'D:\\file\\20240913-data\\20240913105234292'
178
+    config_path = 'config\\cfg_3freq_wafer.json'
179
+    img_folder = 'D:\\data\\four_cam\\0927_storage\\20240927161651920'
155 180
 
156 181
     pmdstart(config_path, img_folder)

+ 9 - 7
src/calibration/get_camera_params.py

@@ -9,20 +9,22 @@ from aprilgrid import Detector
9 9
 
10 10
 
11 11
 def show_detect_result(img, detections):
12
+    colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0)]
12 13
     for detection in detections:
13 14
         corners = detection.corners
14 15
         tag_id = detection.tag_id
15 16
         center = np.mean(corners, axis=0).astype(int)[0]
16
-        for corner in corners:
17
-            cv2.circle(img, tuple(int(x) for x in corner[0]), 5, (255, 0, 0), -1)
17
+        for i, corner in enumerate(corners):
18
+            color = colors[i % len(colors)]
19
+            cv2.circle(img, tuple(int(x) for x in corner[0]), 25, color, -1)
18 20
         cv2.circle(img, center, 5, (255, 255, 0), -1)
19 21
         cv2.putText(img, str(tag_id), (center[0]-40, center[1]), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
20 22
         cv2.polylines(img, [np.int32(corners)], isClosed=True, color=(0, 255, 255), thickness=2)
21 23
 
22
-    cv2.namedWindow('AprilGrid Detection', 0)
23
-    cv2.imshow('AprilGrid Detection', img)
24
-    cv2.waitKey(0)
25
-    cv2.destroyAllWindows()
24
+    # cv2.namedWindow('AprilGrid Detection', 0)
25
+    # cv2.imshow('AprilGrid Detection', img)
26
+    # cv2.waitKey(0)
27
+    # cv2.destroyAllWindows()
26 28
 
27 29
 # 生成 AprilGrid 标定板的 3D 物体坐标,并考虑标签之间的间隙
28 30
 def generate_3d_points(grid_size, tag_size, tag_spacing):
@@ -59,7 +61,7 @@ def calibrate_world(calibration_images_path, cam_id, world_chessboard_size=(6,6)
59 61
     # grid_size = (5, 6)  # 标定板的行数和列数
60 62
     # tag_size = 25.3125    # 每个标签的边长(单位:毫米)
61 63
     object_point_single = generate_3d_points(world_chessboard_size, world_square_size, world_square_spacing)  
62
-    if debug:
64
+    if 0:
63 65
         # 可视化3D点
64 66
         fig = plt.figure()
65 67
         ax = fig.add_subplot(111, projection='3d')

+ 71 - 23
src/eval.py

@@ -38,7 +38,7 @@ def remove_noise(x, y, z, a, b, c, thresh=0.01):
38 38
 
39 39
     # fig = plt.figure(figsize=(10, 7))
40 40
     # ax = fig.add_subplot(111, projection='3d')
41
-    # scatter = ax.scatter(x_filtered, y_filtered, z_filtered, c=z_filtered, cmap='viridis', marker='o')
41
+    # scatter = ax.scatter(x_keep, y_keep, z_keep, c=z_keep, cmap='viridis', marker='o')
42 42
 
43 43
     # cbar = plt.colorbar(scatter, ax=ax, shrink=0.5, aspect=5)
44 44
     # cbar.set_label('Z values')
@@ -109,7 +109,7 @@ def get_mean_line_value(x, y, z, far_point_x, far_point_y, dist_thresh=0.5):
109 109
     A = -(y2 - y1) if x2 != x1 else 1
110 110
     B = x2 - x1 if x2 != x1 else 0
111 111
     C = -(x2 * y1 - x1 * y2) if x2 != x1 else - y1
112
-    dist_thresh = 0.5
112
+    dist_thresh = 2
113 113
     flat_data = np.column_stack((x, y))
114 114
     distances = np.abs(A * x + B * y + C) / np.sqrt(A**2 + B**2)
115 115
     intersections = flat_data[distances < dist_thresh]
@@ -117,11 +117,13 @@ def get_mean_line_value(x, y, z, far_point_x, far_point_y, dist_thresh=0.5):
117 117
     tree = cKDTree(flat_data)
118 118
     _, idx = tree.query(intersections, k=1)
119 119
     z_values = z[idx]
120
-
121
-    z_plane_values = (a * intersections[:, 0] + b * intersections[:, 1] + c)
122
-    warpage_diff = (z_values - z_plane_values)
123
-    #print(warpage_diff)
124
-    line_warpage = max(warpage_diff) - min(warpage_diff)
120
+    if len(z_values) == 0:
121
+        return 0, 0, 0
122
+    else:
123
+        z_plane_values = (a * intersections[:, 0] + b * intersections[:, 1] + c)
124
+        warpage_diff = (z_values - z_plane_values)
125
+        
126
+        line_warpage = max(warpage_diff) - min(warpage_diff)
125 127
     
126 128
     distances_to_far_point = np.sqrt((intersections[:, 0] - far_point_x)**2 + (intersections[:, 1] - far_point_y)**2)
127 129
 
@@ -296,7 +298,7 @@ def find_notch(white_path):
296 298
     # cv2.waitKey(0)
297 299
     return angle_rad, thresh_img
298 300
 
299
-def get_eval_result(pointcloud_path, json_path, theta_notch):
301
+def get_eval_result(pointcloud_path, json_path, theta_notch, debug):
300 302
     data = np.loadtxt(os.path.join(pointcloud_path), delimiter=',')
301 303
     
302 304
     x = data[:, 0]
@@ -313,10 +315,17 @@ def get_eval_result(pointcloud_path, json_path, theta_notch):
313 315
     support_points = get_support_points(x_filtered, y_filtered, z_filtered)
314 316
     a_bow, b_bow, c_bow = fit_plane(support_points[:,0], support_points[:,1], support_points[:,2])
315 317
 
316
-    center_int = (round(np.mean(x_filtered)), round(np.mean(y_filtered)))
318
+    #center_int = (round(np.mean(x_filtered)), round(np.mean(y_filtered)))
319
+    center_int = (np.mean(x_filtered), np.mean(y_filtered))
317 320
     #print('center_int = ', center_int)
318
-    mask = (data[:, 0] == center_int[0]) & (data[:, 1] == center_int[1])
319
-    z_center = data[mask, 2][0]
321
+    distances = np.sqrt((data[:, 0] - center_int[0]) ** 2 + (data[:, 1] - center_int[1]) ** 2)
322
+    # 找到最近的点
323
+    nearest_index = np.argmin(distances)  # 找到最小距离对应的索引
324
+
325
+    # 获取最近点的 z 值
326
+    z_center = data[nearest_index, 2]
327
+    #mask = (data[:, 0] == center_int[0]) & (data[:, 1] == center_int[1])
328
+    #z_center = data[mask, 2][0]
320 329
 
321 330
     fit_z_center = a_bow*center_int[0] + b_bow*center_int[1] + c_bow
322 331
     bow = (z_center - fit_z_center)
@@ -332,19 +341,58 @@ def get_eval_result(pointcloud_path, json_path, theta_notch):
332 341
     print('max_line_bow = ', max_line_bow)
333 342
     #print('max_warpage_z = ', max_warpage_z)
334 343
 
335
-    x2 = int(round(np.mean(x)))  
336
-    y2 = int(round(np.mean(y)))
337
-    horizontal_mask = (y == y2)
338
-    horizontal_z = z[horizontal_mask]  # 水平方向对应的 z 值
339
-    horizontal_x = x[horizontal_mask]
344
+    # x2 = int(round(np.mean(x)))  
345
+    # y2 = int(round(np.mean(y)))
346
+    # horizontal_mask = (y == y2)
347
+    # horizontal_z = z[horizontal_mask]  # 水平方向对应的 z 值
348
+    # horizontal_x = x[horizontal_mask]
340 349
    
341
-    # 提取垂直方向的 z 值(即 x == x2 的所有点)
342
-    vertical_mask = (x == x2)
343
-    vertical_z = z[vertical_mask]  # 垂直方向对应的 z 值
344
-    vertical_y = y[vertical_mask]
350
+    # # 提取垂直方向的 z 值(即 x == x2 的所有点)
351
+    # vertical_mask = (x == x2)
352
+    # vertical_z = z[vertical_mask]  # 垂直方向对应的 z 值
353
+    # vertical_y = y[vertical_mask]
354
+    # 创建二维坐标数组
355
+    points = np.column_stack((x, y))  # 将 x 和 y 组合成二维坐标点
356
+
357
+    # 构建 KD 树
358
+    tree = cKDTree(points)
359
+
360
+    # 定义要查询的中心点
361
+    x2 = np.mean(x)
362
+    y2 = np.mean(y)
363
+
364
+    # 查询水平和垂直方向上最近的点
365
+    dist_horizontal, idx_horizontal = tree.query([x2, y2], k=1)  # 最近的点,返回距离和索引
366
+
367
+    # 查找水平方向 (y == y2) 最近的点
368
+    horizontal_idx = tree.query_ball_point([x2, y2], r=1)  # 设置半径 r 作为容差
369
+    horizontal_z = z[horizontal_idx]
370
+    horizontal_x = x[horizontal_idx]
371
+
372
+    # 查找垂直方向 (x == x2) 最近的点
373
+    vertical_idx = tree.query_ball_point([x2, y2], r=1)  # 同样的容差
374
+    vertical_z = z[vertical_idx]
375
+    vertical_y = y[vertical_idx]
376
+
377
+    # 检查水平和垂直方向是否找到点
378
+    if len(horizontal_x) > 0:
379
+        right_x = max(horizontal_x)
380
+    else:
381
+        print("找不到水平方向的点")
382
+        right_x = None
383
+
384
+    if len(vertical_y) > 0:
385
+        top_y = max(vertical_y)
386
+    else:
387
+        print("找不到垂直方向的点")
388
+        top_y = None
389
+
390
+    top_x = x2
391
+    right_y = y2
392
+
345 393
     
346
-    top_x, top_y = x2, max(vertical_y)
347
-    right_x, right_y = max(horizontal_x), y2
394
+    #top_x, top_y = x2, max(vertical_y)
395
+    #right_x, right_y = max(horizontal_x), y2
348 396
     #print(top_x, top_y)
349 397
     line_warpage_x, line_bow_x, z_values = get_mean_line_value(x, y, z, right_x, right_y)
350 398
     line_warpage_y, line_bow_y, z_values = get_mean_line_value(x, y, z, top_x, top_y)
@@ -376,7 +424,7 @@ def get_eval_result(pointcloud_path, json_path, theta_notch):
376 424
         }
377 425
         #print(result_data)
378 426
         json.dump(result_data, file, indent=4)
379
-    debug = False
427
+
380 428
     if debug:
381 429
         plt.scatter(x, y, c=z, cmap='viridis')
382 430
         plt.colorbar()

+ 3 - 4
src/phase.py

@@ -73,7 +73,6 @@ def compute_phase(img_data: np.ndarray, mask: np.ndarray) -> np.ndarray:
73 73
         np.ndarray: 计算得到的相位数组
74 74
     """
75 75
     I1, I2, I3, I4 = [np.ma.masked_where(mask == 0, img_data[:, :, i]) for i in range(4)]
76
-
77 76
     phase = np.arctan2(I3 - I1, I4 - I2) + np.pi
78 77
     return phase.filled(fill_value=np.nan)
79 78
 
@@ -158,11 +157,11 @@ def unwrap_phase(phases, save_path, num_freq: int = 2, k: int = 32, debug: bool
158 157
 
159 158
         plt.imshow(high_freq_y_phase)
160 159
         plt.title("high_freq_x_phase")
161
-        plt.savefig(os.path.join(save_path, "high_freq_x_phase.png"))
160
+        #plt.savefig(os.path.join(save_path, "high_freq_x_phase.png"))
162 161
         plt.show()
163 162
         plt.imshow(high_freq_x_phase)
164 163
         plt.title("high_freq_y_phase")
165
-        plt.savefig(os.path.join(save_path, "high_freq_y_phase.png"))
164
+        #plt.savefig(os.path.join(save_path, "high_freq_y_phase.png"))
166 165
         plt.show()
167 166
 
168 167
         x, y = np.meshgrid(np.arange(high_freq_x_phase.shape[1]), np.arange(high_freq_x_phase.shape[0]))
@@ -185,7 +184,7 @@ def unwrap_phase(phases, save_path, num_freq: int = 2, k: int = 32, debug: bool
185 184
 
186 185
         # 调整子图之间的间距
187 186
         plt.tight_layout()
188
-        plt.savefig(os.path.join(save_path, "phase_unwrapped.png"))
187
+        #plt.savefig(os.path.join(save_path, "phase_unwrapped.png"))
189 188
         plt.show()
190 189
 
191 190
     return y_phase_unwrapped, x_phase_unwrapped

+ 97 - 21
src/recons.py

@@ -12,6 +12,10 @@ import plotly.io as pio
12 12
 from scipy.spatial import Delaunay
13 13
 from src.pcl_postproc import smooth_pcl, align2ref
14 14
 from src.eval import fit_plane, remove_noise
15
+from scipy.ndimage import gaussian_filter
16
+from scipy.optimize import least_squares
17
+import scipy.sparse as sp
18
+
15 19
 
16 20
 def save_scatter_plot(x, y, z):
17 21
  
@@ -45,39 +49,110 @@ def save_scatter_plot(x, y, z):
45 49
     # 保存为HTML文件,可以在浏览器中查看
46 50
     pio.write_html(fig, file='interactive_3d_scatter.html', auto_open=True)
47 51
 
52
+
53
+
54
+
55
+
56
+# def build_laplacian_matrix(points, triangles):
57
+#     N = len(points)
58
+#     A = lil_matrix((N, N))
59
+#     for tri in triangles:
60
+#         for i in range(3):
61
+#             A[tri[i], tri[i]] += 3  # 每个顶点累加对角线上的值
62
+#             for j in range(3):
63
+#                 if i != j:
64
+#                     A[tri[i], tri[j]] -= 1  # 对应三角形顶点之间建立连接
65
+#     return A.tocsr()
66
+
67
+# def poisson_recons(x, y, gx, gy, debug=False):
68
+
69
+#     n = len(x)
70
+#     # theta = np.linspace(0, 2 * np.pi, n, endpoint=False)
71
+#     points = np.vstack((x, y)).T
72
+#     # Delaunay triangulation
73
+#     tri = Delaunay(points)
74
+#     triangles = tri.simplices
75
+
76
+#     # Build Laplacian matrix based on Delaunay triangulation
77
+#     L = build_laplacian_matrix(points, triangles)
78
+
79
+#     # Calculate divergence from synthetic gradients
80
+#     div_g = np.zeros(n)
81
+#     for i in range(n):
82
+#         div_g[i] = gx[i] + gy[i]  # Simple approximation
83
+
84
+#     # Solve the Poisson equation using the sparse matrix
85
+#     z_reconstructed = spsolve(L, div_g)
86
+
87
+#     # Color mapping based on the mean value of the z-coordinates at the vertices of each triangle
88
+#     # facecolors = z_reconstructed[triangles].mean(axis=1)
89
+#     return z_reconstructed
90
+
91
+
92
+# def least_squares_recons(x, y, gx, gy):
93
+#     """
94
+#     使用最小二乘法从梯度场重建 z 值。
95
+#     """
96
+#     def residuals(z, x, y, gx, gy):
97
+#         # 计算梯度的差值作为残差,确保形状一致
98
+#         dzdx = np.diff(z) / np.nan_to_num(np.diff(x), nan=1e-6, posinf=1e6, neginf=-1e6)
99
+#         dzdy = np.diff(z) / np.nan_to_num(np.diff(y), nan=1e-6, posinf=1e6, neginf=-1e6)
100
+
101
+#         return np.concatenate((dzdx - gx[:-1], dzdy - gy[:-1]))
102
+
103
+#     # 初始估计
104
+#     z_init = np.zeros(len(x))
105
+#     print('residuals =',residuals)
106
+
107
+#     # 使用最小二乘法优化
108
+#     res = least_squares(residuals, z_init, args=(x, y, gx, gy))
109
+
110
+#     return res.x
111
+
112
+
113
+
48 114
 def build_laplacian_matrix(points, triangles):
49
-    N = len(points)
50
-    A = lil_matrix((N, N))
115
+    """
116
+    根据 Delaunay 三角剖分构建 Laplacian 矩阵
117
+    points: 点的坐标
118
+    triangles: 三角剖分后的三角形
119
+    """
120
+    n = len(points)
121
+    L = sp.lil_matrix((n, n))
122
+    # 遍历每个三角形
51 123
     for tri in triangles:
52 124
         for i in range(3):
53
-            A[tri[i], tri[i]] += 3  # 每个顶点累加对角线上的值
54
-            for j in range(3):
55
-                if i != j:
56
-                    A[tri[i], tri[j]] -= 1  # 对应三角形顶点之间建立连接
57
-    return A.tocsr()
125
+            for j in range(i + 1, 3):
126
+                vi, vj = tri[i], tri[j]
127
+                # Laplacian 矩阵的值 (权重)
128
+                L[vi, vj] = -1
129
+                L[vj, vi] = -1
130
+        for i in range(3):
131
+            L[tri[i], tri[i]] += 2  # 对角线元素
132
+    
133
+    return L.tocsc()
58 134
 
59
-def poisson_recons(x, y, gx, gy, debug=False):
60 135
 
136
+def poisson_recons(x, y, gx, gy, debug=False, smooth_sigma=1):
61 137
     n = len(x)
62
-    # theta = np.linspace(0, 2 * np.pi, n, endpoint=False)
63 138
     points = np.vstack((x, y)).T
64
-    # Delaunay triangulation
65 139
     tri = Delaunay(points)
66 140
     triangles = tri.simplices
67 141
 
68
-    # Build Laplacian matrix based on Delaunay triangulation
142
+    # 构建 Laplacian 矩阵
69 143
     L = build_laplacian_matrix(points, triangles)
70 144
 
71
-    # Calculate divergence from synthetic gradients
72
-    div_g = np.zeros(n)
73
-    for i in range(n):
74
-        div_g[i] = gx[i] + gy[i]  # Simple approximation
145
+    # 对梯度场进行高斯平滑,减少边缘噪声
146
+    if smooth_sigma > 0:
147
+        gx = gaussian_filter(gx, sigma=smooth_sigma)
148
+        gy = gaussian_filter(gy, sigma=smooth_sigma)
149
+
150
+    # 计算散度 (divergence)
151
+    div_g = gx + gy
75 152
 
76
-    # Solve the Poisson equation using the sparse matrix
153
+    # 求解泊松方程
77 154
     z_reconstructed = spsolve(L, div_g)
78 155
 
79
-    # Color mapping based on the mean value of the z-coordinates at the vertices of each triangle
80
-    # facecolors = z_reconstructed[triangles].mean(axis=1)
81 156
     return z_reconstructed
82 157
 
83 158
 def reconstruction_cumsum(world_points, camera_points, screen_points, save_path, cam_id, debug=False, smooth=False, align=True, denoise=True):
@@ -137,12 +212,13 @@ def reconstruction_cumsum(world_points, camera_points, screen_points, save_path,
137 212
     # import pdb; pdb.set_trace()
138 213
         z = np.nan_to_num(z, nan=0.0)
139 214
     else:
140
-        z = poisson_recons(x, y, gradient_x, gradient_y)
215
+        z_raw = poisson_recons(x, y, gradient_x, gradient_y)
216
+       # z_raw = least_squares_recons(x, y, gradient_x, gradient_y)
141 217
         # import pdb; pdb.set_trace()
142 218
     #print('-- point cloud: mean - {}; std - {}'.format(np.nanmean(z), np.nanstd(z)))
143 219
     if smooth:
144 220
         #print("-- point cloud is being smoothed.")
145
-        smoothed_points = smooth_pcl(np.column_stack((x, y, z)))
221
+        smoothed_points = smooth_pcl(np.column_stack((x, y, z_raw)))
146 222
         #print('-- nanmean after smoothing: mean - {}; std - {}'.format(np.nanmean(smoothed_points[:, 2]), np.nanstd(smoothed_points[:, 2])))
147 223
     if align:
148 224
         points_aligned = align2ref(smoothed_points)
@@ -245,4 +321,4 @@ def reconstruction_cumsum(world_points, camera_points, screen_points, save_path,
245 321
         plt.close()
246 322
 
247 323
     # return points_aligned[:,2]
248
-    return z, smoothed_points[:,2], points_aligned[:,2], points_denoise
324
+    return z_raw, smoothed_points[:,2], points_aligned[:,2], points_denoise

+ 357 - 16
src/utils.py

@@ -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
+