OpenCV 를 사용한 비디오 객체 추적

C++Beginner
지금 연습하기

소개

이 랩에서는 OpenCV 를 사용하여 비디오 객체 추적을 구현할 것입니다. 이 프로젝트를 배우기 전에 "C++ 로 태양계 만들기" 강좌를 완료해야 합니다.

학습 내용

  • C++ 기본
  • g++ 기본
  • 이미지 표현 (Image representation)
  • OpenCV 응용
  • Meanshift & Camshift 알고리즘

최종 결과

이 실험은 태양계의 행성을 추적할 수 있는 프로그램을 구현합니다. (다음 이미지에서, 우리는 노란색 궤도에서 목성을 선택했으며, 추적된 객체가 빨간색 타원으로 표시된 것을 볼 수 있습니다):

image desc

이 프로젝트를 작성하기 전에, "C++ 로 태양계 만들기" 강좌를 완료해야 합니다.

비디오 파일 생성

LabEx 환경에서는 카메라 환경을 지원하지 않습니다. 따라서, 프로젝트를 위해 비디오 파일을 생성해야 합니다.

비디오 녹화 도구를 설치해 봅시다:

sudo apt-get update && sudo apt-get install gtk-recordmydesktop

설치 후, 응용 프로그램 메뉴에서 녹화 소프트웨어를 찾을 수 있습니다:

image desc

그런 다음, 태양계 프로그램 ./solarsystem을 실행하고 RecordMyDesktop을 사용하여 데스크톱 화면을 녹화 (10~30 초면 충분합니다) 하고, ~/Code/camshiftvideo라는 이름으로 저장합니다:

image desc

녹화를 완료하려면, 오른쪽 하단의 Stop 버튼을 클릭하면 됩니다. 그러면 video.ogv 파일이 생성됩니다:

image desc

디지털 이미지 기본

OpenCV 는 오픈 소스 크로스 플랫폼 컴퓨터 비전 라이브러리입니다. OpenGL 의 이미지 렌더링과 달리, OpenCV 는 이미지 처리 및 컴퓨터 비전을 위한 많은 일반적인 알고리즘을 구현합니다. OpenCV 를 배우기 전에, 컴퓨터에서 이미지와 비디오의 몇 가지 기본 개념을 이해해야 합니다.

우선, 컴퓨터에서 사진 또는 이미지가 어떻게 표현되는지 이해해야 합니다. 사진을 저장하는 두 가지 일반적인 방법이 있습니다: 하나는 벡터 맵이고 다른 하나는 픽셀 맵입니다.

벡터 맵에서 이미지는 선으로 연결된 일련의 점으로 수학적으로 정의됩니다. 벡터 맵 파일의 그래픽 요소는 객체라고 합니다. 각 객체는 자체 포함된 엔티티이며, 색상, 모양, 윤곽선, 크기 및 화면 위치와 같은 속성을 갖습니다.

더 일반적인 것은 픽셀 맵입니다. 예를 들어, 이미지의 크기는 종종 1024*768 입니다. 이것은 사진이 가로 방향으로 1024 픽셀, 세로 방향으로 768 픽셀을 가지고 있음을 의미합니다.

픽셀은 픽셀 맵의 기본 단위입니다. 일반적으로 픽셀은 세 가지 기본 색상 (빨강, 녹색 및 파랑) 의 혼합입니다. 컴퓨터의 본질이 숫자의 인식이기 때문에, 일반적으로 기본 색상을 0 에서 255 까지의 밝기로 표현합니다. 즉, 기본 빨간색의 경우, 0가장 어두운 색, 즉 검정색을 의미하고, 255가장 밝은 색, 즉 순수한 빨간색을 의미합니다.

따라서 픽셀은 삼중항 (R,G,B)로 표현될 수 있으며, 흰색(255,255,255)로, 검정색(0,0,0)으로 표현될 수 있습니다. 그러면 이 이미지를 RGB 색상 공간의 이미지라고 부릅니다. R, GB는 이미지의 세 가지 채널이 됩니다. RGB 색상 공간 외에도 HSV, YCrCb 등과 같은 다른 많은 색상 공간이 있습니다.

픽셀이 픽셀 맵의 기본 단위인 것처럼, 이미지는 비디오의 기본 단위입니다. 비디오는 일련의 이미지로 구성되며, 각 이미지를 프레임이라고 부릅니다. 그리고 우리가 일반적으로 비디오 프레임 속도라고 부르는 것은 이 비디오가 초당 많은 프레임 이미지를 포함하고 있음을 의미합니다. 예를 들어, 프레임 속도가 25 이면, 이 비디오는 초당 25 프레임을 재생합니다.

