Python/파이썬 OpenCV 공부

[파이썬 OpenCV] 그랩컷을 이용한 영상 분할 - cv2.grabCut

AI 꿈나무 2020. 10. 13. 19:35
반응형

그랩컷 - GrabCut

 그랩컷은 그래프 컷(graph cut)기반 영역 분할 알고리즘입니다.

 그래프 알고리즘에서 사용되는 미니멀 컷 알고리즘을 이용해서 영역을 분할합니다.

 

 

 영상의 픽셀을 그래프 정점으로 간주하고, 픽셀들을 두 개의 그룹(객체 그룹, 배경 그룹)으로 분할하는 최적의 컷(Max Flow Minimum Cut)을 찾는 방식입니다.

 

 이 알고리즘으로 객체와 배경을 구분할 수 있습니다.

 객체를 가운데, 배경은 바깥 부분으로 간주합니다.

 

 

 크게 2가지 방법으로 적용할 수 있습니다.

1. 객체 위치를 러프하게 사각형 형태로 주는 방식

2. 객체 부분과 배경 부분을 마우스로 지정해주고 정보를 제공하고 다시 업데이트 하는 방식

 

1. 그랩컷 함수 - cv2.grabCut

 인자를 어떻게 주냐에 따라 단순하게 사용할 수도 있고 복잡하게 사용할 수 있습니다.

 

cv2.grabCut(img, mask, rect, bgdModel, fgdModel, iterCount, mode=None) -> mask, bgdModel, fgdModel

• img: 입력 영상. 8비트 3채널.

• mask: 입출력 마스크. cv2.GC_BGD(0), cv2.GC_FGD(1), cv2.GC_PR_BGD(2), cv2.GC_PR_FGD(3) 네 개의 값으로 구성됨. cv2.GC_INIT_WITH_RECT 모드로 초기화.

• rect: ROI 영역. cv2.GC_INIT_WITH_RECT 모드에서만 사용됨

• bgdModel: 임시 배경 모델 행렬. 같은 영상 처리 시에는 변경 금지.

• fgdModel: 임시 전경 모델 행렬. 같은 영상 처리 시에는 변경 금지.

• iterCount: 결과 생성을 위한 반복 횟수.

• mode: cv2.GC_로 시작하는 모드 상수. 보통 cv2.GC_INIT_WITH_RECT 모드로 초기화하고, cv2.GC_INIT_WITH_MASK 모드로 업데이트함.

mask

 이전까지 사용했던 마스크 영상과는 다릅니다. 이전 마스크는 0또는 255로 구성되어있는 마스크 였지면 여기에서 mask는 (0,1,2,3) 4개의 값으로 구성되어있습니다. 이는 함수 안에서 계속 업데이트 하는 방식으로 구성되어있습니다. 4개의 값은 배경(cv2.GC_BGD(0)), 전경(cv2.GC_FGD(1)), 배경(cv2.GC_PR_BGD(2)), 전경(cv2.GC_PR_FGD(3))로 구성되어 있습니다.

 mode는 cv2.GC_INIT_WITH_RECT 모드로 초기화할 수 있습니다.

 

bgdModel, fhdMode

 재귀 형태로 계속 분할하고 결과가 좋아지는 방향으로 업데이트 하는 것을 원할 때 bgdmodel, fgdmodel 파라미터를 설정합니다. 한번 동작을 원한다면 run을 입력합니다.

 

mode

cv2.GC_로 시작하는 모드 상수. 보통 cv2.GC_INIT_WITH_RECT 모드로 초기화하고, cv2.GC_INIT_WITH_MASK 모드로 업데이트합니다. 이 2가지 플래그만 알아도 됩니다.

 

2. 그랩컷 영상 분할 예제

(1) 객체 위치를 관심 영역으로 설정하고 객체 분할

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

if src is None:
    print('Image load failed!')
    sys.exit()
    
