logo

이미지 좌표계와 픽셀 조작

실습 준비

import cv2 as cv

image = cv.imread("balloon.webp")

if image is None:
    raise FileNotFoundError("이미지를 읽을 수 없습니다")

이미지 표시

show() 함수는 OpenCV와 이미지·영상 입출력에서 정의한 함수를 사용함.

height, width, channels = image.shape
width, height, channels

이미지 좌표계

이미지의 원점 (0, 0)은 왼쪽 위임. 오른쪽으로 갈수록 x 좌표가 커지고 아래쪽으로 갈수록 y 좌표가 커짐.

(0, 0) ─────────────→ x
   │
   │
   │
   ↓
   y

좌표는 보통 (x, y) 순서로 표현하지만 NumPy 배열은 [행, 열], 즉 [y, x] 순서로 접근함.

표현순서
이미지 좌표(x, y)
NumPy 인덱싱image[y, x]
NumPy 이미지 모양(height, width, channels)

픽셀 조회

이미지 중앙의 좌표와 BGR 픽셀값을 확인함.

x = width // 2
y = height // 2
pixel = image[y, x]

{
    "좌표": (x, y),
    "BGR": pixel.tolist(),
}

OpenCV 컬러 이미지의 마지막 축은 B, G, R 채널 순서임.

blue, green, red = image[y, x]

{
    "B": int(blue),
    "G": int(green),
    "R": int(red),
}

채널 하나만 조회하려면 세 번째 인덱스를 사용함.

image[y, x, 2]  # R 채널 값

픽셀 변경과 이미지 복사

NumPy 배열은 픽셀값을 직접 변경 가능함. 원본을 보존하려면 먼저 copy()로 복사본을 만듦.

edited = image.copy()
edited[y, x] = (0, 0, 255)  # BGR의 빨강
show(edited)

edited = image는 같은 배열에 이름만 하나 더 붙이는 것이므로 edited를 바꾸면 image도 바뀜. 독립적인 결과 이미지에는 image.copy()를 사용함.

행과 열 선택

배열 슬라이싱으로 이미지의 행과 열을 선택 가능함.

top_half = image[: height // 2, :]
show(top_half)
left_half = image[:, : width // 2]
show(left_half)
  • image[y1:y2, :]: y 방향의 행 범위 선택
  • image[:, x1:x2]: x 방향의 열 범위 선택
  • 슬라이스의 끝 위치인 y2, x2는 포함하지 않음

관심 영역과 자르기

관심 영역(region of interest, ROI)은 이미지에서 처리하려는 직사각형 영역임. (x, y)를 왼쪽 위 좌표, (w, h)를 영역의 가로·세로 크기로 사용함.

x = width // 2
y = height // 3
w = width // 4
h = height // 5

cropped = image[y : y + h, x : x + w].copy()
show(cropped)

NumPy에서는 세로 범위를 먼저 작성하므로 image[y:y+h, x:x+w] 순서임.

cropped.shape  # (h, w, channels)

슬라이스와 원본의 관계

NumPy 슬라이스는 일반적으로 원본 데이터를 공유하는 뷰(view)임. 슬라이스를 수정하면 원본 역할을 하는 배열도 함께 바뀜.

view_example = image.copy()
roi_view = view_example[y : y + h, x : x + w]
roi_view[:] = (0, 0, 255)
show(view_example)

원본과 독립된 ROI가 필요하면 슬라이스 뒤에 copy()를 사용함.

roi_copy = image[y : y + h, x : x + w].copy()

ROI를 수정하기 전에 복사 여부 확인

슬라이스가 원본과 데이터를 공유한다는 사실을 모르고 수정하면 원본 이미지까지 바뀔 수 있음. 결과 이미지를 따로 만들 때는 먼저 image.copy()를 사용함.

특정 영역 변경

슬라이스에 하나의 값을 대입하면 선택한 모든 픽셀에 브로드캐스팅됨.

검정으로 채우기:

blocked = image.copy()
blocked[y : y + h, x : x + w] = 0
show(blocked)

빨강으로 채우기:

red_region = image.copy()
red_region[y : y + h, x : x + w] = (0, 0, 255)
show(red_region)

선택한 영역의 B 채널만 0으로 만들기:

blue_removed = image.copy()
blue_removed[y : y + h, x : x + w, 0] = 0
show(blue_removed)

현재 영역의 픽셀값을 이용한 연산도 가능함. 다음 코드는 선택한 영역만 색 반전함.

inverted_region = image.copy()
region = inverted_region[y : y + h, x : x + w]
inverted_region[y : y + h, x : x + w] = 255 - region
show(inverted_region)

cv.add()를 ROI에 적용하면 선택한 영역만 밝게 만들 수 있음. 결과가 255를 넘으면 255로 포화됨.

brightened_region = image.copy()
region = brightened_region[y : y + h, x : x + w]
brightened_region[y : y + h, x : x + w] = cv.add(region, 60)
show(brightened_region)

관심 영역 복사

잘라 낸 ROI를 크기가 같은 다른 영역에 대입하면 이미지 안에서 복사 가능함.

source = image[y : y + h, x : x + w].copy()
target_x = width // 8
target_y = height * 2 // 3

pasted = image.copy()
pasted[target_y : target_y + h, target_x : target_x + w] = source
show(pasted)

대입하는 원본과 대상 영역의 높이, 너비, 채널 수가 같아야 함.

source.shape, pasted[target_y : target_y + h, target_x : target_x + w].shape

좌표로 도형과 글자 그리기

OpenCV의 그리기 함수는 좌표를 (x, y) 순서로 받음. NumPy 인덱싱의 [y, x]와 구분해야 함.

annotated = image.copy()

cv.rectangle(
    annotated,
    (x, y),
    (x + w, y + h),
    (0, 255, 0),
    3,
)

cv.circle(
    annotated,
    (x + w // 2, y + h // 2),
    6,
    (0, 0, 255),
    -1,
)

cv.putText(
    annotated,
    "ROI",
    (x, y - 10),
    cv.FONT_HERSHEY_SIMPLEX,
    0.8,
    (0, 255, 0),
    2,
    cv.LINE_AA,
)

show(annotated)

cv.rectangle(), cv.circle(), cv.putText()는 전달한 배열을 직접 수정함. 원본을 보존하려면 그리기 전에 복사본을 만듦.

Previous
색 공간과 이미지 보정