1 초에 1000 밀리초가 있고 프레임 속도를 rate라고 하면, 프레임 이미지 간의 시간 간격은 1000/rate입니다.

이미지 색상 히스토그램

색상 히스토그램은 이미지를 설명하기 위한 도구입니다. 일반적인 히스토그램과 유사하지만, 색상 히스토그램은 특정 이미지에서 계산해야 합니다.

사진이 RGB 색상 공간에 있다면, R 채널에서 모든 색상의 발생 횟수를 셀 수 있습니다. 따라서 256 개의 길이 (색상 확률 조회 테이블) 의 배열을 얻을 수 있습니다. 이미지의 총 픽셀 수 (너비 x 높이) 로 모든 값을 동시에 나누고, 결과 시퀀스를 히스토그램으로 변환합니다. 결과는 R 채널의 색상 히스토그램입니다. 유사한 방식으로, G 채널B 채널의 히스토그램을 얻을 수 있습니다.

히스토그램 역투영

RGB 색상 공간에서 히스토그램은 조명 변화에 민감하다는 것이 증명되었습니다. 이러한 변화가 추적 효과에 미치는 영향을 줄이기 위해, 히스토그램을 역투영해야 합니다. 이 과정은 세 단계로 나뉩니다:

  1. 먼저, 이미지를 RGB 공간에서 HSV 공간으로 변환합니다.
  2. 그런 다음, H 채널의 히스토그램을 계산합니다.
  3. 이미지의 각 픽셀 값을 색상 확률 조회 테이블에서 해당 확률로 대체하여 색상 확률 분포 맵을 얻습니다.

이 과정을 역투영이라고 하며, 색상 확률 분포 맵은 그레이스케일 이미지입니다.

OpenCV 기본 사항

먼저 OpenCV 를 설치해야 합니다:

sudo apt update
sudo apt-get install libopencv-dev

C++ 의 기본 문법을 이미 알고 있다고 가정합니다. 거의 모든 프로그램이 #include <iostream> 헤더 파일과 using namespace std; 또는 std::cout을 사용한다는 것을 알고 있습니다. OpenCV 에도 자체 네임스페이스가 있습니다.

OpenCV 를 사용하려면 다음 헤더 파일을 포함하기만 하면 됩니다:

#include <opencv2/opencv.hpp>

그런 다음:

using namespace cv;

OpenCV 네임스페이스를 활성화합니다 (또는 모든 API 에 대해 직접 cv:: 접두사를 사용합니다).

OpenCV 를 처음 사용하는 경우 OpenCV 인터페이스에 익숙하지 않을 수 있으므로 OpenCV API 를 배우기 위해 cv:: 접두사를 사용하는 것이 좋습니다.

녹화된 비디오를 읽는 첫 번째 프로그램을 작성해 보겠습니다:

//
// main.cpp
//
#include <opencv2/opencv.hpp> // OpenCV 헤더 파일

int main() {

    // 비디오 캡처 객체 생성
    // OpenCV 는 VideoCapture 객체를 제공하며
    // 파일에서 비디오를 읽는 것을 카메라에서 읽는 것과 동일하게 처리합니다.
    // 입력 매개변수가 파일 경로이면 비디오 파일을 읽습니다.
    // 카메라의 식별 번호 (일반적으로 0) 인 경우
    // 카메라를 읽습니다.
    cv::VideoCapture video("video.ogv"); // 파일에서 읽기
    // cv::VideoCapture video(0);        // 카메라에서 읽기

    // 읽은 이미지 프레임을 위한 컨테이너, OpenCV 의 Mat 객체
    // OpenCV 의 핵심 클래스는 Mat 이며, Matrix 를 의미합니다.
    // OpenCV 는 이미지를 설명하기 위해 행렬을 사용합니다.
    cv::Mat frame;
    while(true) {

        // 비디오 데이터를 프레임에 쓰기, >>는 OpenCV 에 의해 덮어쓰기됩니다.
        video >> frame;

        // 프레임이 없으면 루프를 종료합니다.
        if(frame.empty()) break;

        // 현재 프레임 시각화
        cv::imshow("test", frame);

        // 비디오 프레임 속도는 15 이므로 부드럽게 재생하려면 1000/15를 기다려야 합니다.
        // waitKey(int delay) 는 OpenCV 의 대기 함수입니다.
        // 이 시점에서 프로그램은 키보드 입력을 위해 `delay` 밀리초 동안 대기합니다.
        int key = cv::waitKey(1000/15);

        // 키보드에서 ECS 버튼을 클릭하면 루프를 종료합니다.
        if (key == 27) break;
    }
    // 메모리 해제
    cv::destroyAllWindows();
    video.release();
    return 0;

}