# 사장형 지정을 통한 초기 분할
rc = cv2.selectROI(src) # 초기 위치 지정하고 모서리 좌표 4개를 튜플값으로 반환
mask = np.zeros(src.shape[:2], np.unit8) # 마스크는 검정색으로 채워져있고 입력 영상과 동일한 크기

# 결과를 계속 업데이트 하고 싶으면 bgd, fgd 입력
cv2.grabCut(src, mask, rc, None, None, 5, cv2.GC_INIT_WITH_RECT)

# grabCut 자료에서 0,2는 배경, 1,3은 전경입니다.
# mask == 0 or mask == 2를 만족하면 0으로 설정 아니면 1로 설정합니다
mask2 = np.where((mask == 0) | (mask == 2), 0, 1).astype('unit8')

# np.newaxis로 차원 확장
dst = src * mask2[:, :, np.newaxis]

cv2.imshow('dst', dst)
cv2.waitKey()
cv2.destroyAllWindows()

 

 

 

(2) 계속 업데이트 하는 방식

 출력된 영상에 마우스로 객체, 배경 부분을 지정해주고 다시 출력합니다.

 잘못 구분된 영상을 업데이트하여 퀄리티 있는 영상을 출력할 수 있습니다.

 

# 입력 영상 불러오기
src2 = cv2.imread('han.jpg')
src = cv2.resize(src2, (1920, 1280))

if src is None:
    print('Image load failed!')
    sys.exit()
    
# 사각형 지정을 통한 초기 분할
mask = np.zeros(src.shape[:2], np.unit8) # 마스크
bgdModel = np.zeros((1, 65), np.float64) # 배경 모델 무조건 1행 65열, float64
fgdModel = np.zeros((1, 65), np.float64) # 전경 모델 무조건 1행 65열, float64

rc = cv2.selectROI(src)

# RECT는 사용자가 사각형 지정. 이 값에서 계속 업데이트
cv2.grabCut(src, mask, rc, bgdModel, fgdModel, 1, cv2.GC_INIT_WITH_RECT)

# mask 4개 값을 2개로 변환
mask = np.where((mask == 0) | (mask == 2), 0, 1).astype('unit8')
dst = src * mask2[:, :, np.newaxis]

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

# 마우스 이벤트 처리 함수 등록
def on_mouse(event, x, y, flags, param):
    if event == cv2.EVENT_LBUTTONDOWN: # 왼쪽 버튼은 전경
        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: # 오른쪽 버튼은 배경
        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: # 마우스 움직임
        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)

cv2.setMouseCallback('dst', on_mouse)

while True:
    key = cv2.waitKey()
    if key == 13:
        cv2.grabcut(src, mask, rc, bgdModel, fgdModel, 1, cv2.GC_INIT_WITH_MASK) # 마스크 초기화
        mask2 = np.where((mask == 2) | (mask == 0), 0, 1).astype('uint8')
        dst = src * mask2[:, :, np.newaxis]
        cv2.imshow('dst', dst)

    elif key == 27:
        break

cv2.destroyAllWindows()
        

 

 이처럼 객체 검출을 잘 못하였을 때 직접 배경과 전경을 마우스로 지정하고 업데이트 할 수 있습니다.

 

 

 


황선규 박사님의 'OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝' 을 공부하면서 정리해 보았습니다.

OpenCV 명령어 튜토리얼 홈페이지 : docs.opencv.org/4.3.0/d8/d83/tutorial_py_grabcut.html

 

OpenCV: Interactive Foreground Extraction using GrabCut Algorithm

Goal In this chapter We will see GrabCut algorithm to extract foreground in images We will create an interactive application for this. Theory GrabCut algorithm was designed by Carsten Rother, Vladimir Kolmogorov & Andrew Blake from Microsoft Research Cambr

docs.opencv.org

예제 코드 출처 :  황선규 박사님 github홈페이지

 

『OpenCV 4로 배우는 컴퓨터 비전과 머신 러닝』

예제 소스 코드는 아래 링크를 참고하세요

sunkyoo.github.io

반응형