13장. Shape 분석과 ROI 처리
사각형 ROI
도서관에서 책 한 쪽을 볼 때 두 가지 방법이 있습니다. 책 위에 네모난 구멍이 뚫린 종이(창)를 대고 보거나, 그 쪽을 복사기로 복사해 따로 봅니다. 창으로 보는 동안 연필로 칠하면 책에 칠해지고, 복사본에 칠하면 책은 그대로입니다. NumPy 슬라이싱으로 자른 ROI 는 창이고, .copy() 를 붙이면 복사본입니다.
왼쪽 상자가 창(보기, view), 오른쪽 상자가 복사본(copy)입니다. 2장에서 잘라 내기를, 3장에서 copy 를, 4장에서 보기와 복사본의 차이를 봤습니다. ROI 에서는 이 차이가 특히 중요합니다. ROI 를 고쳐서 원본에 바로 반영하고 싶으면 창이 편하고, 원본을 지키면서 ROI 만 따로 실험하려면 복사본을 씁니다. 여기서 구멍 뚫린 종이가 슬라이싱, 복사기가 .copy() 입니다.
슬라이싱 ROI 견주기
| 쓰는 법 | 원본과 숫자를 함께 쓰나 | ROI 를 칠하면 원본은 | 쓰는 때 |
|---|---|---|---|
roi = img[y:y+h, x:x+w] | 함께 쓴다(창) | 함께 바뀐다 | ROI 를 고쳐 원본에 반영 |
roi = img[y:y+h, x:x+w].copy() | 따로 쓴다(복사본) | 그대로 | 원본을 지키며 따로 실험, 저장 |
img[y:y+h, x:x+w] = 새조각 | (대입) | 그 자리가 새조각으로 바뀐다 | 처리한 조각 붙여 넣기 |
boundingRect 로 ROI 정하기와 여백
좌표를 손으로 재지 않고, 윤곽선의 cv2.boundingRect 가 돌려준 (x, y, 가로, 세로) 로 자르면 ROI 를 자동으로 정할 수 있습니다. 이때 둘레에 여백을 붙이면 가장자리가 잘리지 않습니다. 그런데 여백 때문에 시작 번호가 0 보다 작아지면 큰 문제가 생깁니다. 파이썬 슬라이싱에서 음수 번호는 "끝에서부터"라는 뜻이라, img[-10:50] 은 오류 없이 빈 조각을 돌려줍니다.
왼쪽은 상자에 딱 맞게, 가운데는 여백 10칸을 붙여 자른 송장입니다. 가운데 쪽이 송장의 흰 테두리가 온전히 보입니다. 오른쪽은 스티커에 여백 40칸을 붙인 결과입니다. 아래쪽 끝이 그림 끝(세로 240)을 넘어서 그 자리까지만 잘렸습니다. 이렇게 시작은 max(0, ...) 로, 끝은 min(그림 크기, ...) 로 그림 안에 묶어 둡니다.
| 계산 | 송장(x=60, 여백 10) | 송장(x=60, 여백 70) |
|---|---|---|
| 시작 = x − 여백 | 50 | −10 |
| 그냥 자르면 | 50번 칸부터 | 끝에서 10번째 칸부터 → 빈 조각 |
max(0, x − 여백) | 50 | 0 |
짧은 예시
import cv2
img = cv2.imread("ch13_parcel.png")
H, W = img.shape[:2]
x, y, w, h = 60, 40, 145, 113 # 송장의 boundingRect
m = 70 # 일부러 큰 여백
# 그냥 자르면: 시작 번호가 음수(-30, -10)가 되어 "끝에서 30번째 줄부터", "끝에서 10번째 칸부터"로 읽힌다
bad = img[y - m:y + h + m, x - m:x + w + m]
print(bad.shape) # 결과: (13, 0, 3) → 가로는 0칸, 세로는 엉뚱한 13줄
# max(0, ...), min(크기, ...) 로 그림 안에 묶는다
y0, y1 = max(0, y - m), min(H, y + h + m)
x0, x1 = max(0, x - m), min(W, x + w + m)
safe = img[y0:y1, x0:x1]
print((x0, y0, x1, y1), safe.shape) # 결과: (0, 0, 275, 223) (223, 275, 3)np.clip(값, 아래, 위) 로도 같은 일을 합니다. np.clip(x - m, 0, W) 는 max(0, min(W, x - m)) 과 같습니다.
주의할 점
- 슬라이싱 ROI 는 원본을 보는 창입니다.
cv2.rectangle처럼 그리는 함수를 ROI 에 쓰면 원본에도 그려집니다. 원본을 지키려면.copy()를 붙입니다. - 슬라이싱 순서는 [세로, 가로] 이고,
boundingRect는 (x, y, 가로, 세로) 입니다.img[y:y+h, x:x+w]처럼 y 를 먼저 씁니다(2장). - 시작 번호가 음수가 되면 오류 없이 빈 조각이 나옵니다. 자른 뒤
shape를 찍어 보거나, 처음부터max(0, ...)로 묶습니다.