main.cpp 파일을 ~/Code/camshift에서 video.ogv와 동일한 폴더에 넣고 프로그램을 컴파일합니다:

g++ main.cpp `pkg-config opencv --libs --cflags opencv` -o  main

프로그램을 실행하면 비디오가 재생되는 것을 볼 수 있습니다:

./main
image desc

참고

다음과 같은 오류가 발생할 수 있습니다:

libdc1394 error: Failed to initialize libdc1394

이것은 OpenCV 의 버그이며 실행에는 영향을 미치지 않습니다.

문제를 제거하려면 프로그램을 실행하기 전에 다음 코드를 실행할 수 있습니다:

sudo ln /dev/null /dev/raw1394

Meanshift 및 Camshift 알고리즘

  • Meanshift
  • Camshift
  • 추적 대상 선택을 위해 마우스 콜백 (mouse callback) 이벤트를 설정
  • 비디오 스트림에서 이미지를 읽기
  • Camshift 구현

Meanshift (평균 이동) 알고리즘

Meanshift 및 Camshift 알고리즘은 객체 추적을 위한 두 가지 고전적인 알고리즘입니다. Camshift 는 Meanshift 를 기반으로 합니다. 수학적 해석은 복잡하지만 기본 아이디어는 비교적 간단합니다. 따라서 이러한 수학적 사실은 건너뛰고 먼저 Meanshift 알고리즘을 소개합니다.

화면에 빨간색 점 집합이 있다고 가정하면 파란색 원 (창) 은 가장 밀도가 높은 영역 (또는 점의 수가 가장 많은 곳) 으로 이동해야 합니다.

image desc

위 이미지에서 파란색 원을 C1으로 표시하고 원의 중심을 C1_o로 표시합니다. 그러나 이 원의 무게 중심은 C1_r이며, 파란색 실선 원으로 표시됩니다.

C1_oC1_r이 겹치지 않으면 원 C1을 원 C1_r의 중심으로 반복해서 이동합니다. 결국 가장 밀도가 높은 원 C2에 머물게 됩니다.

이미지 처리를 위해 일반적으로 이미지의 역투영 히스토그램을 사용합니다. 추적 대상이 이동하면 이 이동 프로세스가 역투영 히스토그램에 의해 반영될 수 있다는 것이 분명합니다. 따라서 Meanshift 알고리즘은 결국 선택한 창을 이동 대상의 위치로 이동시킵니다. (알고리즘은 결국 수렴함을 증명했습니다.)

Camshift (캠시프트) 알고리즘

이전 설명을 통해 Meanshift 알고리즘이 항상 고정된 창 크기를 추적한다는 것을 알 수 있었습니다. 이는 비디오에서 대상 객체가 반드시 클 필요는 없기 때문에 우리의 요구 사항과 일치하지 않습니다.

그래서 Camshift 는 이 문제를 개선하기 위해 만들어졌습니다. 이는 Camshift 의 Continuously Adaptive Meanshift 에서도 확인할 수 있습니다.

기본 아이디어는 다음과 같습니다. 먼저 Meanshift 알고리즘을 적용합니다. Meanshift 결과가 수렴되면 Camshift 는 창 크기를 업데이트하고, 창에 맞게 방향성 타원을 계산한 다음, 해당 타원을 새로운 창으로 적용하여 Meanshift 알고리즘을 적용합니다.

OpenCV 는 Camshift 알고리즘에 대한 일반적인 인터페이스를 제공합니다.

RotatedRect CamShift(InputArray probImage, Rect& window, TermCriteria criteria)

첫 번째 매개변수 probImage는 대상 히스토그램의 역투영입니다. 두 번째 매개변수 window는 Camshift 알고리즘의 검색 창입니다. 세 번째 매개변수 criteria는 알고리즘 종료 (종료) 조건입니다.

분석 (Analysis) 단계

