import os
import torch
import numpy as np
import imageio
import json
import torch.nn.functional as F
import cv2
trans_t = lambda t : torch.Tensor([
[1,0,0,0],
[0,1,0,0],
[0,0,1,t],
[0,0,0,1]]).float()
rot_phi = lambda phi : torch.Tensor([
[1,0,0,0],
[0,np.cos(phi),-np.sin(phi),0],
[0,np.sin(phi), np.cos(phi),0],
[0,0,0,1]]).float()
rot_theta = lambda th : torch.Tensor([
[np.cos(th),0,-np.sin(th),0],
[0,1,0,0],
[np.sin(th),0, np.cos(th),0],
[0,0,0,1]]).float()
def pose_spherical(theta, phi, radius):
c2w = trans_t(radius)
c2w = rot_phi(phi/180.*np.pi) @ c2w
c2w = rot_theta(theta/180.*np.pi) @ c2w
c2w = torch.Tensor(np.array([[-1,0,0,0],[0,0,1,0],[0,1,0,0],[0,0,0,1]])) @ c2w
return c2w
def load_LINEMOD_data(basedir, half_res=False, testskip=1):
splits = ['train', 'val', 'test']
metas = {}
for s in splits:
with open(os.path.join(basedir, 'transforms_{}.json'.format(s)), 'r') as fp:
metas[s] = json.load(fp)
all_imgs = []
all_poses = []
counts = [0]
for s in splits:
meta = metas[s]
imgs = []
poses = []
if s=='train' or testskip==0:
skip = 1
else:
skip = testskip
for idx_test, frame in enumerate(meta['frames'][::skip]):
fname = frame['file_path']
if s == 'test':
print(f"{idx_test}th test frame: {fname}")
imgs.append(imageio.imread(fname))
poses.append(np.array(frame['transform_matrix']))
imgs = (np.array(imgs) / 255.).astype(np.float32) # keep all 4 channels (RGBA)
poses = np.array(poses).astype(np.float32)
counts.append(counts[-1] + imgs.shape[0])
all_imgs.append(imgs)
all_poses.append(poses)
i_split = [np.arange(counts[i], counts[i+1]) for i in range(3)]
imgs = np.concatenate(all_imgs, 0)
poses = np.concatenate(all_poses, 0)
H, W = imgs[0].shape[:2]
focal = float(meta['frames'][0]['intrinsic_matrix'][0][0])
K = meta['frames'][0]['intrinsic_matrix']
print(f"Focal: {focal}")
render_poses = torch.stack([pose_spherical(angle, -30.0, 4.0) for angle in np.linspace(-180,180,40+1)[:-1]], 0)
if half_res:
H = H//2
W = W//2
focal = focal/2.
imgs_half_res = np.zeros((imgs.shape[0], H, W, 3))
for i, img in enumerate(imgs):
imgs_half_res[i] = cv2.resize(img, (W, H), interpolation=cv2.INTER_AREA)
imgs = imgs_half_res
# imgs = tf.image.resize_area(imgs, [400, 400]).numpy()
near = np.floor(min(metas['train']['near'], metas['test']['near']))
far = np.ceil(max(metas['train']['far'], metas['test']['far']))
return imgs, poses, render_poses, [H, W, focal], K, i_split, near, far
동차행렬에 대한 설명은 위의 내용은 1장에서 이미 설명 했으니
아래를 참고하면 된다.
NERF 코드 분석 1.load_blender 그리고 동차 좌표계
위에서 설명하지 않았던 부분으로 c2w에 대해 좀 설명 하자면
c2w (camera-to-world 변환)는 "camera-to-world" 변환을 나타낸다.
이 변환은 카메라 좌표계에서의 점을 세계 좌표계로 변환하는 데 사용되는데,
이 함수에서는 c2w 변환을 구성하여 카메라의 위치와 방향을 결정한다.
@ 연산자는 행렬 곱셈 연산이다.
transpose같은 기능 안쓰고 바로 행렬 곱셈이 가능한 편리한 기능이다.
c2w = trans_t(radius): 카메라를 z축을 따라 radius만큼 이동시키는 변환 행렬을 생성한다.
c2w = rot_phi(phi/180.*np.pi) @ c2w: 카메라를 x축을 따라 phi 각도만큼 회전시킨다.
여기서 각도는 라디안 단위로 변환된다.
c2w = rot_theta(theta/180.*np.pi) @ c2w: 카메라를 y축을 따라 theta 각도만큼 회전시킨다.
마찬가지로 각도는 라디안 단위로 변환된다.
c2w = torch.Tensor(...) @ c2w: 마지막 변환은 카메라의 방향을 조정하는 데 사용된다.
이 특정 행렬은 카메라의 방향을 y축에서 z축으로 바꾸는 데 사용된다.
결론적으로 c2w 변환은 주어진 구면 좌표를 사용하여
카메라의 위치와 방향을 세 좌표계에서 정의한다.
load_LINEMOD_data 함수는 LINEMOD 데이터셋을 로드하는 함수다.
여기서 LINEMOD는 3D 객체 인식 및 포즈 추정을 위한 데이터셋이다.
함수의 구조와 주요 기능을 분석하면 다음과 같이 볼 수 있다:
입력:
basedir: 데이터셋의 기본 디렉토리.
half_res: 이미지의 해상도를 반으로 줄일지 여부.
testskip: 테스트 데이터를 로드할 때 건너뛸 간격.
출력:
imgs: 로드된 이미지들.
poses: 각 이미지에 대한 3D 포즈.
render_poses: 렌더링 포즈.
[H, W, focal]: 이미지의 높이, 너비 및 초점 거리.
K: intrinsic matrix.
Intrinsic matrix K는 일반적으로 다음과 같은 형태를 가진다:
여기서 각 요소는 다음과 같은 의미를 가진다:
따라서 한 번 카메라를 보정하면 동일한 카메라에 대해 계속 사용할 수 있다.
i_split: 데이터 분할 인덱스.
near: 가장 가까운 거리.
far: 가장 먼 거리.
데이터셋은 'train', 'val', 'test'의 세 가지 분할로 구성된다.
각 분할에 대한 메타데이터를 JSON 파일에서 로드한다.
각 분할에 대해 이미지와 포즈를 로드한다.
이미지는 RGBA 형식으로 로드되며, 포즈는 4x4 변환 행렬로 로드된다.
그 다음 이미지의 높이, 너비 및 초점 거리를 추출한다.
render_poses는 렌더링을 위한 포즈를 생성한다.
이는 주어진 각도 범위 내에서 균일하게 분포된 포즈를 생성한다.
half_res 옵션이 활성화된 경우, 이미지의 해상도를 반으로 줄이고,
near와 far는 데이터셋의 가장 가까운 및 가장 먼 거리를 나타낸다.
: