2018년 7월 29일 일요일

PyTorch Data Loading and Processing Tutorial (1/2)

Data Loading and Processing Tutorial (1/2)


https://pytorch.org/tutorials/beginner/data_loading_tutorial.html

데이터를 로딩하는것에 관한 Tutorial 두가지 패키지가 설치되어 있어야합니다.

scikit-image: 이미지를 io와 변형을 위해
pandas: csv 파싱을 쉽게하기 위해

코드에서는 아래와 같이 사용합니다.
import pandas as pd
from skimage import io, transform

여기에서 다룰 데이터는 얼굴 안면 포즈에 관한 데이터 셋입니다. 얼굴은 아래 이미지와 같이 주석이 달아져 있다는것을 의미합니다. 주석은 각각의 얼굴에 68개 점으로 나타납니다.

data셋은 아래 링크에서 다운로드 받아서 faces.zip을 압축을 풀어서 faces 폴더 아래에 위치되도록 합니다.
https://download.pytorch.org/tutorial/faces.zip

데이터셋은 아래와 같은 csv파일로 아래와 같은 형태로 되어 있습니다.

faces/face_landmarks.csv

image_name,part_0_x,part_0_y,part_1_x,part_1_y,part_2_x, ... ,part_67_x,part_67_y
0805personali01.jpg,27,83,27,98, ... 84,134
1084239450_e76e00b7e7.jpg,70,236,71,257, ... ,128,312

CSV파일을 읽고 (N,2) 배열에 위치 정보를 얻습니다. N은 landmarks의 숫자입니다. 여기에서는 68이 됩니다. 2는 x, y 의 좌표를 의미하고요.
그건 아래와 같이 코드를 만들었습니다.

landmarks = landmarks_frame.iloc[n, 1:].as_matrix() -> 1번부터 끝까지 읽음
landmarks = landmarks.astype('float').reshape(-1, 2) -> 배열 형태를 바꿈
아래 예제는 n=65번째 내용을 읽어들임
landmarks_frame = pd.read_csv('faces/face_landmarks.csv')

n = 65
img_name = landmarks_frame.iloc[n, 0]
landmarks = landmarks_frame.iloc[n, 1:].as_matrix()
landmarks = landmarks.astype('float').reshape(-1, 2)

print('Image name: {}'.format(img_name))
print('Landmarks shape: {}'.format(landmarks.shape))
print('First 4 Landmarks: {}'.format(landmarks[:4]))

Out:
Image name: person-7.jpg
Landmarks shape: (68, 2)
First 4 Landmarks: [[32. 65.]
 [33. 76.]
 [34. 86.]
 [34. 97.]]
앞에서 65번 이미지 person-7.jpg 정보를 얻어왔으니 해당 정보를 바탕으로 이미지를 그리고 그위에 landmarks 정보를 그리도록 하는 소스입니다.
scatter 는 아래 링크를 참고하기 바랍니다.
https://pythonspot.com/matplotlib-scatterplot/

def show_landmarks(image, landmarks):
    """Show image with landmarks"""
    plt.imshow(image)
    plt.scatter(landmarks[:, 0], landmarks[:, 1], s=10, marker='.', c='r')
    plt.pause(0.001)  # pause a bit so that plots are updated

plt.figure()
show_landmarks(io.imread(os.path.join('faces/', img_name)),
               landmarks)
plt.show()



Dataset class


torch.utils.data.Dataset은 데이터 집합을 나타내는 추상 클래스입니다. 사용자 지정 데이터 집합은 데이터 집합을 상속하고 다음 메서드를 재정의해야합니다.

__len__ : len (dataset)은 데이터 집합의 크기를 반환합니다.
__getitem__ : dataset[i] 를 사용하여 i 번째 샘플을 가져올 수 있도록 색인 생성을 지원하기위해 사용

얼굴 표식 데이터 세트를 위한 데이터 세트 클래스를 생성합니다. __init__에서 csv를 읽을 것이지만 __getitem__에 이미지 읽기를 남겨 둡니다. 이것은 모든 이미지가 동시에 메모리에 저장되지 않고 필요에 따라 읽히기 때문에 메모리 측면에서 효율적입니다.

데이터 세트의 샘플은 {'image': image, 'landmarks': landmarks}가 됩니다. dataset은 샘플에 필요한 처리를 적용 할 수 있도록 선택적 인수 변환을 사용합니다.다음 절에서 변환의 유용성을 보게 될 것이다.

