3D 데이터의 시각화를 위해서든 3D데이터의 2D 데이터화를 위해서든 공간에 임의의 위치에 카메라를 두고 렌더링을 해야할 때가 있는데, 다른 툴을 쓰자니 진입 장벽이 높고 라이센스 문제도 있어 애를 먹다가 open3d로 간단히 할 수 있는 방법을 찾아 정리한다.
3D graphics 툴 대비 퀄리티나 자유도 측면에서 아쉬운 점은 분명히 있겠지만, 그 정도의 수준을 요구하지 않는다면 충분한 방법같다.
open3d.visualization.Visualizer()를 이용하는 방법인데 글로 설명하는 것보다 코드로 설명하는 것이 이해도 쉽고 혹 사용하고자 하는 사람에게도 편할 것 같아 하나의 class로 작성한 코드는 다음과 같다.
class Renderer():
def __init__(self, obj, img_h, img_w):
self.vis = o3d.visualization.Visualizer()
self.vis.create_window(visible=True, width=img_w, height=img_h)
self.vis.add_geometry(obj)
self.opt = self.vis.get_render_option()
self.opt.background_color = np.asarray([0, 0, 0])
self.opt.mesh_color_option = o3d.visualization.MeshColorOption.Color
self.ctr = self.vis.get_view_control()
print("Field of view (before changing) %.2f" % self.ctr.get_field_of_view())
self.ctr.set_constant_z_near(0.001)
self.ctr.set_constant_z_far(10.)
self.w = img_w
self.h = img_h
def render(self, cam_param, T_gk):
intrinsic = o3d.camera.PinholeCameraIntrinsic(self.w, self.h, cam_param['fx'], cam_param['fy'], cam_param['cx'], cam_param['cy'])
new_cam_param = o3d.camera.PinholeCameraParameters()
new_cam_param.intrinsic = intrinsic
new_cam_param.extrinsic = np.linalg.inv(T_gk)
self.ctr.convert_from_pinhole_camera_parameters(new_cam_param)
self.vis.poll_events()
self.vis.update_renderer()
depth_rn = np.asarray(self.vis.capture_depth_float_buffer(True))
depth_rn = depth_rn.astype(np.float32)
rgb_rn = np.asarray(self.vis.capture_screen_float_buffer(True))
rgb_rn = rgb_rn.astype(np.float32)
return depth_rn, rgb_rn
def close(self):
self.vis.clear_geometries()
self.vis.destroy_window()
필요한 것은 3D 데이터와 그 데이터를 촬영한 가상 카메라의 파라미터들이다.
카메라의 파라미터는 총 7개가 필요하다.
1. image width
2. image height
3. focal length x방향, fx
4. focal length y방향, fy
5. principal point x값, cx (정확히는 후처리를 위해 필요함, 아래 참고)
6. principal point y값, cy (정확히는 후처리를 위해 필요함, 아래 참고)
7. camera pose 4x4 matrix
- T_gk : global coord에서 바라본 cam k의 pose
- Pg = T_gk * Pk (Pg: global coord point, Pk: cam coord point)
위 7개의 값을 설정한 뒤, 사용하면 output으로 렌더링된 depth 이미지와 rgb 이미지를 얻을 수 있다.
*주의*
open3d에서 왜인지 모르겠는데 cx, cy 값 설정은 제대로 안 먹는다. 다시 말하면, cx, cy를 맘대로 넣어봤자 다음 오류가 나면서 결국엔 이미지 resolution의 중앙값으로 변경되어 처리된다. 해결 방법은 못찾았고 회피 방법은 찾았다.
[Open3d WARNING] [ViewControl] ConvertFromPinholeCameraParameters() failed because window height and width do not match.
회피 방법은, 일단 cx, cy 값은 각각 (img_w-1)/2 , (img_h-1)/2 와 같이 이미지 resolution 중앙값으로 넣고 사용한다. 그리고 렌더링된 이미지를 타겟으로 하는 cx, cy에 맞도록 warping을 해주는 방식이다.
M = np.float64([[1, 0, 0],[0, 1, 0]])
M[0, 2] = target_cx - (img_w - 1) / 2
M[1, 2] = target_cy - (img_h - 1) / 2
warped_depth = cv2.warpAffine(depth_rn, M, (img_w, img_h), flags=cv2.INTER_NEAREST)
warped_rgb = cv2.warpAffine(rgb_rn, M, (img_w, img_h), flags=cv2.INTER_NEAREST)
위와 같이 후처리해주면 동일한 효과를 얻을 수 있다.
*주의2*
create_window(visible=False)로 사용할 경우, capture_dpeth_float_buffer(True)가 아무 값도 읽지 못한다. depth의 경우, visualizer의 값을 읽어오는 것이기 때문에 visualizer가 떠있어야 하나보다. 정확히 어떻게 구현되어 있는지 모르니 이유는 모르나 RGB 렌더링은 visible=False로 두어도 되고, 아닐 경우 윈도우 뜨는게 귀찮더라도 visible=True 둬야 한다.
참고
'Knowhow > Vision' 카테고리의 다른 글
COLMAP read bin/txt/ db files in python (0) | 2022.07.13 |
---|---|
COLMAP[GUI] multi-camera setting (0) | 2022.07.07 |
Obj file의 manifold/non-manifold 구분 (0) | 2022.06.30 |
3D mesh voxelization (0) | 2022.06.17 |
Opencv large FoV image undistortion (0) | 2022.06.16 |