[컴퓨터 비전] 13 Video segmentation

이찬영·2024년 2월 27일
0

컴퓨터 비전

목록 보기
12/20

[컴퓨터비전 STUDY / KOCW 한동대학교 황성수 교수님 강의 Review]

Introduction

Image/video segmentation은 디지털 이미지를 여러 개의 영역으로 나누는 과정을 말한다.

예를 들면, 기상 캐스터에 사용되는 Chroma-keying 기법, 움직이는 물체들을 따로 검출해 낼 수 있는 감시 카메라 등에 사용된다.


Background subtraction

Background substraction이란, 현재 영상을 background로부터 빼내는 것을 의미한다.

비디오로부터, 관심 대상을 확인할 수 있다.

먼저, t 시간대의 영상 f (x, y, z)와 배경 영상 B (x, y, z)가 있다고 가정한다.

우리는 두개의 이미지로부터 변화를 감지해낸다.

t 시간대의 영상과 배경 영상의 절댓값 차가 임계값보다 크면, 1 or 255로 처리하고, 그 외에는 다 0으로 처리한다.

Background substraction을 수행할 때 다음과 같은 가정이 필요하다.

  • 두 영상은 같은 위치에서 찍힌 것이어야 한다.
  • 두 영상은 조도가 비슷해야만 한다.

우리는 갑작스럽고 점진적인 조명의 변화를 다루어야만 한다.

예를 들면, 반복적인 움직임을 어떻게 처리할 수 있을까?

  • 흔들리는 나뭇잎들
  • 파도

아니면 오랫동안 변하지 않는 것들을 어떻게 처리할 수 있을까?

  • 움직이지 않는 가방
  • 주차되어 있는 자동차

결국 배경을 얼마나 잘 검출해 내느냐가 Background subtraction의 핵심이다!

다음은 absdiff를 사용하여 비디오 파일에서 전경(객체)을 분리해내는 background subtraction을 구현한 코드이다.

 int main() {
 	VideoCapturecapture("background.mp4");	# 비디오 파일 열기
 	Mat background, image, gray, result, foregroundMask, foregroundImg;
 
 	//set the first frame as background
 	capture >> background;		# 첫 번째 프레임을 배경 이미지로 설정
 	cvtColor(background, background, CV_BGR2GRAY);	# 그레이 스케일 이미지 변환
    
 	while (true) {
 		if (capture.grab() == 0) break;		# 더 이상 처리할 프레임이 없을 때
 		capture.retrieve(image);	# 현재 프레임을 image 변수에 저장함
 		cvtColor(image, gray, CV_BGR2GRAY);	 	# 그레이 스케일로 이미지 변환
 		
        # 배경 이미지와 현재 프레임의 차이를 계산하여 foregroundMask에 저장함
 		absdiff(background, gray, foregroundMask);
        
        # foregroundMask을 이진화하여 전경과 배경을 명확히 구분함 (threshold : 50)
 		threshold(foregroundMask, foregroundMask, 50, 255, CV_THRESH_BINARY);
 		
        # 전경 마스크를 foregroundImg에 복사함
        foregroundMask.copyTo(foregroundImg);
        # 마스크가 적용된 부분만 gray 이미지에서 foregroundImg로 복사함, 전경 객체만 남음
 		gray.copyTo(foregroundImg, foregroundMask);
 
 		imshow("foregroundImg", foregroundImg);
 		imshow("foregroundMask", foregroundMask);
 		imshow("background", background);
 		waitKey(33);
 	}
 }

결과는 다음과 같다.


Background estimation

1. Mean filter

Background (배경)은 이전 영상들(n frames)의 평균이다.

평균을 더 많이 취할수록, 아래 그림처럼 결국 도로만 남게 된다.

다음은 Generating average image를 수행하는 C++ 코드이다.

 int main() {
 	VideoCapture capture("background.mp4");		# 비디오 파일 열기
 	Mat image, sum, avg;	# Mat 타입 변수 3개 선언
 	int cnt = 2;
 
 	capture >> avg;		# 비디오에서 첫번째 프레임을 읽어 avg 변수에 저장함

 	while (true) {
 		if (!capture.read(image)) break;	# 더 이상 읽을 프레임이 없을 때
        
 		# 현재 프레임과 이전까지의 편균을 사용해 새로운 평균을 계산함
        add(image / cnt, avg*(cnt- 1) / cnt, avg);		
        
 		imshow("avg", avg);
 		cnt++;
 		waitKey(33);
 	}
}

결과는 다음과 같다.