Camshift 알고리즘의 기본 아이디어를 이해한 후, 이 코드의 구현이 주로 몇 단계로 나뉜다는 것을 분석할 수 있습니다.

  1. 추적할 대상을 선택하기 위해 마우스 콜백 이벤트 (mouse callback event) 를 설정합니다.
  2. 비디오 스트림에서 이미지를 읽습니다.
  3. Camshift 프로세스를 구현합니다.

아래에서 main.cpp의 코드를 계속 수정합니다.

마우스 콜백 함수로 추적 대상 객체 선택하기 (객체 추적)

OpenCV 는 OpenGL 과 다릅니다. 마우스 콜백 함수에는 다섯 개의 매개변수가 지정됩니다. 처음 세 개가 우리에게 가장 필요한 것입니다. event의 값을 통해 마우스 왼쪽 버튼이 눌린 이벤트 (CV_EVENT_LBUTTONDOWN), 마우스 왼쪽 버튼이 떼어진 이벤트 (CV_EVENT_LBUTTONUP) 등을 얻을 수 있습니다.

bool selectObject = false; // 객체가 선택되었는지 여부를 나타내는 데 사용
int trackObject = 0;       // 1 은 추적 객체가 있음을 의미하고, 0 은 객체가 없음을 의미하며, -1 은 Camshift 속성을 계산하지 않았음을 의미합니다.
cv::Rect selection;        // 마우스로 선택한 영역을 저장합니다.
cv::Mat image;             // 비디오에서 이미지를 캐시합니다.

// OpenCV 의 마우스 콜백 함수:
// void onMouse(int event, int x, int y, int flag, void *param)
// 네 번째 매개변수 `flag` 는 추가 상태를 나타냅니다.
// param 은 사용자 매개변수를 의미하며, 필요하지 않으므로 이름을 지정하지 않습니다.
void onMouse( int event, int x, int y, int, void* ) {
    static cv::Point origin;
    if(selectObject) {
        // 선택된 높이, 너비 및 왼쪽 상단 모서리 위치 결정
        selection.x = MIN(x, origin.x);
        selection.y = MIN(y, origin.y);
        selection.width = std::abs(x - origin.x);
        selection.height = std::abs(y - origin.y);

        // &는 cv::Rect 에 의해 덮어쓰여집니다.
        // 이는 두 영역의 교차점을 의미합니다.
        // 여기의 주요 목적은 선택된 영역 외부의 영역을 처리하는 것입니다.
        selection &= cv::Rect(0, 0, image.cols, image.rows);
    }

    switch(event) {
            // 왼쪽 버튼이 눌린 경우 처리
        case CV_EVENT_LBUTTONDOWN:
            origin = cv::Point(x, y);
            selection = cv::Rect(x, y, 0, 0);
            selectObject = true;
            break;
            // 왼쪽 버튼이 떼어진 경우 처리
        case CV_EVENT_LBUTTONUP:
            selectObject = false;
            if( selection.width > 0 && selection.height > 0 )
                trackObject = -1; // 추적 객체가 Camshift 속성을 계산하지 않았습니다.
            break;
    }
}

비디오 스트리밍에서 이미지 읽어오기 (영상 스트리밍, 이미지 로드)

비디오 스트리밍을 읽는 구조를 구현했습니다. 더 자세한 내용을 작성해 보겠습니다.

int main() {
    cv::VideoCapture video("video.ogv");
    cv::namedWindow("CamShift at LabEx");

    // 1. 마우스 이벤트 콜백 등록
    cv::setMouseCallback("CamShift at LabEx", onMouse, NULL);

    cv::Mat frame;

    // 2. 비디오에서 이미지 읽기
    while(true) {
        video >> frame;
        if(frame.empty()) break;

        // 캐싱을 위해 프레임에서 전역 변수 이미지로 이미지 쓰기
        frame.copyTo(image);

        // 객체를 선택하는 경우 사각형 그리기
        if( selectObject && selection.width > 0 && selection.height > 0 ) {
            cv::Mat roi(image, selection);
            bitwise_not(roi, roi);
        }
        imshow("CamShift at LabEx", image);
        int key = cv::waitKey(1000/15.0);
        if(key == 27) break;
    }
    // 할당된 메모리 해제
    cv::destroyAllWindows();
    video.release();
    return 0;
}

참고:

ROI (관심 영역, Region of Interest): 이미지 처리에서 처리할 모든 영역은 관심 영역, 즉 ROI 가 될 수 있습니다.

OpenCV 를 이용한 Camshift 구현 (Camshift, OpenCV)

