16장. AI 데이터 정제와 데이터 증강
기하 증강
옷가게의 마네킹을 떠올려 봅시다. 같은 옷을 입힌 마네킹을 조금 돌려 세우고, 거울에 비춰 보고, 가까이 다가가 보고, 진열대에서 한 칸 옆으로 옮깁니다. 옷은 그대로인데 보이는 모습이 달라집니다. 기하 증강은 칸의 값(색)은 그대로 두고 자리만 옮겨 사진을 늘립니다. 14장에서 배운 함수를 그대로 씁니다.
그림의 네 상자가 이 장에서 쓰는 기하 증강입니다. 좌우 뒤집기는 cv2.flip(img, 1), 작은 회전은 getRotationMatrix2D 와 warpAffine, 자르고 키우기는 슬라이싱과 resize, 옮기기는 2×3 표의 warpAffine 입니다. 여기서 거울에 비춰 보기가 좌우 뒤집기, 마네킹을 조금 돌리기가 회전, 다가가 보기가 자르고 키우기, 옆 칸으로 옮기기가 평행 이동입니다.
회전과 옮기기로 생긴 빈 곳은 검게 칠하지 않고 BORDER_REFLECT(가장자리를 거울처럼 비춰 채우기)로 채웠습니다. 셋째 그림의 아래쪽 모서리를 보면 식탁보 무늬가 비친 듯 이어집니다. 14장처럼 검게 채우면 모델이 "검은 세모가 있는 사진"을 배울 수 있습니다.
이 장의 기하 증강 설정
| 증강 | 함수 | 이 장의 세기 | 빈 곳 |
|---|---|---|---|
| 좌우 뒤집기 | cv2.flip(img, 1) | 걸거나 안 걸거나 | 생기지 않음 |
| 작은 회전 | getRotationMatrix2D, warpAffine | −15도 ~ +15도 | 반사로 채움 |
| 자르고 키우기 | img[y:y+h, x:x+w], resize | 가로·세로의 80~95% 를 잘라 원래 크기로 | 생기지 않음 |
| 옮기기 | 2×3 표 [[1, 0, dx], [0, 1, dy]] | 몇 칸에서 수십 칸 | 반사로 채움 |
좌우 뒤집기와 상자 좌표
물건의 자리를 상자로 적은 데이터(17장부터 다루는 탐지용 데이터)는 그림을 뒤집으면 상자도 함께 옮겨야 합니다. 좌우로 뒤집으면 가로 x 가 거울처럼 바뀝니다. 가로가 200 인 그림에서 x 가 40 인 점은 200 − 40 = 160 으로 갑니다. 상자의 왼쪽 끝과 오른쪽 끝은 자리가 서로 바뀝니다. 이 장의 증강은 사진 전체에 이름 하나를 붙이는 분류용이라 상자가 없습니다. 상자를 함께 옮기는 증강은 17장에서 다룹니다.
짧은 예시
import cv2
import numpy as np
img = cv2.imread("ch16_raw/apple_04.png")
h, w = img.shape[:2]
print(w, h) # 결과: 200 150
# 좌우 뒤집기: 왼쪽 끝 칸이 오른쪽 끝으로 간다
flip = cv2.flip(img, 1)
print(np.array_equal(flip[:, 0], img[:, w - 1])) # 결과: True
# 상자 (x1, x2) = (40, 100) 을 좌우 뒤집으면: 새 x1 = 가로 - 옛 x2, 새 x2 = 가로 - 옛 x1
x1, x2 = 40, 100
print(w - x2, w - x1) # 결과: 100 160
# 15도 돌리기: 빈 곳은 BORDER_REFLECT(가장자리 거울 반사)로 채운다
M = cv2.getRotationMatrix2D((w / 2, h / 2), 15, 1.0)
rot = cv2.warpAffine(img, M, (w, h), borderMode=cv2.BORDER_REFLECT)
print(rot.shape) # 결과: (150, 200, 3) → 크기 그대로상자 좌표를 바꾸는 식에서 "가로 − 옛 x2" 가 새 왼쪽 끝인 까닭은, 뒤집으면 오른쪽 끝(100)이 왼쪽으로 오기 때문입니다.
주의할 점
- 자르고 키울 때 너무 많이 자르면 과일이 잘려 나갑니다. 과일이 사진 가운데 있다고 믿지 말고, 자른 결과를 눈으로 확인합니다.
- 위아래 뒤집기는 위에서 내려다본 사진(접시 위의 과일, 부품 검사)에는 괜찮지만, 옆에서 찍은 사진에서는 그림자가 위로 가서 어색해집니다. 이 장은 좌우 뒤집기만 씁니다.