마스크와 색상 기반 탐지
실습 준비
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 인자는 연산 위치를 제한.
OpenCV 마스크의 조건
조건은 원본 이미지와 높이·너비가 같은 1채널 uint8 배열. OpenCV는 0이 아닌 위치를 선택하며, 이진 마스크에는 관례상 255 사용.
마스크 반전
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.