추적 대상 계산을 위한 역투영 히스토그램은 cvtColor 함수를 사용해야 하며, 이 함수는 RGB 색상 공간의 원본 이미지를 HSV 색상 공간으로 변환할 수 있습니다. 히스토그램 계산은 초기 대상을 선택한 후에 수행되어야 하므로 다음과 같습니다.

int main() {
    cv::VideoCapture video("video.ogv");
    cv::namedWindow("CamShift at LabEx");
    cv::setMouseCallback("CamShift at LabEx", onMouse, NULL);

    cv::Mat frame;
    cv::Mat hsv, hue, mask, hist, backproj;
    cv::Rect trackWindow;             // 추적 윈도우
    int hsize = 16;                   // 히스토그램용
    float hranges[] = {0,180};        // 히스토그램용
    const float* phranges = hranges;  // 히스토그램용

    while(true) {
        video >> frame;
        if(frame.empty()) break;
        frame.copyTo(image);

        // HSV 공간으로 변환
        cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);
        // 객체가 있는 경우 처리
        if(trackObject) {

            // H: 0~180, S: 30~256, V: 10~256 만 처리하고 나머지는 필터링하여 나머지를 마스크에 복사합니다.
            cv::inRange(hsv, cv::Scalar(0, 30, 10), cv::Scalar(180, 256, 10), mask);
            // hsv 에서 채널 h 분리
            int ch[] = {0, 0};
            hue.create(hsv.size(), hsv.depth());
            cv::mixChannels(&hsv, 1, &hue, 1, ch, 1);

            // 추적 객체가 계산되지 않은 경우 속성 추출
            if( trackObject < 0 ) {

                // 채널 h 및 마스크 ROI 설정
                cv::Mat roi(hue, selection), maskroi(mask, selection);
                // ROI 히스토그램 계산
                calcHist(&roi, 1, 0, maskroi, hist, 1, &hsize, &phranges);
                // 히스토그램 정규화
                normalize(hist, hist, 0, 255, CV_MINMAX);

                // 추적 객체 설정
                trackWindow = selection;

                // 추적 객체가 계산되었음을 표시
                trackObject = 1;
            }
            // 역투영 히스토그램
            calcBackProject(&hue, 1, 0, hist, backproj, &phranges);
            // 공통 영역 가져오기
            backproj &= mask;
            // Camshift 알고리즘 호출
            cv::RotatedRect trackBox = CamShift(backproj, trackWindow, cv::TermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ));
            // 그리기에는 영역이 너무 작음
            if( trackWindow.area() <= 1 ) {
                int cols = backproj.cols, rows = backproj.rows, r = (MIN(cols, rows) + 5)/6;
                trackWindow = cv::Rect(trackWindow.x - r, trackWindow.y - r,
                                       trackWindow.x + r, trackWindow.y + r) & cv::Rect(0, 0, cols, rows);
            }
            // 추적 영역 그리기
            ellipse( image, trackBox, cv::Scalar(0,0,255), 3, CV_AA );

        }


        if( selectObject && selection.width > 0 && selection.height > 0 ) {
            cv::Mat roi(image, selection);
            bitwise_not(roi, roi);
        }
        imshow("CamShift at LabEx", image);
        int key = cv::waitKey(1000/15.0);
        if(key == 27) break;
    }
    cv::destroyAllWindows();
    video.release();
    return 0;
}

요약 (요약, Summary)

다음은 이 프로젝트에서 작성한 모든 내용을 보여줍니다.

#include <opencv2/opencv.hpp>

bool selectObject = false; // 객체 선택 여부 사용
int trackObject = 0;       // 1 은 추적 객체가 있음을 의미하고, 0 은 객체가 없음을 의미하며, -1 은 Camshift 속성을 계산하지 않았음을 의미합니다.
cv::Rect selection;        // 마우스로 선택한 영역 저장
cv::Mat image;             // 비디오에서 이미지 캐시

