IT

OpenCV-Python으로 영상 배경 제거(Background Subtraction)하기

유병혁 2018. 12. 26. 13:56

안녕하세요? 이번 글은 OpenCV-Python으로 영상 배경 제거(Background Subtraction)하는 방법을 학습해 보도록 하겠습니다.


일단 파이썬에서 OpenCV를 이용하기 위해 관련 패키지를 설치해 보겠습니다.

opencv-contrib-python은 비공식적으로 사전 빌드된 파이썬용 OpenCV 패키지입니다.

opencv-contrib-python | https://pypi.org/project/opencv-contrib-python/

1
2
3
4
5
6
7
# opencv-contrib-python 설치
import sys, subprocess
subprocess.call([sys.executable, '-m''pip''install''--upgrade''opencv-contrib-python'])
 
 
import cv2 as cv
print(cv.__version__)
cs


아래 자료는 소백산국립공원 생태통로에 설치된 카메라 트랩(camera trap)이 포착한 고라니(waterdeer) 영상입니다.

보시는 것처럼 정적인 배경(static background)이 있고, 그 위에 움직이는 전경(moving foreground)이 존재하고 있습니다.


위 고라니 영상을 재생해 보겠습니다. 먼저 VideoCapture 객체를 생성하고 동영상 파일을 지정해 줍니다.

그리고 반복문을 통해 각각의 프레임을 읽어들이고 화면에 보여줍니다. 이 때 cv.waitKey() 함수는 키보드

입력을 대기하는 함수로 여기서는 30 밀리초(1초 = 1000밀리초, millisecond, ms) 값을 지정하였습니다.

