|
1 | 1 | """ |
2 | 2 | Filter the point cloud based on a ROI box given relative to the Zivid Calibration Board. |
3 | 3 |
|
4 | | -The ZDF file for this sample can be found under the main instructions for Zivid samples. |
| 4 | +The ZFC file for this sample can be downloaded from https://support.zivid.com/en/latest/api-reference/samples/sample-data.html. |
5 | 5 |
|
6 | 6 | """ |
7 | 7 |
|
8 | | -import argparse |
9 | | -from pathlib import Path |
10 | | -from typing import Tuple |
| 8 | +from typing import List |
11 | 9 |
|
12 | 10 | import numpy as np |
13 | 11 | import zivid |
14 | 12 | from sample_utils.display import display_depthmap, display_pointcloud |
15 | 13 | from sample_utils.paths import get_sample_data_path |
16 | | -from zivid.point_cloud import PointCloud |
17 | 14 |
|
18 | 15 |
|
19 | | -def _options() -> argparse.Namespace: |
20 | | - """Function to read user arguments. |
| 16 | +def _transform_points(points: List[np.ndarray], transform: np.ndarray) -> List[np.ndarray]: |
| 17 | + """Perform a homogenous transformation to every point in 'points' and return the transformed points. |
| 18 | +
|
| 19 | + Args: |
| 20 | + points: list of 3D points to be transformed |
| 21 | + transform: homogenous transform (4x4) |
21 | 22 |
|
22 | 23 | Returns: |
23 | | - Arguments from user |
| 24 | + transformed_points: list of transformed 3D points |
24 | 25 |
|
25 | 26 | """ |
26 | | - parser = argparse.ArgumentParser(description=__doc__) |
27 | | - |
28 | | - parser.add_argument( |
29 | | - "--zdf-path", |
30 | | - required=False, |
31 | | - type=Path, |
32 | | - default=get_sample_data_path() / "BinWithCalibrationBoard.zdf", |
33 | | - help="Path to the ZDF file", |
34 | | - ) |
| 27 | + rotation_matrix = transform[:3, :3] |
| 28 | + translation_vector = transform[:3, 3] |
35 | 29 |
|
36 | | - parser.add_argument( |
37 | | - "--roi-box-bottom-left-corner-x", |
38 | | - required=False, |
39 | | - type=float, |
40 | | - default=-80, |
41 | | - help="Distance in X coordinate from the board origin to the bottom left bin corner in checkerboard coordinate system", |
42 | | - ) |
| 30 | + transformed_points = [] |
| 31 | + for point in points: |
| 32 | + transformed_points.append(rotation_matrix @ point + translation_vector) |
43 | 33 |
|
44 | | - parser.add_argument( |
45 | | - "--roi-box-bottom-left-corner-y", |
46 | | - required=False, |
47 | | - type=float, |
48 | | - default=280, |
49 | | - help="Distance in Y coordinate from the board origin to the bottom left bin corner in checkerboard coordinate system", |
50 | | - ) |
| 34 | + return transformed_points |
51 | 35 |
|
52 | | - parser.add_argument( |
53 | | - "--roi-box-bottom-left-corner-z", |
54 | | - required=False, |
55 | | - type=float, |
56 | | - default=5, |
57 | | - help="Distance in Z coordinate from the board origin to the bottom left bin corner in checkerboard coordinate system", |
58 | | - ) |
59 | 36 |
|
60 | | - parser.add_argument( |
61 | | - "--box-dimension-in-axis-x", |
62 | | - required=False, |
63 | | - type=float, |
64 | | - default=600, |
65 | | - help="Bin dimension in X axis of the checkerboard coordinate system", |
66 | | - ) |
67 | | - |
68 | | - parser.add_argument( |
69 | | - "--box-dimension-in-axis-y", |
70 | | - required=False, |
71 | | - type=float, |
72 | | - default=400, |
73 | | - help="Bin dimension in Y axis of the checkerboard coordinate system", |
74 | | - ) |
75 | | - |
76 | | - parser.add_argument( |
77 | | - "--box-dimension-in-axis-z", |
78 | | - required=False, |
79 | | - type=float, |
80 | | - default=80, |
81 | | - help="Bin dimension in Z axis of the checkerboard coordinate system", |
82 | | - ) |
| 37 | +def _main() -> None: |
| 38 | + app = zivid.Application() |
83 | 39 |
|
84 | | - parser.add_argument( |
85 | | - "--downsample", |
86 | | - required=False, |
87 | | - type=str, |
88 | | - choices=["by2x2", "by3x3", "by4x4"], |
89 | | - help="Downsampling rate; possible value: by2x2, by3x3, and by4x4", |
90 | | - ) |
| 40 | + file_camera = get_sample_data_path() / "BinWithCalibrationBoard.zfc" |
91 | 41 |
|
92 | | - return parser.parse_args() |
| 42 | + print(f"Creating virtual camera using file: {file_camera}") |
| 43 | + camera = app.create_file_camera(file_camera) |
93 | 44 |
|
| 45 | + settings = zivid.Settings([zivid.Settings.Acquisition()]) |
94 | 46 |
|
95 | | -def roi_box_point_cloud( |
96 | | - point_cloud: PointCloud, |
97 | | - roi_box_bottom_left_corner_x: float, |
98 | | - roi_box_bottom_left_corner_y: float, |
99 | | - roi_box_bottom_left_corner_z: float, |
100 | | - box_dimension_in_axis_x: float, |
101 | | - box_dimension_in_axis_y: float, |
102 | | - box_dimension_in_axis_z: float, |
103 | | -) -> Tuple[np.ndarray, np.ndarray]: |
104 | | - """Filter point cloud based on ROI box by providing box location and dimensions. |
| 47 | + original_point_cloud = camera.capture(settings).point_cloud() |
105 | 48 |
|
106 | | - This function assumes that the point cloud is transformed to the checkerboard frame. |
| 49 | + print("Displaying the original point cloud") |
| 50 | + display_pointcloud(original_point_cloud.copy_data("xyz"), original_point_cloud.copy_data("rgba")[:, :, :3]) |
107 | 51 |
|
108 | | - Args: |
109 | | - point_cloud: Zivid point cloud |
110 | | - roi_box_bottom_left_corner_x: Distance in X coordinate from the board origin to the bottom left bin corner in checkerboard coordinate system |
111 | | - roi_box_bottom_left_corner_y: Distance in Y coordinate from the board origin to the bottom left bin corner in checkerboard coordinate system |
112 | | - roi_box_bottom_left_corner_z: Distance in Z coordinate from the board origin to the bottom left bin corner in checkerboard coordinate system |
113 | | - box_dimension_in_axis_x: Bin dimension in X axis of the checkerboard coordinate system |
114 | | - box_dimension_in_axis_y: Bin dimension in Y axis of the checkerboard coordinate system |
115 | | - box_dimension_in_axis_z: Bin dimension in Z axis of the checkerboard coordinate system |
| 52 | + print("Configuring ROI box based on bin size and checkerboard placement") |
| 53 | + roi_box_length = 545 |
| 54 | + roi_box_width = 345 |
| 55 | + roi_box_height = 150 |
116 | 56 |
|
117 | | - Returns: |
118 | | - masked_xyz: A masked numpy array of X, Y and Z point cloud coordinates (HxWx3 ndarray) |
119 | | - masked_rgba: A masked masked RGB image (HxWx3 ndarray) |
| 57 | + # Coordinates are relative to the checkerboard origin which lies in the intersection between the four checkers |
| 58 | + # in the top-left corner of the checkerboard: Positive x-axis is "East", y-axis is "South" and z-axis is "Down" |
| 59 | + roi_box_lower_right_corner = np.array([240, 260, 0.5]) |
| 60 | + roi_box_upper_right_corner = np.array( |
| 61 | + [ |
| 62 | + roi_box_lower_right_corner[0], |
| 63 | + roi_box_lower_right_corner[1] - roi_box_width, |
| 64 | + roi_box_lower_right_corner[2], |
| 65 | + ] |
| 66 | + ) |
| 67 | + roi_box_lower_left_corner = np.array( |
| 68 | + [roi_box_lower_right_corner[0] - roi_box_length, roi_box_lower_right_corner[1], roi_box_lower_right_corner[2]] |
| 69 | + ) |
120 | 70 |
|
121 | | - """ |
122 | | - xyz = point_cloud.copy_data("xyz") |
123 | | - rgba = point_cloud.copy_data("rgba") |
| 71 | + point_o_in_checkerboard_frame = roi_box_lower_right_corner |
| 72 | + point_a_in_checkerboard_frame = roi_box_upper_right_corner |
| 73 | + point_b_in_checkerboard_frame = roi_box_lower_left_corner |
124 | 74 |
|
125 | | - masked_xyz = np.copy(xyz) |
126 | | - masked_rgba = np.copy(rgba) |
| 75 | + print("Detecting and estimating pose of the Zivid checkerboard in the camera frame") |
| 76 | + detection_result = zivid.calibration.detect_feature_points(original_point_cloud) |
| 77 | + transform_checkerboard_to_camera = detection_result.pose().to_matrix() |
127 | 78 |
|
128 | | - # Creating ROI box mask |
129 | | - margin_of_box_roi = 10 |
130 | | - mask_x = np.logical_and( |
131 | | - xyz[:, :, 0] > roi_box_bottom_left_corner_x - margin_of_box_roi, |
132 | | - xyz[:, :, 0] < roi_box_bottom_left_corner_x + box_dimension_in_axis_x + margin_of_box_roi, |
133 | | - ) |
134 | | - mask_y = np.logical_and( |
135 | | - xyz[:, :, 1] < roi_box_bottom_left_corner_y + margin_of_box_roi, |
136 | | - xyz[:, :, 1] > roi_box_bottom_left_corner_y - box_dimension_in_axis_y - margin_of_box_roi, |
137 | | - ) |
138 | | - mask_z = np.logical_and( |
139 | | - xyz[:, :, 2] < roi_box_bottom_left_corner_z + margin_of_box_roi, |
140 | | - xyz[:, :, 2] > roi_box_bottom_left_corner_z - box_dimension_in_axis_z - margin_of_box_roi, |
| 79 | + print("Transforming the ROI base frame points to the camera frame") |
| 80 | + roi_points_in_camera_frame = _transform_points( |
| 81 | + [point_o_in_checkerboard_frame, point_a_in_checkerboard_frame, point_b_in_checkerboard_frame], |
| 82 | + transform_checkerboard_to_camera, |
141 | 83 | ) |
142 | | - mask = np.logical_and(np.logical_and(mask_x, mask_y), mask_z) |
143 | 84 |
|
144 | | - # Filtering out points outside the ROI box |
145 | | - masked_xyz[~mask] = np.nan |
146 | | - masked_rgba[~mask] = 0 |
| 85 | + print("Setting the ROI") |
| 86 | + settings.region_of_interest.box.enabled = True |
| 87 | + settings.region_of_interest.box.point_o = roi_points_in_camera_frame[0] |
| 88 | + settings.region_of_interest.box.point_a = roi_points_in_camera_frame[1] |
| 89 | + settings.region_of_interest.box.point_b = roi_points_in_camera_frame[2] |
| 90 | + settings.region_of_interest.box.extents = (-10, roi_box_height) |
147 | 91 |
|
148 | | - return masked_xyz, masked_rgba |
| 92 | + roi_point_cloud = camera.capture(settings).point_cloud() |
149 | 93 |
|
| 94 | + print("Displaying the ROI-filtered point cloud") |
| 95 | + display_pointcloud(roi_point_cloud.copy_data("xyz"), roi_point_cloud.copy_data("rgba")[:, :, :3]) |
150 | 96 |
|
151 | | -def _main() -> None: |
152 | | - |
153 | | - with zivid.Application(): |
154 | | - |
155 | | - user_options = _options() |
156 | | - data_file = user_options.zdf_path |
157 | | - print(f"Reading ZDF frame from file: {data_file}") |
158 | | - frame = zivid.Frame(data_file) |
159 | | - point_cloud = frame.point_cloud() |
160 | | - |
161 | | - print("Displaying the point cloud original point cloud") |
162 | | - display_pointcloud(point_cloud.copy_data("xyz"), point_cloud.copy_data("rgba")[:, :, 0:3]) |
163 | | - |
164 | | - if user_options.downsample: |
165 | | - point_cloud.downsample(user_options.downsample) |
166 | | - |
167 | | - print("Detecting and estimating pose of the Zivid checkerboard in the camera frame") |
168 | | - detection_result = zivid.calibration.detect_feature_points(point_cloud) |
169 | | - transform_camera_to_checkerboard = detection_result.pose().to_matrix() |
170 | | - |
171 | | - print("Camera pose in checkerboard frame:") |
172 | | - transform_checkerboard_to_camera = np.linalg.inv(transform_camera_to_checkerboard) |
173 | | - print(transform_checkerboard_to_camera) |
174 | | - |
175 | | - print("Transforming point cloud from camera frame to Checkerboard frame") |
176 | | - point_cloud.transform(transform_checkerboard_to_camera) |
177 | | - |
178 | | - print("Bottom-Left ROI Box corner:") |
179 | | - roi_box_bottom_left_corner_x = user_options.roi_box_bottom_left_corner_x # Positive is "East" |
180 | | - roi_box_bottom_left_corner_y = user_options.roi_box_bottom_left_corner_y # Positive is "South" |
181 | | - roi_box_bottom_left_corner_z = user_options.roi_box_bottom_left_corner_z # Positive is "Down" |
182 | | - print(f"X: {roi_box_bottom_left_corner_x}") |
183 | | - print(f"Y: {roi_box_bottom_left_corner_y}") |
184 | | - print(f"Z: {roi_box_bottom_left_corner_z}") |
185 | | - |
186 | | - print("ROI Box size:") |
187 | | - roi_box_length = user_options.box_dimension_in_axis_x |
188 | | - roi_box_width = user_options.box_dimension_in_axis_y |
189 | | - roi_box_height = user_options.box_dimension_in_axis_z |
190 | | - print(f"Length: {roi_box_length}") |
191 | | - print(f"Width: {roi_box_width}") |
192 | | - print(f"Height: {roi_box_height}") |
193 | | - |
194 | | - print("Filtering the point cloud based on ROI Box") |
195 | | - filtered_xyz, filtered_rgba = roi_box_point_cloud( |
196 | | - point_cloud, |
197 | | - roi_box_bottom_left_corner_x, |
198 | | - roi_box_bottom_left_corner_y, |
199 | | - roi_box_bottom_left_corner_z, |
200 | | - roi_box_length, |
201 | | - roi_box_width, |
202 | | - roi_box_height, |
203 | | - ) |
204 | | - |
205 | | - print("Displaying transformed point cloud after ROI Box filtering") |
206 | | - display_pointcloud(filtered_xyz, filtered_rgba[:, :, 0:3]) |
207 | | - |
208 | | - print("Displaying depth map of the transformed point cloud after ROI Box filtering") |
209 | | - display_depthmap(filtered_xyz) |
| 97 | + print("Displaying depth map of the ROI-filtered point cloud") |
| 98 | + display_depthmap(roi_point_cloud.copy_data("xyz")) |
210 | 99 |
|
211 | 100 |
|
212 | 101 | if __name__ == "__main__": |
|
0 commit comments