Open3d 에서 OBJ를 확인할 때 가장 번거로운 문제가 texture가 제멋대로 처리될 때다.
대표적으로 아래 그림처럼 texture가 있음에도 검정색으로 뜬다거나, 회색으로 뜨는 상황이 있다.
검색해보면 o3d.io.read_triangle_mesh(..., enable_post_processing=True)로 설정하면 해결된다고 하는데, 이렇게 해결되지 않는게 진짜 머리 터진다.
원인이야 많이 있겠지만, 내 경험 상 이런 경우 문제는 크게 4가지로 나뉘는 것 같다.
매번 까먹고 다시 구르는게 억울해서 메모 해둔다.
*enable_post_processing=True 무지성으로 쓰지 말자.
홈페이지 보면 위와 같은 작업을 수행한다고 하는데, 저 첫 줄의 triangulate meshes with polygonal faces 저게 face index를 건든다.
따라서 저걸 true로 해두면 mesh가 정리되기는 하지만 mesh를 정리하는 과정에서 face index가 변경되므로, 원본 face 구성을 따라 코딩을 해야할 경우에는 문제가 된다. 따라서 무지성 True로 쓰면 디버깅이 어려워 진다.
1) OBJ, mtl, png가 다 준비된 상태인데 안 될 때
OBJ 파일이 있는 위치에 보통 mtl 파일과 texturemap, png 파일이 같이 있다. 이 경우 obj를 열면 자동으로 material과 texture를 읽어 떠야하는게 정상이다.
그런데 안되는 경우는 첫번째로 OBJ 파일을 열어 mtl과 png 경로 연결이 잘 되어있는지 확인해보아야 한다.
예를 들어, obj 파일을 열어보면 다음과 같이 mtl 파일 어떤 것을 쓰는지 적혀있다.
이 때 첫 줄과 두번째 줄에 있는 mtl 이름이 OBJ 파일과 같은 경로에 있는 mtl과 안 맞을 경우, 안 읽힌다.
mtl 파일 안에는 또 texture map 경로가 적혀있는데 이 경로가 또 틀렸을 경우, 안 읽힌다. 차례대로 OBJ에 mtl 경로가 잘못 적혀있으면 mtl, texture 둘 다 못 읽고, mtl에 texture 경로가 잘못 적혀있으면 texture 못 읽는다. 그러면 당연하게도 texture가 회색으로 나오거나 검정으로 나온다.
주의 ) OBJ 파일 내에 mtl 경로가 안 적혀있을 수도 있다. 이렇게 적힌 경우는 meshlab의 컨벤션을 따라 파일이 만들어져 obj 파일을 열 때 자동으로 material과 texture를 같이 읽도록 의도된 것이다. 이렇게 저장하는건 의무가 아니기 때문에 없을 수 있다.
이 경우, 1)의 케이스가 아니므로 2)를 확인해보면 된다.
2) OBJ 내 material_ids랑 textures 인덱스가 안 맞을 때
이건 진짜 왜 이렇게 되는지 이유는 모르겠다. 간혹 OBJ 파일을 읽고 나서 내부 값들을 보면 textures와 triangle_materials_ids가 여러 개일 때가 있다.
이게 실제로 obj가 여러 material로 구성되어 있으면 여러 개인게 맞는데 보통 texture map 하나에 material 1개 이므로, textures도 1개, material_ids는 전부 0으로 되어있어야 정상이다.
하지만 위와 같이 크기도 심지어 0인 texture image가 들어있고, material_ids가 안 맞을 때가 있다. (이건 사실 obj 만든 사람 실수일수도)
이 경우 보통 textures의 마지막에 원하는 texture map이 들어있는데, material_ids가 해당 index (여기선 2)가 아닐 경우, 당연히 texture를 제대로 못 읽어서 회색 아니면 숯덩이가 나온다.
enable_post_processing=True로 설정하면 크기가 0인 texture image를 정리해주고, material_ids index도 이에 따라 업데이트해주는 것 같은데, 그럼에도 불구하고 꼬여있을 수 있다. 따라서 이 두 값을 확인하는게 필요함.
3) inplace 연산 생각 못해서 UV coordinate 잘못 건드렸을 때
이건 철저히 코딩하는 사람 잘못이다. 알다시피, OBJ에서 texture를 읽는 방식은 OBJ 내의 uv 좌표를 읽고 texture map에서 해당위치 색깔을 떼어오는 식이다.
이때 OBJ 내 uv 좌표계는 texture map 해상도에 무관하게 0~1로 normalization되어 있는 형태다.
그런데 이 때 이 OBJ 내 uv 좌표계값을 잘못 건드린다면 당연히 잘못된 위치로 가서 texture를 떼어오므로, 숯덩이 texture가 나올 수 있다.
내가 저지른 끔찍한 실수는 다음과 같다.
def get_vertex_uvs(mesh):
verts = np.asarray(mesh.vertices)#.copy()
faces = np.asarray(mesh.triangles)#.copy()
uvs = np.asarray(mesh.triangle_uvs).reshape(-1, 3, 2)#.copy()
vertex_uvs = np.zeros_like(verts[:, :2]) # N 2
vertex_check = np.zeros_like(verts[:, :1]) # checked or not
for face, uv in zip(faces, uvs):
for vidx, uv_i in zip(face, uv):
uv_i[0] *= 2048
uv_i[1] *= 2048
uv_i[1] = 2048 - uv_i[1]
if vertex_check[vidx] != 0:
continue
else:
vertex_uvs[vidx] = uv_i
vertex_check[vidx] = 1
if np.sum(vertex_check) == np.shape(verts)[0]:
return vertex_uvs
else:
None
위와 같이 0~1 로 normalize 되어있는 uv 좌표계를 실제 이미지 픽셀 좌표계로 계산하기 위해서 함수를 짰었다.
그런데 이 때 저 uvs = np.asarray(mesh.triangle_uvs) 이 변수를 다룰 때, inplace 연산으로 처리를 해서 OBJ 내부에 있는 실제 triangle_uvs의 값이 바뀌어 버렸다...
위 예시에 주석 처리한 것처럼 .copy() 값으로 연산하는 식으로 inplace 연산으로부터 안전하게 계산해야 한다.
1)과 2)를 확인했음에도 texture가 망가져 나온다면 위와 같은 inplace 연산 실수가 없었는지 검증해보길 추천한다.
python만 쓰다면 shallow copy, deep copy에 무뎌질 수 있는데 놓치면 망한다.
4) Open3D 버전 문제
1), 2), 3)까지 다 확인했는데도 texture가 제멋대로 라면 마지막 방법은 open3d 버전을 의심해보는 것이다. 이슈를 보면 0.12.0 버전에서 texture가 안 불러와지는 문제가 있었다고 한다. 따라서 버전을 업데이트해보는게 최후의 방법.
'Trouble > Vision' 카테고리의 다른 글
Trimesh load_mesh, export 시 texture 제대로 안 읽히고 저장 안될 때 (0) | 2024.09.02 |
---|---|
Open3d mesh wrong vertex count, face order (Open3d mesh 제멋대로 읽혀질 때) (0) | 2024.08.07 |
Opencv cornerSubPix empty array 문제 (0) | 2024.02.27 |
Python OpenCV solvePnP 이유 모를 type error (0) | 2024.02.22 |
C++ OpenCV cvtColor memory leakage 문제 (0) | 2024.02.05 |