로봇이 Object를 grasphing하는 task의 시뮬레이션을 위해서는, object의 mesh가 필요하다.
Python의 라이브러리를 사용하면 쉽게 mesh를 생성하고 export할 수 있다.
지금부터 Open3D를 이용하여 xyz format인 point clouds를 stl, ply, obj 등 mesh형태로 export 하는 방법을 알아보자.
나는 아래 url로부터 실습파일을 다운로드하였다.
https://drive.google.com/drive/folders/1Ih_Zz9a6UcbUlaA-puEB_is7DYvXrb4w
1. 데이터 로드
Numpy와 Open3D 라이브러리를 Import 한다.
import numpy as np
import open3d as o3d
datapath를 지정하고, 데이터를 load한다.
input_path = "your_path_to_file/"
output_path = "your_path_to_output_folder/"
dataname = "sample.xyz"
point_cloud= np.loadtxt(input_path+dataname,skiprows=1)
numpy를 open3D type으로 변환한다.
pcd = o3d.geometry.PointCloud()
pcd.points = o3d.utility.Vector3dVector(point_cloud[:,:3])
pcd.colors = o3d.utility.Vector3dVector(point_cloud[:,3:6]/255)
pcd.normals = o3d.utility.Vector3dVector(point_cloud[:,6:9]) # point cloud파일에 normals이 없는 경우 생략
아래 코드를 통해서 point cloud를 visualization할 수 있다.
o3d.visualization.draw_geometries([pcd])
2. Mesh 생성을 위한 방법 선택
surface reconstruction process를 위한 방법은 두 가지가 있다.
1) Ball-Pivoting Algorithm
가상의 공을 이용하여 주어진 point clouds를 interpolate하는 triangle mesh를 생성하는 방법이다. BPA의 원리는 아주 간단하다. 사용자가 지정한 반경 p의 공이 다른 점을 포함하지 않고 세 점에 닿으면 이 세 점이 삼각형을 형성한다. 시드 삼각형으로부터 시작하여 공이 다른 점에 닿을 때까지 가장자리 주위를 회전시킨다. 즉, 가장자리의 끝 점과 접촉을 유지하면서 가장자리 주위를 회전한다. 다른 점을 만나면 그 점을 이용해 새로운 삼각형을 형성한다.이 과정을 삼각형의 모든 가장자리에 대해서 수행한다. 한 시드 삼각형에 대한 작업이 끝나면 또 다른 시드 삼각형에서 이 과정을 반복하면서 모든 points들이 이용될 수 있도록 한다.
BPA를 이용할 때 주의해야할 사항은 아래와 같다.
- 공의 반경은 어떻게 선택해야할까?: 이론적으로 공의 지름은 점의 사이의 평균거리보다 약간 크게 설정하는 것이 좋다.
- 포인트 사이의 거리가 너무 멀어서 공이 떨어져버리는 경우에는 어떻게 해야할까?: 공이 가장자리를 따라서 회전하면서 적절한 point를 놓치고 object의 다른 포인트와 만나거나 이전에 삼각형 생성을 위해 이전에 사용한 points 중 하나와 만나는 경우가 있다. 이런 경우에는 새로운 삼각형의 normal "Facet"이 point의 normal "Vertex"과 일치하는 지 확인한다. 만약 일치하지 않으면 삼각형이 아닌 구멍을 만든다.
- Object에 주름이나 골짜기가 있어서 표면과 표면 사이의 거리가 공의 크기보다 작다면 어떻게 해야할까?: 공이 주름을 무시하고 굴러가도록 놔둔다. 즉, BPA에서는 Object의 주름을 고려하지 않고 Mesh를 생성하게 되어 주름이 있는 Object에는 적합하지 않다.
- 포인트들의 집합이 떨어진 여러 영역에 분포되어 있으면 어떻게 해야할까?: 가상의 공을 다양한 위치에서 여러번 떨어뜨린다. 이를 통해서 간격이 일정하지 않아도 공이 Mesh 전체를 캡처할 수 있다.
2) Poisson reconstruction
간단하게 표현하자면 data를 smooth cloth로 감싸는 방법이다.
우리가 reconstruction하고 싶은 surface를 이용하여 아래와 같은 function X를 정의한다.
X(x, y, z): 점 (x, y, z)가 표면 내부의 점이라면 1, 외부의 점이라면 0으로 나타내는 indication function.
X의 gradient, ▽X를 계산해보면 표면 위에서 점들이 0이아닌 기울기벡터를 가지게 된다.
이 ▽X를 point cloud의 normal vector와 비교하여 가장 유사한 ▽X를 찾아 추출하면 surface를 얻을 수 있다.
Poisson reconstruction의 주요 파라미터는 4개가 있으며 각 역할은 아래와 같다.
- Depth: 생성된 mesh의 해상도를 결정한다. Poisson Reconstruction에서는 sample point를 octree 구조에 배치하며 reconstruction 해나가는데, 이때 Octree의 depth를 결정하는 parameter이다. depth를 높게 설정할수록 더 디테일한 mesh를 얻을 수 있다. Default값은 8이다. Noise가 많은 데이터를 이용해 reconstruction을 하면 outlier도 mesh 생성에 포함되기 때문에 원하는 mesh를 얻기 힘들다. 이때, Depth를 낮게 (5~7 정도)로 설정을 하면 smoothing을 제공하므로 outlier의 영향을 줄일 수 있다. 그러나 detail을 잃는다는 단점이 있다.
- Width: Octree의 width를 지정하는 parameter이다. depth가 지정된 경우에는 무시되기 때문에 자세히 다루지는 않겠다.
- Scale: reconstruction에 사용되는 cube의 직경과 sample의 bounding cube 직경의 비율을 나타낸다. 매우 추상적인 parameter이다. Default값으로 설정하여도 잘 작동한다.
- Fit: linear_fit parameter가 true로 설정된 경우, linear interpolation을 이용하여 iso-vertices의 위치를 결정한다.
3. 코드 구현
1) BPA
points 사이의 평균 거리를 이용해 가상 공의 직경을 결정한다.
distances = pcd.compute_nearest_neighbor_distance()
avg_dist = np.mean(distances)
radius = 3 * avg_dist
mesh를 생성한다.
bpa_mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(pcd,o3d.utility.DoubleVector([radius, radius * 2]))
원한다면 mesh를 export하기 전 down sampling할 수 있다.
dec_mesh = mesh.simplify_quadric_decimation(100000)
아래 코드를 통해서는 중복된 삼각형, vertices 등 불필요한 요소들을 제거할 수 있다.
dec_mesh.remove_degenerate_triangles()
dec_mesh.remove_duplicated_triangles()
dec_mesh.remove_duplicated_vertices()
dec_mesh.remove_non_manifold_edges()
생성된 mesh를 export한다. 이름, 확장자 (.ply, .obj, .stl)등을 설정할 수 있다.
o3d.io.write_triangle_mesh(output_path+"bpa_mesh.ply", dec_mesh)
2) Poisson' reconstruction
아래 코드를 통해 mesh를 생성할 수 있다.
poisson_mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_poisson(pcd, depth=8, width=0, scale=1.1, linear_fit=False)[0]
깔끔한 결과를 얻기 위해서 노란색 부분을 잘라내는 과정이 추가로 필요하다.
point clouds를 이용해 bounding box를 생성하고, bounding box밖의 mesh는 잘라낸다.
bbox = pcd.get_axis_aligned_bounding_box()
p_mesh_crop = poisson_mesh.crop(bbox)
마지막으로 생성한 mesh를 export한다.
o3d.io.write_triangle_mesh(output_path+"p_mesh_c.ply", p_mesh_crop)
4. 결과 확인
Meshlab과 같은 3D 뷰어를 통해서 결과를 확인할 수 있다.
6. 참고문헌
- Bernardini, F., Mittleman, J., Rushmeier, H., Silva, C. A., & Taubin, G. (1999). The ball-pivoting algorithm for surface reconstruction. IEEE Transactions on Visualization and Computer Graphics, 5(4), 349–359. https://doi.org/10.1109/2945.817351
- Kazhdan, M., Bolitho, M., & Hoppe, H. (2006). Poisson surface reconstruction. Symposium on Geometry Processing, 61–70. https://doi.org/10.5555/1281957.1281965
- Poux, F., PhD. (2021, December 14). Generate 3D meshes from point clouds with Python | Towards Data Science. Medium. https://towardsdatascience.com/5-step-guide-to-generate-3d-meshes-from-point-clouds-with-python-36bad397d8ba
'RobotGrasping' 카테고리의 다른 글
[Franka Emika] path recording 하기 (0) | 2023.04.27 |
---|---|
[FRANKA EMIKA] 초기 세팅 (0) | 2023.04.27 |
[Meshroom] 사진으로 3D mesh 생성하기 (0) | 2023.04.24 |
[ROS] 개발환경 구축_윈도우에서 가상머신으로 Ubuntu 설치, ROS noetic 설치 (0) | 2023.04.20 |
[Meshlab] 설치 및 그래픽카드, GPU memory 설정 (0) | 2023.04.20 |