class FaceLandmarksDataset(Dataset):
    """Face Landmarks dataset."""

    def __init__(self, csv_file, root_dir, transform=None):
        """
        Args:
            csv_file (string): Path to the csv file with annotations.
            root_dir (string): Directory with all the images.
            transform (callable, optional): Optional transform to be applied
                on a sample.
        """
        self.landmarks_frame = pd.read_csv(csv_file)
        self.root_dir = root_dir
        self.transform = transform

    def __len__(self):
        return len(self.landmarks_frame)

    def __getitem__(self, idx):
        img_name = os.path.join(self.root_dir,
                                self.landmarks_frame.iloc[idx, 0])
        image = io.imread(img_name)
        landmarks = self.landmarks_frame.iloc[idx, 1:].as_matrix()
        landmarks = landmarks.astype('float').reshape(-1, 2)
        sample = {'image': image, 'landmarks': landmarks}

        if self.transform:
            sample = self.transform(sample)

        return sample

클래스를 인스턴스화하고 데이터 샘플을 반복합니다. 처음 4개 샘플의 크기를 출력하고 랜드 마크를 표시합니다.  (if i == 3: break 코드)

face_dataset = FaceLandmarksDataset(csv_file='faces/face_landmarks.csv',
                                    root_dir='faces/')

fig = plt.figure()

for i in range(len(face_dataset)):
    sample = face_dataset[i]

    print(i, sample['image'].shape, sample['landmarks'].shape)

    ax = plt.subplot(1, 4, i + 1)
    plt.tight_layout()
    ax.set_title('Sample #{}'.format(i))
    ax.axis('off')
    show_landmarks(**sample)

    if i == 3:
        plt.show()
        break



Out:
0 (324, 215, 3) (68, 2)
1 (500, 333, 3) (68, 2)
2 (250, 258, 3) (68, 2)
3 (434, 290, 3) (68, 2)

Transforms

위에서 볼 수있는 한 가지 문제점은 샘플의 크기가 동일하지 않다는 것입니다. 대부분의 신경망은 고정된 크기의 이미지를 기대합니다. 따라서 우리는 전처리 코드를 작성해야 합니다
여기에서 3가지 함수를 만듭니다.

Rescale: 이미지 배율 조정
RandomCrop: 임의로 잘라내기. 이것은 확대 입니다.
ToTensor: numpy 이미지를 토치 이미지로 변환합니다 (축을 교환해야합니다).

간단한 함수 대신 호출 가능한 클래스로 작성하여 변환의 매개 변수를 호출 할 때마다 전달할 필요가 없습니다. 이를 위해서는 __call__ 메쏘드와 필요한 경우 __init__ 메쏘드를 구현하기 만하면됩니다. 그러면 다음과 같은 변환을 사용할 수 있습니다.

tsfm = Transform(params)
transformed_sample = tsfm(sample)

class Rescale(object):
    """Rescale the image in a sample to a given size.

    Args:
        output_size (tuple or int): Desired output size. If tuple, output is
            matched to output_size. If int, smaller of image edges is matched
            to output_size keeping aspect ratio the same.
    """

    def __init__(self, output_size):
        assert isinstance(output_size, (int, tuple))
        self.output_size = output_size

    def __call__(self, sample):
        image, landmarks = sample['image'], sample['landmarks']

        h, w = image.shape[:2]
        if isinstance(self.output_size, int):
            if h > w:
                new_h, new_w = self.output_size * h / w, self.output_size
            else:
                new_h, new_w = self.output_size, self.output_size * w / h
        else:
            new_h, new_w = self.output_size

        new_h, new_w = int(new_h), int(new_w)

        img = transform.resize(image, (new_h, new_w))

        # h and w are swapped for landmarks because for images,
        # x and y axes are axis 1 and 0 respectively
        landmarks = landmarks * [new_w / w, new_h / h]

        return {'image': img, 'landmarks': landmarks}


class RandomCrop(object):
    """Crop randomly the image in a sample.

    Args:
        output_size (tuple or int): Desired output size. If int, square crop
            is made.
    """

    def __init__(self, output_size):
        assert isinstance(output_size, (int, tuple))
        if isinstance(output_size, int):
            self.output_size = (output_size, output_size)
        else:
            assert len(output_size) == 2
            self.output_size = output_size

    def __call__(self, sample):
        image, landmarks = sample['image'], sample['landmarks']

        h, w = image.shape[:2]
        new_h, new_w = self.output_size

        top = np.random.randint(0, h - new_h)
        left = np.random.randint(0, w - new_w)

        image = image[top: top + new_h,
                      left: left + new_w]

        landmarks = landmarks - [left, top]

        return {'image': image, 'landmarks': landmarks}


class ToTensor(object):
    """Convert ndarrays in sample to Tensors."""

    def __call__(self, sample):
        image, landmarks = sample['image'], sample['landmarks']

        # swap color axis because
        # numpy image: H x W x C
        # torch image: C X H X W
        image = image.transpose((2, 0, 1))
        return {'image': torch.from_numpy(image),
                'landmarks': torch.from_numpy(landmarks)}


나머지는 다음에 계속 합니다.





댓글 없음:

댓글 쓰기