본문 바로가기

DEVELOP_NOTE/ML

[Real-time Instance Segmentation] 화상대화 배경제거 (1)

현재 회사에서 제공하고 있는 메신저의 화상대화 서비스에서 'ZOOM'에서 제공하는 배경 제거(대체)기능 구현에 대한 업무를 맡게 되었다.

사실, Instance Segmentation과 real-time 수준의 빠른 모델적용을 요구하는 부분에 있어 지금까지 경험이 부족했기 때문에,

어려울 수 있지만, 이번 Task가 큰 경험이 될 것으로 생각하고, 기쁜마음으로 시작하게 되었다.

 

일단 배경 제거 기능의 경우 1차적으로 화상대화에 참여하고있는 화자와 그 외의 영역을 분리할 수 있어야하고, 화상대화 내부에서 빠르게 동작해야하기 때문에

1) Instance Segmentation 이 선행되어야하고,
2) Real-time으로 제공되어야한다.

 

우선, 해당 과제에 대해 정보를 수집하기 시작했고,

우선 Instance Segmentation model을 알아보던 중, OpenCV의 cv2.bgsegm.createBackgroundSubtractorMOG이라는

배경추출 라이브러리를 알게되었고, 테스트 필요항목에 포함시켰다.

 

두번째로는 Rembg라는 라이브러리인데, 이미지내에서 일명 '누끼따기'로 유명한 라이브러리이다.

real time frame각각에 대해 배경분할을 진행한 후 각각의 frame을 이어붙이는 방식은 어떨까 생각해봤다.

 

그래서 위 라이브러리와 모델을 실행하는 간단한 파일을 만들어 로컬에서 성능을 시험해보았다.

 

import argparse
import cv2
import numpy as np
from rembg import remove
from PIL import Image

cam = cv2.VideoCapture(0)
width = cam.get(cv2.CAP_PROP_FRAME_WIDTH)
height = cam.get(cv2.CAP_PROP_FRAME_HEIGHT)
count = cam.get(cv2.CAP_PROP_FRAME_COUNT)
fps = cam.get(cv2.CAP_PROP_FPS)

print('가로: ', str(width))
print('세로: ', str(height))
print('총 프레임 수: ', str(count))
print('FPS: ' + str(fps))

cam.set(3,1280) #CV_CAP_PROP_FRAME_WIDTH
cam.set(4,720) #CV_CAP_PROP_FRAME_HEIGHT
#cam.set(5,0) #CV_CAP_PROP_FPS

parser = argparse.ArgumentParser(description="0.opencv사용 / 1.rembg사용")
parser.add_argument('--method', '-m', required=True, help="사용할 라이브러리를 알려주세요. 예)0 or 1")
args = parser.parse_args()

if int(args.method) == 0:
    """
    opencv의 createBackgroundSubtractorMOG 라이브러리 활용하는 방법
    """
    fgbg = cv2.bgsegm.createBackgroundSubtractorMOG(backgroundRatio=0.7, noiseSigma=0)
    """
    history=200: 히스토리 길이
    nmixtures=5: 가우시안 믹스처의 개수
    backgroundRatio=0.7: 배경 비율
    noiseSigma=0: 노이즈 강도 (0=자동)
    """

    # fgbg = cv2.createBackgroundSubtractorMOG2(history=500, varThreshold=16, detectShadows=False)
    """
    history=500 #히스토리 개수
    varThreshold=16 #분산 임계 값
    detectShadows=True #그림자 표시
    """

    while True:
        ret_val, img = cam.read() # 캠 이미지 불러오기
        fgmask = fgbg.apply(img)
        condition = fgmask != 0 #배경으로 구해진 영역인 '0'인 fixel을 제외하고 모두 1로 치환해준다.
        fgmask[condition] = 1
        for tensor in range(img.shape[2]): # RGB하나씩 꺼내서 배경으로 계산된(fgmask) 영역만 모두 0으로 치환한다.
            img[:,:,tensor] = img[:,:,tensor] * fgmask

        cv2.imshow("Cam Viewer",img) # 불러온 이미지 출력하기
    #     cv2.imshow('bgsub', fgmask)
    #     cv2.imshow('answer', test)
        if cv2.waitKey(1) == 27:
            break  # esc to quit

elif int(args.method) == 1:
    """
    rembg 라이브러리를 활용해서 프레임당 변환으로 구현하는 방식
    """

    while True:
        ret_val, img = cam.read() # 캠 이미지 불러오기
        output = remove(img)

        cv2.imshow("Cam Viewer",output) # 불러온 이미지 출력하기
    #     cv2.imshow('bgsub', fgmask)
    #     cv2.imshow('answer', test)
        if cv2.waitKey(1) == 27:
            break  # esc to quit

            
# ddd.resize(int(ddd.shape[0]/2),int(ddd.shape[1]/2), ddd.shape[2])

1) 첫번째, cv2.bgsegm.createBackgroundSubtractorMOG 라이브러리 적용 결과

cv2.bgsegm.createBackgroundSubtractorMOG라이브러리는 영상내에서 픽셀당 색상의 변화가 있을 경우를 인식객체, 없는 경우 배경으로 간주하여, 움직이는 object를 추출하는 라이브러리이다.

속도는 Real time에 적합했지만, 이 때문에, 아무래도 계속해서 움직이지않는 한 정상적으로 객체를 추출할 수 없다는 문제가 있었다.

 

 

2) 두번째, Rembg 적용 결과

결과, 영역내에서 객체와 배경을 매우 깔끔하게 분리해내고있는것을 확인할 수 있다.

하지만, 해당 모델 자체가 매우 무거운것으로 보여진다.

real-time으로 제공하기위해서는 FPS 30으로 재생할때, 끊김 없이 동작해야하지만, 매우 심하게 끊어지는것을 확인할 수 있었다.

 

Rembg의 연산량을 줄여보기 위해, 이미지의 해상도를 낮추고 Input하도록 처리해보거나 여러가지 시도를 해보았지만,

속도에는 큰 변화가 없고, segmentation정확도만 낮아지는 현상을 보였다.

결국, 성능을 유지하면서 속도문제를 극복하기는 힘들어 보였다.

 

아무래도 위 방식으로 해결방안을 마련하기는 힘들것으로 보이고, 다음번에는 real-time에 적합한 가벼운 Instance segmentation model을 찾아봐야할 것 같다.