// OpenCV 의 마우스 콜백 함수:
// void onMouse(int event, int x, int y, int flag, void *param)
// 네 번째 매개변수 `flag` 는 추가 상태를 나타냅니다.
// param 은 사용자 매개변수를 의미하며, 필요하지 않으므로 이름이 없습니다.
void onMouse( int event, int x, int y, int, void* ) {
    static cv::Point origin;
    if(selectObject) {
        // 선택된 높이와 너비 및 왼쪽 상단 모서리 위치 결정
        selection.x = MIN(x, origin.x);
        selection.y = MIN(y, origin.y);
        selection.width = std::abs(x - origin.x);
        selection.height = std::abs(y - origin.y);

        // &는 cv::Rect 에 의해 덮어쓰기됩니다.
        // 이는 두 영역의 교차점을 의미합니다.
        // 여기의 주요 목적은 선택된 영역 외부의 영역을 처리하는 것입니다.
        selection &= cv::Rect(0, 0, image.cols, image.rows);
    }

    switch(event) {
            // 왼쪽 버튼이 눌린 경우 처리
        case CV_EVENT_LBUTTONDOWN:
            origin = cv::Point(x, y);
            selection = cv::Rect(x, y, 0, 0);
            selectObject = true;
            break;
            // 왼쪽 버튼이 해제된 경우 처리
        case CV_EVENT_LBUTTONUP:
            selectObject = false;
            if( selection.width > 0 && selection.height > 0 )
                trackObject = -1; // 추적 객체가 Camshift 속성을 계산하지 않음
            break;
    }
}

int main( int argc, const char** argv ) {
    cv::VideoCapture video("video.ogv");
    cv::namedWindow("CamShift at LabEx");
    cv::setMouseCallback("CamShift at LabEx", onMouse, NULL);

    cv::Mat frame, hsv, hue, mask, hist, backproj;
    cv::Rect trackWindow;             // 추적 윈도우
    int hsize = 16;                   // 히스토그램용
    float hranges[] = {0,180};        // 히스토그램용
    const float* phranges = hranges;  // 히스토그램용

    while(true) {
        video >> frame;
        if(frame.empty()) break;
        frame.copyTo(image);

        // HSV 공간으로 변환
        cv::cvtColor(image, hsv, cv::COLOR_BGR2HSV);
        // 객체가 있는 경우 처리
        if(trackObject) {

            // H: 0~180, S: 30~256, V: 10~256 만 처리하고 나머지는 필터링하여 나머지를 마스크에 복사합니다.
            cv::inRange(hsv, cv::Scalar(0, 30, 10), cv::Scalar(180, 256, 256), mask);
            // hsv 에서 채널 h 분리
            int ch[] = {0, 0};
            hue.create(hsv.size(), hsv.depth());
            cv::mixChannels(&hsv, 1, &hue, 1, ch, 1);

            // 추적 객체가 계산되지 않은 경우 속성 추출
            if( trackObject < 0 ) {

                // 채널 h 및 마스크 ROI 설정
                cv::Mat roi(hue, selection), maskroi(mask, selection);
                // ROI 히스토그램 계산
                calcHist(&roi, 1, 0, maskroi, hist, 1, &hsize, &phranges);
                // 히스토그램 정규화
                normalize(hist, hist, 0, 255, CV_MINMAX);

                // 추적 객체 설정
                trackWindow = selection;

                // 추적 객체가 계산되었음을 표시
                trackObject = 1;
            }
            // 역투영 히스토그램
            calcBackProject(&hue, 1, 0, hist, backproj, &phranges);
            // 공통 영역 가져오기
            backproj &= mask;
            // Camshift 알고리즘 호출
            cv::RotatedRect trackBox = CamShift(backproj, trackWindow, cv::TermCriteria( CV_TERMCRIT_EPS | CV_TERMCRIT_ITER, 10, 1 ));
            // 그리기에는 영역이 너무 작음
            if( trackWindow.area() <= 1 ) {
                int cols = backproj.cols, rows = backproj.rows, r = (MIN(cols, rows) + 5)/6;
                trackWindow = cv::Rect(trackWindow.x - r, trackWindow.y - r,
                                       trackWindow.x + r, trackWindow.y + r) & cv::Rect(0, 0, cols, rows);
            }
            // 추적 영역 그리기
            ellipse( image, trackBox, cv::Scalar(0,0,255), 3, CV_AA );

        }


        if( selectObject && selection.width > 0 && selection.height > 0 ) {
            cv::Mat roi(image, selection);
            bitwise_not(roi, roi);
        }
        imshow("CamShift at LabEx", image);
        int key = cv::waitKey(1000/15.0);
        if(key == 27) break;
    }
    cv::destroyAllWindows();
    video.release();
    return 0;
}

main.cpp를 다시 컴파일해 보겠습니다.

g++ main.cpp $(pkg-config opencv --libs --cflags opencv) -o main

그리고 실행합니다.

./main

이제 프로그램에서 객체를 선택할 수 있으며 추적이 진행 중입니다.

image desc

위의 이미지에서 목성을 선택했으며 추적 윈도우는 빨간색 타원입니다.