본문 바로가기

OpenCV

OpenCV 동영상 배경 단순 추정 C++, Python

반응형

 

많은 컴퓨터 비전 응용 분야에서는 처리 능력이 부족합니다. 이러한 경우에는 간단하면서도 효과적인 기술을 사용해야 합니다.

 

이 글에서는 카메라가 고정되어 있고 장면에 움직이는 물체가 있을 때 장면의 배경을 추정하는 한 가지 기법을 살펴보겠습니다. 이러한 상황은 드물지 않습니다. 예를 들어, 많은 교통 및 감시 카메라는 고정되어 있습니다.

 

 

 

이 게시물은 OpenCV 4.4에서 테스트되었습니다.

 

시간적 중앙값 필터링

 

이 글에서 설명하려는 아이디어를 이해하기 위해, 1차원의 더 간단한 문제를 고려해 보겠습니다.

 

10밀리초마다 양(예를 들어, 방의 온도)을 추정한다고 가정해 보겠습니다.

 

예를 들어, 방의 온도가 화씨 70도라고 합시다.

 

 

 

중앙값은 견고한 추정치입니다

 

위 그림에서는 두 개의 온도계(좋은 온도계와 나쁜 온도계)로 측정한 값을 보여줍니다.

 

왼쪽에 보이는 좋은 온도계는 70도를 나타내지만, 약간의 가우시안 잡음이 있습니다. 온도를 더 정확하게 추정하려면 몇 초 동안 값을 평균하면 됩니다. 잡음은 양수와 음수 값을 갖는 가우시안 잡음이므로 평균을 구하면 잡음이 제거됩니다. 실제로 이 경우의 평균값은 70.01입니다.

 

반면에 나쁜 온도계는 대부분의 경우 좋은 온도계처럼 작동하지만 가끔 숫자가 완전히 틀릴 때가 있습니다.

 

실제로, 불량 온도계에서 나온 수치의 평균을 구하면 71.07도가 나옵니다. 이는 분명히 과장된 추정입니다.

 

우리는 여전히 온도를 정확하게 추정할 수 있을까?

 

정답은 '예'입니다. 데이터에 이상치가 포함된 경우, 중앙값은 우리가 추정하려는 값에 대한 더 안정적인 추정치입니다.

 

중앙값은 데이터를 오름차순이나 내림차순으로 정렬했을 때 중간에 있는 값입니다.

 

위에 표시된 곡선의 중앙값은 70.05도로 71.07도보다 훨씬 더 정확한 추정치입니다.

 

유일한 단점은 중앙값을 계산하는 데 평균에 비해 비용이 더 많이 든다는 것입니다.

 

배경 추정을 위한 중앙값 사용

 

이제 카메라가 정적일 때 배경을 추정하는 문제로 돌아가 보겠습니다.

 

대부분의 경우, 카메라가 움직이지 않기 때문에 모든 픽셀은 배경의 같은 부분을 본다고 가정할 수 있습니다. 가끔은 자동차나 다른 움직이는 물체가 앞에 나타나 배경을 가리기도 합니다.

 

비디오 시퀀스의 경우 무작위로 몇몇 프레임(예: 25개 프레임)을 샘플링할 수 있습니다.

 

다시 말해, 각 픽셀에 대해 이제 배경을 25번 추정할 수 있습니다. 픽셀이 자동차나 다른 움직이는 물체에 50% 이상 가려지지 않는 한, 이 25개 프레임에 대한 픽셀의 중앙값은 해당 픽셀의 배경을 정확하게 추정할 수 있습니다.

 

이를 모든 픽셀에 대해 반복하면 전체 배경을 복구할 수 있습니다.

 

배경 추정 코드(C++/Python)

 

이제 실제 코드를 살펴보겠습니다.

 

파이썬 코드

 

import numpy as np
import cv2
from skimage import data, filters
 
# Open Video
cap = cv2.VideoCapture('video.mp4')
 
# Randomly select 25 frames
frameIds = cap.get(cv2.CAP_PROP_FRAME_COUNT) * np.random.uniform(size=25)
 
# Store selected frames in an array
frames = []
for fid in frameIds:
    cap.set(cv2.CAP_PROP_POS_FRAMES, fid)
    ret, frame = cap.read()
    frames.append(frame)
 
# Calculate the median along the time axis
medianFrame = np.median(frames, axis=0).astype(dtype=np.uint8)    
 
# Display median frame
cv2.imshow('frame', medianFrame)
cv2.waitKey(0)

 

C++ 코드

 

#include <opencv2/opencv.hpp>
#include <iostream>
#include <random>
 
using namespace std;
using namespace cv;

 

 

우리는 중앙 프레임을 계산하기 위해 몇 가지 함수를 생성할 것입니다.

 

int computeMedian(vector<int> elements) 
{
  nth_element(elements.begin(), elements.begin()+elements.size()/2, elements.end());
 
  //sort(elements.begin(),elements.end());
  return elements[elements.size()/2];
}
 
