logo

마스크와 색상 기반 탐지

실습 준비

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 색상 범위 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 상한

HSV 범위에서 마스크를 만드는 과정

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)

색상 범위에 해당하는 1채널 이진 마스크

마스크 적용

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

aba AND b
000
010
100
111
cv.bitwise_and(a, b).item()  # AND 결과
실행 결과
8
AND: 1010 & 1100 = 1000 → 8

두 비트가 모두 1인 자리만 1. 10과 12에 공통인 8의 자리만 남아 결과는 8.

OR

aba OR b
000
011
101
111
cv.bitwise_or(a, b).item()  # OR 결과
실행 결과
14
OR: 1010 | 1100 = 1110 → 14

두 비트 중 하나 이상이 1이면 결과 비트는 1. 두 수의 1을 합친 1110의 값은 14.

XOR

aba XOR b
000
011
101
110
cv.bitwise_xor(a, b).item()  # XOR 결과
실행 결과
6
XOR: 1010 ^ 1100 = 0110 → 6

두 비트가 서로 다를 때만 결과 비트는 1. 4와 2의 자리가 남아 결과는 6.

NOT

aNOT a
01
10
cv.bitwise_not(a).item()  # NOT 결과
실행 결과
245
NOT: ~00001010 = 11110101 → 245

NOT은 각 비트의 0과 1을 교환. auint8이므로 8개 비트 전체를 반전한 결과는 245.

Previous
좌표로 도형과 글자 그리기