데이터 증강

증강은 학습의 중요한 부분입니다. Detectron2의 데이터 증강 시스템은 다음과 같은 목표를 해결하려 합니다.

  1. 여러 데이터 타입을 함께 증강하는 것 (예: 이미지를 비롯한 바운딩 박스(bounding box) 및 마스크(mask))

  2. 정적으로 선언된 증강 시퀀스를 적용하는 것

  3. 증강하려는 새로운 커스텀 데이터 타입(회전된 바운딩 박스, 비디오 클립 등)을 추가하는 것

  4. 증강에 의해 적용되는 operation 을 처리하고 조작하는 것

처음 두 기능이 대부분의 일반적인 용례를 다루며 albumentations 등 다른 라이브러리에서도 사용할 수 있습니다. 나머지 기능 지원을 위해 detectron2의 증강 API에 약간의 오버헤드가 추가됩니다. 본 튜토리얼에서 이에 대해 설명할 것입니다.

이 튜토리얼은 새로운 데이터로더를 작성할 때 증강을 사용하는 방법, 그리고 새로운 증강을 작성하는 방법에 중점을 두고 있습니다. Detectron2의 기본 데이터로더를 사용하는 경우, 데이터로더 튜토리얼 에 설명된 것처럼 사용자가 제공한 커스텀 증강 목록을 적용하는 것을 기본으로 지원합니다.

기본 사용법

기능 (1)과 (2)의 기본 사용법은 다음과 같습니다.

from detectron2.data import transforms as T
# 적용할 증강 목록을 정의.
augs = T.AugmentationList([
    T.RandomBrightness(0.9, 1.1),
    T.RandomFlip(prob=0.5),
    T.RandomCrop("absolute", (640, 640))
])  # 타입: T.Augmentation

# 증강의 입력("image"는 필수, 나머지는 선택)을 정의.
input = T.AugInput(image, boxes=boxes, sem_seg=sem_seg)
# 증강을 적용.
transform = augs(input)  # 타입: T.Transform
image_transformed = input.image  # 새로운 이미지
sem_seg_transformed = input.sem_seg  # 새로운 시맨틱 세그멘테이션

# 함께 증강해야 하는 다른 데이터가 있으면 아래와 같이 변환을 사용하십시오.
image2_transformed = transform.apply_image(image2)
polygons_transformed = transform.apply_polygons(polygons)

다음과 같이 세 가지 기본 개념이 있습니다.

  • T.Augmentation 은 입력을 수정하기 위한 “policy” 를 정의합니다.

    • __call__(AugInput) -> Transform 메소드가 in-place로 입력을 증강하고 적용된 operation을 반환합니다.

  • T.Transform 은 실제 데이터를 변환(transform)하는 operations 을 구현합니다.

    • apply_image, apply_coords 와 같은 메소드가 각 데이터 타입의 변환 방법을 정의합니다.

  • T.AugInputT.Augmentation 에 필요한 입력과 변환 방법을 저장합니다. 이 개념은 일부 고급 사용법에 필요합니다. 앞선 예시와 같이 반환된 transform 을 사용하여 T.AugInput 에 없는 다른 데이터도 증강할 수 있으므로 일반적인 모든 사례에서는 이 클래스를 직접 사용하는 것만으로 충분할 것입니다.

새로운 증강 작성

대부분의 2D 증강은 입력 이미지에 대한 정보만을 필요로 합니다. 이러한 증강은 아래와 같이 쉽게 구현할 수 있습니다.

class MyColorAugmentation(T.Augmentation):
    def get_transform(self, image):
        r = np.random.rand(2)
        return T.ColorTransform(lambda x: x * r[0] + r[1] * 10)

class MyCustomResize(T.Augmentation):
    def get_transform(self, image):
        old_h, old_w = image.shape[:2]
        new_h, new_w = int(old_h * np.random.rand()), int(old_w * 1.5)
        return T.ResizeTransform(old_h, old_w, new_h, new_w)

augs = MyCustomResize()
transform = augs(input)

이미지 외에도 주어진 AugInput 의 속성이 아래 예시와 같이 함수 시그니처의 일부라면 사용할 수 있습니다.

class MyCustomCrop(T.Augmentation):
    def get_transform(self, image, sem_seg):
        # image와 sem_seg 모두를 활용해 어디를 crop할지 결정
        return T.CropTransform(...)

augs = MyCustomCrop()
assert hasattr(input, "image") and hasattr(input, "sem_seg")
transform = augs(input)

서브클래싱을 통해 새로운 변환 operation을 추가할 수도 있습니다. T.Transform.

고급 사용법