cv::Mat compute_median(std::vector<cv::Mat> vec) 
{
  // Note: Expects the image to be CV_8UC3
  cv::Mat medianImg(vec[0].rows, vec[0].cols, CV_8UC3, cv::Scalar(0, 0, 0));
 
  for(int row=0; row<vec[0].rows; row++) 
  {
    for(int col=0; col<vec[0].cols; col++) 
    {
      std::vector<int> elements_B;
      std::vector<int> elements_G;
      std::vector<int> elements_R;
 
      for(int imgNumber=0; imgNumber<vec.size(); imgNumber++) 
      {
        int B = vec[imgNumber].at<cv::Vec3b>(row, col)[0];
        int G = vec[imgNumber].at<cv::Vec3b>(row, col)[1];
        int R = vec[imgNumber].at<cv::Vec3b>(row, col)[2];
 
        elements_B.push_back(B);
        elements_G.push_back(G);
        elements_R.push_back(R);
      }
 
      medianImg.at<cv::Vec3b>(row, col)[0]= computeMedian(elements_B);
      medianImg.at<cv::Vec3b>(row, col)[1]= computeMedian(elements_G);
      medianImg.at<cv::Vec3b>(row, col)[2]= computeMedian(elements_R);
    }
  }
  return medianImg;
}

 

 

int main(int argc, char const *argv[])
{
  std::string video_file;
  // Read video file
  if(argc > 1)
  {
    video_file = argv[1];
  } else
  {
    video_file = "video.mp4";
  }
 
  VideoCapture cap(video_file);
  if(!cap.isOpened())
    cerr << "Error opening video file\n";
 
  // Randomly select 25 frames
  default_random_engine generator;
  uniform_int_distribution<int>distribution(0, 
  cap.get(CAP_PROP_FRAME_COUNT));
 
  vector<Mat> frames;
  Mat frame;
 
  for(int i=0; i<25; i++) 
  {
    int fid = distribution(generator);
    cap.set(CAP_PROP_POS_FRAMES, fid);
    Mat frame;
    cap >> frame;
    if(frame.empty())
      continue;
    frames.push_back(frame);
  }
  // Calculate the median along the time axis
  Mat medianFrame = compute_median(frames);
 
  // Display median frame
  imshow("frame", medianFrame);
  waitKey(0);
}

 

 

보시다시피, 무작위로 25개의 프레임을 선택하여 25개 프레임에 걸쳐 각 픽셀의 중앙값을 계산합니다. 이 중앙값 프레임은 모든 픽셀이 배경을 최소 50% 이상 인식하는 한 배경을 정확하게 추정하는 데 유용합니다.

 

결과는 아래와 같습니다.

 

 

시간적 중앙값 필터링을 사용하여 중앙값 프레임 계산.

배경의 추정치는 25개 프레임에 걸쳐 각 픽셀의 중앙값을 찾아 계산한 중앙 프레임 입니다.

 

프레임 차이 분석(C++/Python)

 

당연히 다음 질문은 이미지의 움직이는 부분을 보여주는 마스크를 각 프레임에 만들 수 있는지 여부입니다.

 

이는 다음 단계에서 완료됩니다.

 

  1. 중앙값 프레임을 회색조로 변환합니다.
  2. 비디오의 모든 프레임을 반복합니다. 현재 프레임을 추출하여 회색조로 변환합니다.
  3. 현재 프레임과 중간 프레임의 절대 차이를 계산합니다.
  4. 위 이미지의 임계값을 설정하여 노이즈를 제거하고 출력을 이진화합니다.

 

코드를 살펴보죠.

 

파이썬 코드

 

# Reset frame number to 0
cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
 
# Convert background to grayscale
grayMedianFrame = cv2.cvtColor(medianFrame, cv2.COLOR_BGR2GRAY)
 
# Loop over all frames
ret = True
while(ret):
 
  # Read frame
  ret, frame = cap.read()
  # Convert current frame to grayscale
  frame = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
  # Calculate absolute difference of current frame and 
  # the median frame
  dframe = cv2.absdiff(frame, grayMedianFrame)
  # Treshold to binarize
  th, dframe = cv2.threshold(dframe, 30, 255, cv2.THRESH_BINARY)
  # Display image
  cv2.imshow('frame', dframe)
  cv2.waitKey(20)
 
# Release video object
cap.release()
 
# Destroy all windows
cv2.destroyAllWindows()

 

C++ 코드

 

//  Reset frame number to 0
cap.set(CAP_PROP_POS_FRAMES, 0);
 
// Convert background to grayscale
Mat grayMedianFrame;
cvtColor(medianFrame, grayMedianFrame, COLOR_BGR2GRAY);
 
// Loop over all frames
while(1) 
{
  // Read frame
  cap >> frame;
 
  if (frame.empty())
    break;
 
  // Convert current frame to grayscale
  cvtColor(frame, frame, COLOR_BGR2GRAY);
 
  // Calculate absolute difference of current frame and the median frame
  Mat dframe;
  absdiff(frame, grayMedianFrame, dframe);
 
  // Threshold to binarize
  threshold(dframe, dframe, 30, 255, THRESH_BINARY);
 
  // Display Image
  imshow("frame", dframe);
  waitKey(20);
}
 
  cap.release();
  return 0;
}

 

결과

 

아래 영상은 배경 추정과 프레임 차분의 출력을 보여줍니다.

 

 

 

 

 

 

반응형

더욱 좋은 정보를 제공하겠습니다.~ ^^