마스크와 색상 기반 탐지
실습 준비
import cv2 as cv
import numpy as np
from yeom import to_pil
image = cv.imread("balloon.webp")
마스크
마스크(mask): 이미지에서 처리할 픽셀을 표시하는 1채널 배열. 원본 이미지와 높이·너비가 같고, 보통 uint8 자료형의 0과 255를 사용.
| 마스크 값 | 의미 |
|---|---|
| 0 | 선택하지 않은 영역 |
| 255 | 선택한 영역 |
mask_example = np.array(
[
[0, 0, 255, 255],
[0, 255, 255, 0],
],
dtype=np.uint8,
)
mask_example
array([[ 0, 0, 255, 255],
[ 0, 255, 255, 0]], dtype=uint8)
마스크에는 원본의 색 정보가 없고, 각 위치의 사용 여부만 표시.
색상 범위로 마스크 만들기
HSV 색 공간은 색상 H와 밝기 V가 분리되어 있어 색 범위 지정에 유리.
hsv = cv.cvtColor(image, cv.COLOR_BGR2HSV)
lower = np.array([40, 50, 50], dtype=np.uint8) # 초록 계열의 H·S·V 하한
upper = np.array([80, 255, 255], dtype=np.uint8) # 초록 계열의 H·S·V 상한

cv.inRange(): 각 채널이 하한과 상한 사이인 픽셀은 255, 나머지는 0으로 변환. 하한과 상한도 범위 안.
mask = cv.inRange(hsv, lower, upper)
mask.shape # 형태
(1024, 1024)
mask.dtype # 자료형
dtype('uint8')
np.unique(mask) # 고유한 픽셀값
array([ 0, 255], dtype=uint8)
결과는 원본과 높이·너비가 같은 2차원 uint8 배열. 고유 픽셀값은 0과 255.
to_pil(mask)

마스크 적용
cv.bitwise_and()에 같은 이미지 두 개와 mask를 전달하면 선택한 픽셀만 원래 값 유지. 나머지는 0으로 바뀌어 검게 표시.
selected = cv.bitwise_and(image, image, mask=mask)
to_pil(selected)

같은 이미지끼리 AND한 픽셀값은 변화 없음. mask 인자는 연산 위치를 제한.
마스크 반전
cv.bitwise_not(): 각 비트를 반전. 0과 255로 구성된 마스크에서는 선택 영역과 제외 영역을 서로 교환.
inverse_mask = cv.bitwise_not(mask)
to_pil(inverse_mask)

excluded = cv.bitwise_and(image, image, mask=inverse_mask)
to_pil(excluded)

반전 마스크에서는 기존 초록색 선택 영역이 검정이고 나머지 픽셀은 유지. 원래 마스크와 겹치지 않으며 두 마스크의 OR 결과는 전체 영역.
비트 연산
비트 연산(bitwise operation): 정수를 구성하는 각 비트를 같은 위치끼리 계산.
| 연산 | 결과가 1이 되는 조건 |
|---|---|
| AND | 두 비트가 모두 1 |
| OR | 하나 이상의 비트가 1 |
| XOR | 두 비트가 서로 다름 |
| NOT | 입력 비트를 반전 |
10과 12의 4비트 이진수 표현은 각각 1010, 1100.
a = np.array([10], dtype=np.uint8)
b = np.array([12], dtype=np.uint8)
AND
a |
b |
a AND b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
cv.bitwise_and(a, b).item() # AND 결과
8
AND: 1010 & 1100 = 1000 → 8
두 비트가 모두 1인 자리만 1. 10과 12에 공통인 8의 자리만 남아 결과는 8.
OR
a |
b |
a OR b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 1 |
cv.bitwise_or(a, b).item() # OR 결과
14
OR: 1010 | 1100 = 1110 → 14
두 비트 중 하나 이상이 1이면 결과 비트는 1. 두 수의 1을 합친 1110의 값은 14.
XOR
a |
b |
a XOR b |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
cv.bitwise_xor(a, b).item() # XOR 결과
6
XOR: 1010 ^ 1100 = 0110 → 6
두 비트가 서로 다를 때만 결과 비트는 1. 4와 2의 자리가 남아 결과는 6.
NOT
a |
NOT a |
|---|---|
| 0 | 1 |
| 1 | 0 |
cv.bitwise_not(a).item() # NOT 결과
245
NOT: ~00001010 = 11110101 → 245
NOT은 각 비트의 0과 1을 교환. a가 uint8이므로 8개 비트 전체를 반전한 결과는 245.
퀴즈
OpenCV 이미지 마스크의 일반적인 특징으로 맞는 것을 모두 고르세요.
퀴즈를 풀려면 대화형 기능을 불러와야 합니다.