다음은 openCV MoG2에서 Background subtraction을 수행하는 코드이다.

 int main() {
 	# MOG2 배경 차감 알고리즘을 사용하여 배경 모델을 초기화함 (가우시안 분포 사용)
 	Ptr<BackgroundSubtractor> bg_model= createBackgroundSubtractorMOG2();
 	Mat image, foregroundMask, backgroundImg, foregroundImg;
 	VideoCapturecap("background.mp4");		# 비디오 캡처 시작
 
 	while (true) {
 		cap >> image;		# 비디오에서 다음 프레임을 읽어 image 변수에 저장함
 		resize(image, image, Size(640, 480));	# 이미지의 크기를 640x480으로 조정함
	 	
        # 전경 마스크가 비어 있으면 초기화함
 		if (foregroundMask.empty())
 			foregroundMask.create(image.size(), image.type());		
 		
        # 현재 이미지에 대해 배경 차감을 수행하고, 결과를 foregroundMask에 저장함
 		bg_model->apply(image, foregroundMask);
 		
        # 마스크에 가우시안 blur를 적용하여 노이즈를 줄임
        GaussianBlur(foregroundMask, foregroundMask, Size(11, 11), 3.5, 3.5);
        
        # 마스크를 이진화하여 전경과 배경을 명확하게 구분함
 		threshold(foregroundMask, foregroundMask, 10, 255, THRESH_BINARY);
        
        # 전경 이미지 생성
        # 먼저 foregroundImg를 검은색으로 초기화하고, 
        # 전경 마스크에 해당하는 부분만 현재 이미지에서 foregroundImg로 복사함
 		foregroundImg= Scalar::all(0);
 		image.copyTo(foregroundImg, foregroundMask);
        
        # 배경 모델로부터 배경 이미지 추출함
 		bg_model->getBackgroundImage(backgroundImg);	
  
 		imshow("foreground mask", foregroundMask);
 		imshow("foreground image", foregroundImg);
 
 		if (!backgroundImg.empty()) {
 			imshow("mean background image", backgroundImg);
 		}
 		waitKey(33);
	}
}

결과는 다음과 같다.


2. Median filter

또한 Median filter 기법을 사용할 수도 있다.
이것은 영상 frames 중 중간값을 선택해 사용하는 방식이다.

잠깐 조건부 확률에 대해서 짚고 넘어가자.

조건부 확률의 정의와 그로부터 파생된 Bayes rule은 다음과 같다.

조건부 확률이란, 어떤 사건 A가 일어났을 때, A와 B가 동시에 일어날 확률을 구하는 것이다.


그렇다면 A를 pixel value, B를 background 라고 할 때, 예를 들어, p(A|B)는 배경 영상이 있을 때 pixel이 특정한 값 A를 가질 확률을 의미하는 것으로 이해할 수 있다. p(B|A)는 현재 영상에서 특정한 pixel이 A일 때 배경일 확률을 의미한다.

또한, Gaussian mixture model(이후 GMM)을 이용해 특정한 값을 가질 확률을 구해 보는 방법도 있다.

GMM을 이용해서 Background estimation을 하는 과정은 다음과 같다.

  1. 몇 개의 GMM을 쓸 것인지 정한다.
  2. Training 단계에서, trainig data와 결합된 Gaussian model의 평균과 분산을 구한다. (p(A|B)를 평가함)
  3. 각각의 픽셀에 p(B|A)를 계산해 background와 foreground 중 어디에 속하는지 분류해낸다. (예를 들어 p(B|A) > 0.5 이면, 배경이고 그렇지 않으면, 배경이 아닌 것으로 판단하는 식이다.)

다음은 객체의 개수를 세는 코드이다.

 int main() {
 	Mat gray = imread("contours.jpg", 0);	# 이미지 파일을 그레이 스케일로 읽음
 	Mat result;
    # gray 이미지를 이진화하여 result에 저장함 (threshold : 180)
 	threshold(gray, result, 180, 255, THRESH_BINARY_INV);
 	
    vector<vector<Point>> contours;
 	vector<Vec4i>hierarchy;
    
    # result 이미지에서 윤곽을 찾아 contours 벡터에 저장함
    # CV_RETR_EXTERNAL 플래그는 이미지의 가장 바깥쪽 윤곽을 찾음
    # CV_CHAIN_APPROX_SIMPLE 알고리즘은 윤곽 점들을 압축하여 메모리 사용을 최적화함
 	findContours(result, contours, hierarchy, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_SIMPLE);
 	
    # 윤곽 수를 표시해 줌, FONT_HERSHEY_SIMPLEX는 폰트 스타일
 	putText(result, format("contour count: %d", contours.size()), Point(50, 80), 
	FONT_HERSHEY_SIMPLEX, 1, Scalar(128), 4);
 
 	imshow("contours", result);
 	waitKey(0);
 }

결과는 다음과 같다.

profile
자율주행, AI, 클라우드

0개의 댓글