'q' 키를 누르거나 동영상의 끝까지 도달하면 반복문은 중지됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cap = cv.VideoCapture('waterdeer.avi')
while(cap.isOpened()):
    # ret: frame capture 결과(boolean)
    # frame: capture한 frame
    ret, frame = cap.read()
    if (ret):
        cv.imshow('frame', frame)
        k = cv.waitKey(30& 0xFF
        if k == ord('q'):
            break
    else:
        break
 
cap.release()
cv.destroyAllWindows()
cs


만약 고라니가 없는, 동적 전경이 없는 상태의 배경 이미지를 가지고 있다면 배경 제거는 어떻게 구할 수 있을까요?!

아마도 이상적인 환경에서는 배경 이미지에서 새 이미지를 차감하면 전경 객체만을 손쉽게 탐지할 수 있을 것입니다.


이제 OpenCV에서 제공하는 배경 제거 알고리즘을 직접 적용해보도록 하겠습니다.

Background Subtraction | https://docs.opencv.org/3.4.4/db/d5c/tutorial_py_bg_subtraction.html


OpenCV는 아래와 같이 다양한 배경 제거 알고리즘을 제공하고 있습니다:


이미지 출처: https://docs.opencv.org/3.4/d7/df6/classcv_1_1BackgroundSubtractor.html


여기서는 'BackgroundSubtractorMOG2' 알고리즘을 시험해 보고자 합니다. MOG2는 가우시안 혼합 기반 배경/전경 분할

알고리즘 (Gaussian Mixture-based Background/Foreground Segmentation Algorithm)으로 아래 2편의 논문에 기반하여 구현되었습니다.


Zivkovic, Z. 2004. Improved adaptive Gausian mixture model for background subtraction.

International Conference Pattern Recognition, 2, 28-31.

논문 바로가기: http://www.zoranz.net/Publications/zivkovic2004ICPR.pdf


Zivkovic, Z., van der Heijden, F. 2006. Efficient adaptive density estimation per image pixel for the task of background subtraction.

Pattern Recognition Letters, 27(7), 773-780.

논문 바로가기: http://www.zoranz.net/Publications/zivkovicPRL2006.pdf


아래와 같이 배경 제거 알고리즘 MOG2를 적용하는 구문을 삽입하였습니다. 결과를 볼까요?! 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cap = cv.VideoCapture('waterdeer.avi')
 
# BackgroundSubtractorMOG2
fgbg = cv.createBackgroundSubtractorMOG2()
 
while(cap.isOpened()):
    # ret: frame capture 결과(boolean)
    # frame: capture한 frame
    ret, frame = cap.read()
    if (ret):
        fgmask = fgbg.apply(frame)
        cv.imshow('frame', fgmask)
        k = cv.waitKey(30& 0xFF
        if k == ord('q'):
            break
    else:
        break
 
cap.release()
cv.destroyAllWindows()
cs


자, 이제 동적인 전경과 정적인 배경이 흑백으로 구분(회색은 그림자 탐지)된 것을 보실 수 있습니다.


배경 제거 알고리즘이 적용된 영상을 저장하기 위해 VideoWriter 객체를 생성하였습니다. fourcc는 코덱 정보,
frame_rate는 초당 저장될 프레임 수, frame_width와 frame_height는 저장될 크기가 되겠습니다. 끝에 0값은
컬러 프레임 여부를 나타내는데요, 이 값이 0이 아니면 컬러 프레임을 예상하고 인코딩을 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
cap = cv.VideoCapture('waterdeer.avi')
frame_width, frame_height, frame_rate = int(cap.get(3)), int(cap.get(4)), int(cap.get(5))
 
fourcc = cv.VideoWriter_fourcc(*'DIVX')
out = cv.VideoWriter('waterdeer-mog2.avi', fourcc, frame_rate, (frame_width, frame_height), 0)
 
# BackgroundSubtractorMOG2
fgbg = cv.createBackgroundSubtractorMOG2()
 
while(cap.isOpened()):
    # ret: frame capture 결과(boolean)
    # frame: capture한 frame
    ret, frame = cap.read()
    if (ret):
        fgmask = fgbg.apply(frame)
        cv.imshow('mog2', fgmask)
        out.write(fgmask)
        k = cv.waitKey(30& 0xFF
        if k == ord('q'):
            break
    else:
        break
 
cap.release()
out.release()
cv.destroyAllWindows()
cs


한편, 배경 제거 영상에 원치 않는 잡음들이 보이는데요, 이러한 노이즈를 제거하는데는

'형태 변환(Morphological Transformations)' 알고리즘을 유용하게 사용하실 수 있습니다.


형태 변환은 일반적으로 이진 이미지에 수행되며, 원본 이미지와 커널(kernel)을 입력값으로 사용합니다.

이 중 Opening은 잡음 제거에 유용한 방법으로 침식 후 팽창(erosion followed by dilation)을 통해 본래

이미지 크기로 돌려놓은 것입니다.

형태 변환(Morphological Transformations) | https://docs.opencv.org/3.4.4/d9/d61/tutorial_py_morphological_ops.html


좌측부터 원본 이미지, 침식(erosion), 팽창(dilation) 순입니다. 간단한 구현이지만 잡음 제거에 확실한 도움이 되겠죠?!

  

그림출처: https://docs.opencv.org/3.4.4/j.png

그림출처: https://docs.opencv.org/3.4.4/erosion.png

그림출처: https://docs.opencv.org/3.4.4/dilation.png


아래는 Opening 기법의 적용 전후 비교 이미지입니다.

그림 출처: https://docs.opencv.org/3.4.4/opening.png


자, 이제 코드를 한 번 더 업데이트하는데요, 커널을 생성하고 형태 변환 중 Opening 기법을 적용하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
cap = cv.VideoCapture('waterdeer.avi')
frame_width, frame_height, frame_rate = int(cap.get(3)), int(cap.get(4)), int(cap.get(5))
 
fourcc = cv.VideoWriter_fourcc(*'DIVX')
out = cv.VideoWriter('waterdeer-mog2.avi', fourcc, frame_rate, (frame_width, frame_height), 0)
 
# BackgroundSubtractorMOG2
fgbg = cv.createBackgroundSubtractorMOG2()
kernel = cv.getStructuringElement(cv.MORPH_ELLIPSE,(3,3))
 
while(cap.isOpened()):
    # ret: frame capture 결과(boolean)
    # frame: capture한 frame
    ret, frame = cap.read()
    if (ret):
        fgmask = fgbg.apply(frame)
        fgmask = cv.morphologyEx(fgmask, cv.MORPH_OPEN, kernel)
        cv.imshow('mog2', fgmask)
        out.write(fgmask)
        k = cv.waitKey(30& 0xFF
        if k == ord('q'):
            break
    else:
        break
 
cap.release()
out.release()
cv.destroyAllWindows()
cs


결과는 아래와 같습니다. 더 깔끔해졌죠?! 다음 글에서는 동적 전경 영역만을 프레임으로 추출해 보도록 하겠습니다.