우리 시스템에서 지원하는 몇 가지 고급 사용법 예시를 제공합니다. 이러한 옵션은 새로운 연구를 할 때 흥미로울 수 있지만 표준 용례에서는 보통 변경할 필요가 없습니다.

커스텀 변환 전략

detectron2의 Augmentation 은 증강 데이터뿐만 아니라 operationsT.Transform 으로 반환합니다. 이를 통해 사용자는 데이터에 커스텀 변환 전략(strategy)을 적용할 수 있습니다. 예시로 키포인트 데이터를 사용하겠습니다.

키포인트는 (x, y) 좌표이지만 고유의 시맨틱(semantic) 의미로 인해 증강하기가 쉽지 않습니다. 이러한 의미는 사용자만 알고 있으므로 사용자는 반환된 transform 을 보고 이를 수동으로 확장할 수 있습니다. 예를 들어, 이미지가 가로로 뒤집힐 때 “왼쪽 눈”과 “오른쪽 눈”에 대한 키포인트 어노테이션(annotation)을 바꾸고 싶을 수 있습니다. 이는 다음과 같이 해결할 수 있습니다(detectron2의 기본 데이터로더에 기본으로 포함).

# augs와 input은 앞선 예시에서 정의
transform = augs(input)  # 타입: T.Transform
keypoints_xy = transform.apply_coords(keypoints_xy)   # 좌표에 변환 적용

# 적용된 변환 목록 전체 확인
transforms = T.TransformList([transform]).transforms
# 홀수번 뒤집혔는지 확인
do_hflip = sum(isinstance(t, T.HFlipTransform) for t in transforms) % 2 == 1
if do_hflip:
    keypoints_xy = keypoints_xy[flip_indices_mapping]

다른 예시로, 키포인트 어노테이션에 종종 “가시성(visibility)”이라는 필드가 있습니다. 일련의 증강(예: cropping 등)을 통해 키포인트가 이미지의 경계를 벗어날 수 있으나 (이미지 padding 등을 통해) 다시 이미지 경계 안으로 돌아올 수 있습니다. 사용자가 이러한 키포인트에 “invisible” 레이블을 지정한 경우, 모든 변환 단계가 끝난 후에 가시성을 확인해야 합니다. 이것은 다음을 통해 달성할 수 있습니다.

transform = augs(input)  # 타입: T.TransformList
assert isinstance(transform, T.TransformList)
for t in transform.transforms:
    keypoints_xy = t.apply_coords(keypoints_xy)
    visibility &= (keypoints_xy >= [0, 0] & keypoints_xy <= [W, H]).all(axis=1)

# 참고로, detectron2의 `transform_keypoint_annotations` 함수는 위와 같은 키포인트들을 "visible"로 레이블링합니다.
# keypoints_xy = transform.apply_coords(keypoints_xy)
# visibility &= (keypoints_xy >= [0, 0] & keypoints_xy <= [W, H]).all(axis=1)

기하학적 역변환

이미지가 추론 전에 증강에 의해 전처리되면, 예측 결과(예: 세그멘테이션 마스크)의 위치가 증강된 이미지 상에 식별됩니다. inverse() API로 증강의 역변환을 적용해 원본 이미지에 대한 추론 결과를 얻어보겠습니다.

transform = augs(input)
pred_mask = make_prediction(input.image)
inv_transform = transform.inverse()
pred_mask_orig = inv_transform.apply_segmentation(pred_mask)

새로운 데이터 타입 추가

T.Transform 은 이미지, 좌표, 마스크, 박스, 폴리곤을 비롯해 변환을 적용할 일반적인 데이터 타입을 몇 가지 지원합니다. 아래 예시와 같이 새로운 데이터 타입을 등록할 수도 있습니다.

@T.HFlipTransform.register_type("rotated_boxes")
def func(flip_transform: T.HFlipTransform, rotated_boxes: Any):
    # 함수 코드를 작성
    return flipped_rotated_boxes

t = HFlipTransform(width=800)
transformed_rotated_boxes = t.apply_rotated_boxes(rotated_boxes)  # 정의한 함수가 호출됨

T.AugInput 확장

증강 과정은 주어진 입력값에 포함되어 있는 속성에만 접근할 수 있습니다. T.AugInput 는 “image”, “boxes”, “sem_seg”를 정의하는데 이들만으로도 일반적인 증강 전략에서 증강 방법을 결정하기에 충분합니다. 그렇지 않으면 커스텀 구현이 필요합니다.

AugInput에서 “transform()” 메소드를 다시 구현하면 서로 의존성이 있는 필드들을 함께 증강할 수도 있습니다. 이러한 용례가 드물긴 하지만 (예: 증강된 마스크를 기반으로 바운딩 박스를 후처리) 시스템에서 지원합니다.