본문 바로가기
OpenCV/OpenCV-Chapter

CH08 OpenCV-Python 영상 분할 그랩컷(GrabCut)

by kiimy 2021. 12. 24.
728x90

그랩컷(GrabCut)이란?

• 그래프 컷(graph cut) 기반 영역 분할 알고리즘

• 영상의 픽셀을 그래프 정점으로 간주하고, 픽셀들을 두 개의 그룹으로 나누는

  최적의 컷(Max Flow Minimum Cut)을 찾는 방식

 

그랩컷 영상 분할 동작 방식

• 사각형 지정 자동 분할

• 사용자가 지정한 전경/배경 정보를 활용하여 영상 분할

영상자체를 두 개의 그룹으로 나누고 픽셀과 픽셀 사이의 관계를
어떻게 정의를 해서 최적의 컷을 만들어 낼 것이냐

전경은 시야에서 중요한 대상

배경은 그 나머지 부분으로 중요성이 덜한 대상

# 입력 영상 불러오기
src = cv2.imread('ch08\\images\\nemo.jpg')

if src is None:
    print('Image load failed!')
    sys.exit()

# 사각형 지정을 통한 초기분할
# x, y, w, h
rc = cv2.selectROI(src)

'''
cv2.grabCut(img, mask, rect, bgdModel, fgdModel, iterCount, mode=None)
            -> mask, bgdModel, fgdModel
mode : 보통 cv2.GC_INIT_WITH_RECT 모드로
       초기화하고, cv2.GC_INIT_WITH_MASK 모드로 업데이트함.
bgdModel 배경
fgdModel 전경
( 위 두개는 분할을 좀 더 좋아지는 방향으로 업데이트할 때 사용 )
'''
mask = np.zeros(src.shape[:2], np.uint8)
cv2.grabCut(src, mask, rc, None, None, 5, mode=cv2.GC_INIT_WITH_RECT)
# dst1 이미지가 이상하게 나옴
dst1 = src * mask[:, :, np.newaxis]

# mask = 0,1,2,3 네 개의 값으로 구성
# 일반 적인 copyTo에서 사용하는 mask = 0, 255 두 개의 값을 가짐
'''
*mask*
BGD = Background 0
FGD = foward 1
PR BGD = 확률 2
PR FGD = 확률 3
'''
# mask 행렬에서 값이 0 또는 2인 원소는 0으로, (background)
# 그렇지 않은 원소는 1로 설정 (foward)
mask2 = np.where((mask==0) | (mask==2), 0, 1).astype('uint8')
dst2 = src * mask2[:, :, np.newaxis] # mask2= 0,1을 가진 mask

cv2.imshow('src', src)
# mask 0,1,2,3 값을 가진 grayscale 상수를 곱해줌으로써 이미지확인
cv2.imshow('mask', mask* 32)
cv2.imshow('mask2', mask2* 32)
cv2.imshow('dst1', dst1)
cv2.imshow('dst2', dst2)
cv2.waitKey()

ROI, mask, grabcut

bgdModel, fgdModel 사용시 

# 사각형 지정을 통한 초기 분할
mask = np.zeros(src.shape[:2], np.uint8) 

# 분할을 좀 더 좋아지는 방향으로 업데이트 할 때
bgdModel = np.zeros((1,65), np.float64) # 배경모델, 무조건(1,65)지정
fgdModel = np.zeros((1,65), np.float64) # 전경모델, 마찬가지

rc = cv2.selectROI(src)

cv2.grabCut(src, mask, rc, bgdModel, fgdModel, 1, mode= cv2.GC_INIT_WITH_RECT)

mask2 = np.where((mask == 0) | (mask == 2), 0, 1).astype('uint8')
dst = src * mask2[:,:, np.newaxis]

# 초기 분할 결과 출력
cv2.imshow('dst', dst)

# 마우스 이벤트
def onMouse(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDOWN: # foward 전경
        cv2.circle(dst, (x,y), 3, (255,0,0), -1)
        cv2.circle(mask, (x,y), 3, cv2.GC_FGD, -1)
        cv2.imshow('dst', dst)

    elif event == cv2.EVENT_RBUTTONDOWN: # background 배경
        cv2.circle(dst, (x,y), 3, (0,0,255), -1)
        cv2.circle(mask, (x,y), 3, cv2.GC_BGD, -1)
        cv2.imshow('dst', dst)

    elif event == cv2.EVENT_MOUSEMOVE:
        # .. | .. or로 하면 그림 계속 그려짐
        if flags & cv2.EVENT_FLAG_LBUTTON:
            cv2.circle(dst, (x,y), 3, (255,0,0), -1)
            cv2.circle(mask, (x,y), 3, cv2.GC_FGD, -1)
            cv2.imshow('dst', dst)

        elif flags & cv2.EVENT_FLAG_RBUTTON:
            cv2.circle(dst, (x,y), 3, (0,0,255), -1)
            cv2.circle(mask, (x,y), 3, cv2.GC_BGD, -1)
            cv2.imshow('dst', dst)

# setMouse콜백           
cv2.setMouseCallback('dst', onMouse)

# 선택 부분 적용
while True:
    key = cv2.waitKey()
    if key == 13: # Enter
        # 사용자가 지정한 전경/배경 정보를 활용하여 영상 분할
        cv2.grabCut(src, mask, rc, bgdModel, fgdModel, 1, mode= cv2.GC_INIT_WITH_MASK)
        mask2 = np.where((mask==0) | (mask==2), 0, 1).astype('uint8')
        dst = src * mask2[:,:,np.newaxis]
        cv2.imshow('dst', dst)
    elif key == ord('q'):
        break
단점
배경을 구분하기 어려운경우 성능이 좋지 않다(= 잘 쓰이진 않음
== So, 입력영상이 단순해야함

 

728x90

댓글