NERF 코드분석 3.load_LINEMOD

코드짜는침팬지·2023년 9월 16일
0

학부 연구생

목록 보기
3/10
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에 대해 좀 설명 하자면
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

load_LINEMOD_data 함수는 LINEMOD 데이터셋을 로드하는 함수다.
여기서 LINEMOD는 3D 객체 인식 및 포즈 추정을 위한 데이터셋이다.

함수의 구조와 주요 기능을 분석하면 다음과 같이 볼 수 있다:

입력:

basedir: 데이터셋의 기본 디렉토리.
half_res: 이미지의 해상도를 반으로 줄일지 여부.
testskip: 테스트 데이터를 로드할 때 건너뛸 간격.

  • 입력들의 경우 기본적으로 좀 더 빠른 트레이닝을 위한 도구라 보면 된다.
    A100을 이용해서 돌려도 하루 이상 걸리기 때문에 초반에 테스트 할 때는
    다음 기능들이 도움이 된다.

출력:

imgs: 로드된 이미지들.
poses: 각 이미지에 대한 3D 포즈.
render_poses: 렌더링 포즈.
[H, W, focal]: 이미지의 높이, 너비 및 초점 거리.

K: intrinsic matrix.

  • K는 intrinsic matrix 또는 camera calibration matrix라고도 불린다.
    이 행렬은 카메라의 내부 파라미터를 나타내는데,
    이 내부 파라미터는 카메라의 렌즈와 이미지 센서의 특성에 의존하며,
    3D 포인트를 2D 이미지 평면에 투영할 때 사용된다.

Intrinsic matrix K는 일반적으로 다음과 같은 형태를 가진다:

[fxscx0fycy001]\begin{bmatrix} f_x & s & c_x \\ 0 & f_y & c_y \\ 0 & 0 & 1 \\ \end{bmatrix}

여기서 각 요소는 다음과 같은 의미를 가진다:

  • ( f_x )와 ( f_y ): x축과 y축의 초점 거리(focal lengths)로, 이 값들은 이미지 센서의 크기와 카메라의 렌즈에 의존한다.
  • ( c_x )와 ( c_y ): 이미지의 중심점 (optical center or principal point)의 좌표다. 대부분의 경우, 이미지의 중심에 가깝다.
  • ( s ): 스큐(skew) 파라미터로, x축과 y축 사이의 각도를 나타낸다.
    요즘 사용하는 카메라는 대부분 0에 가깝다.
    Intrinsic matrix는 카메라의 내부 구조와 관련된 파라미터만을 포함하므로,
    카메라를 다른 위치나 방향으로 움직여도 변하지 않는다.

따라서 한 번 카메라를 보정하면 동일한 카메라에 대해 계속 사용할 수 있다.

i_split: 데이터 분할 인덱스.

near: 가장 가까운 거리.
far: 가장 먼 거리.

  • 이 두 변수는 렌더링할 때 사용되는 깊이 범위를 나타내는데,
    3D 렌더링에서는 특정 깊이 범위 내의 객체만 렌더링하는 것이 일반적이다.
    near와 far는 이 깊이 범위를 정의하고,
    이를 통해 불필요한 객체의 렌더링을 방지하고, 렌더링 성능을 향상시키는 기능을 할 수 있다.
    또한 깊이 정보는 3D 객체 인식에서 중요한 역할을 하는데, near와 far를 사용하여 깊이 정보의 정확도를 조절할 수 있다.

작동방식:

  1. 데이터셋은 'train', 'val', 'test'의 세 가지 분할로 구성된다.
    각 분할에 대한 메타데이터를 JSON 파일에서 로드한다.
    각 분할에 대해 이미지와 포즈를 로드한다.
    이미지는 RGBA 형식으로 로드되며, 포즈는 4x4 변환 행렬로 로드된다.
    그 다음 이미지의 높이, 너비 및 초점 거리를 추출한다.

  2. render_poses는 렌더링을 위한 포즈를 생성한다.
    이는 주어진 각도 범위 내에서 균일하게 분포된 포즈를 생성한다.
    half_res 옵션이 활성화된 경우, 이미지의 해상도를 반으로 줄이고,
    near와 far는 데이터셋의 가장 가까운 및 가장 먼 거리를 나타낸다.

:

  1. trans_t, rot_phi, rot_theta, pose_spherical: 이 함수들은 3D 변환을 위한 행렬을 생성한다.
    그중 pose_spherical 함수는 주어진 각도와 반지름을 사용하여 3D 공간에서의 카메라 포즈를 계산할 수 있다.
    pose_spherical 함수는 주어진 구면 좌표 (theta, phi, radius)를 사용하여 3D 공간에서의 카메라 포즈를 계산하는데,
    여기서 theta와 phi는 각도를 나타내며, radius는 원점에서 카메라까지의 거리를 나타낸다.
profile
학과 꼴찌 공대 호소인

0개의 댓글