2009년 말에 릴리즈 된 OpenCV 2.0 에서는 C++ API가 추가되었는데, 웹캠에서 영상을 캡처하는 부분에는 VideoCapture 라는 클래스가 추가되었다. 이것을 이용해 간단하게 웹캠의 영상을 화면에 표시해주는 프로그램을 만들었다.
먼저, VideoCapture class를 사용하기 위해 "opencv/cv.h"와 "opencv/highgui.h"를 include 해야 한다.
#include "opencv/cv.h"
#include "opencv/highgui.h"
#include "opencv/highgui.h"
영상을 화면에 보여주기 위해 윈도우가 하나 필요한데, wxFrame을 상속받아 MainFrame을 만들었다. (앞에 wx가 붙은 클래스는 모두 wxWidgets의 클래스이다.)
class MainFrame: public wxFrame
{
public:
MainFrame(wxWindow* parent);
virtual ~MainFrame();
protected:
VideoCapture m_cam; // VideoCapture 객체
Mat m_original; // 캡처한 이미지를 담을 곳
ImageView* m_camView; // 이미지를 보여주는 윈도우
wxTimer m_timer; // 주기적으로 이미지를 가져와 보여주기 위해 타이머를 사용한다.
}
{
public:
MainFrame(wxWindow* parent);
virtual ~MainFrame();
protected:
VideoCapture m_cam; // VideoCapture 객체
Mat m_original; // 캡처한 이미지를 담을 곳
ImageView* m_camView; // 이미지를 보여주는 윈도우
wxTimer m_timer; // 주기적으로 이미지를 가져와 보여주기 위해 타이머를 사용한다.
}
생성자에서 다음과 같이 VideoCapture를 설정한다.
MainFrame::MainFrame(wxWindow* parent)
: wxFrame(parent, wxID_ANY, wxEmptyString), m_timer(this, ID_TIMER)
{
m_timer.Start(1000 / 20); // 20 fps
m_cam.open(0);
m_cam.set(CV_CAP_PROP_FRAME_WIDTH, 640);
m_cam.set(CV_CAP_PROP_FRAME_HEIGHT, 480);
}
: wxFrame(parent, wxID_ANY, wxEmptyString), m_timer(this, ID_TIMER)
{
m_timer.Start(1000 / 20); // 20 fps
m_cam.open(0);
m_cam.set(CV_CAP_PROP_FRAME_WIDTH, 640);
m_cam.set(CV_CAP_PROP_FRAME_HEIGHT, 480);
}
VideoCapture::open(int) 함수는 디바이스를 open 하는데, 파라미터는 웹캠 번호이다. (TODO: 시스템에 존재하는 웹캠 리스트를 가져오는 방법은?) 혹은 open(const string&) 안에 string 을 넘겨 동영상을 불러올 수도 있다고 한다.
VideoCapture::set(int propId, double value) 함수는 파라미터를 설정하는 함수인데, 다음과 같은 파라미터가 있다. (cvSetCaptureProperty() 함수 참고)
- CV_CAP_PROP_POS_MSEC - Film current position in milliseconds or video capture timestamp
- CV_CAP_PROP_POS_FRAMES - 0-based index of the frame to be decoded/captured next
- CV_CAP_PROP_POS_AVI_RATIO - Relative position of the video file (0 - start of the film, 1 - end of the film)
- CV_CAP_PROP_FRAME_WIDTH - Width of the frames in the video stream
- CV_CAP_PROP_FRAME_HEIGHT - Height of the frames in the video stream
- CV_CAP_PROP_FPS - Frame rate
- CV_CAP_PROP_FOURCC - 4-character code of codec
- CV_CAP_PROP_BRIGHTNESS - Brightness of the image (only for cameras)
- CV_CAP_PROP_CONTRAST - Contrast of the image (only for cameras)
- CV_CAP_PROP_SATURATION - Saturation of the image (only for cameras)
- CV_CAP_PROP_HUE - Hue of the image (only for cameras)
m_timer 는 wxTimer 객체인데, 주기적으로 이벤트를 발생시키는 역할을 한다. 파라미터는 이벤트 주기이고, millisecond 단위이다. m_timer가 주기적으로 이벤트를 발생시키면, OnUpdateCamera() 함수가 호출되도록 했다. 이 함수 안에서 m_cam (VideoCapture 객체) 로부터 이미지를 가져오도록 구현했다.
BEGIN_EVENT_TABLE(MainFrame, wxFrame)
EVT_TIMER(ID_TIMER, MainFrame::OnUpdateCamera)
END_EVENT_TABLE()
void MainFrame::OnUpdateCamera(wxTimerEvent& event)
{
Mat temp1, temp2;
m_cam >> temp1;
flip(temp1, temp2, 1);
cvtColor(temp2, m_original, CV_BGR2RGB);
m_camView->SetImage(m_original);
Refresh(false); }
EVT_TIMER(ID_TIMER, MainFrame::OnUpdateCamera)
END_EVENT_TABLE()
void MainFrame::OnUpdateCamera(wxTimerEvent& event)
{
Mat temp1, temp2;
m_cam >> temp1;
flip(temp1, temp2, 1);
cvtColor(temp2, m_original, CV_BGR2RGB);
m_camView->SetImage(m_original);
Refresh(false); }
m_cam 으로부터 영상을 캡처하기 위해서는 frame을 grab() 하고 retrieve() 해야하는데, >> 연산자가 이 두 역할을 한번에 해준다. 위와 같이 사용하면 temp1 안에 영상이 들어오게 된다.
다음에 영상이 거울처럼 보이게 하려고 flip(..) 함수를 호출했고, m_cam 에서 가져온 영상이 BGR 모델이기 때문에 이것을 RGB로 변경하기 위해 cvtColor(..) 함수를 호출했다.
이제 영상은 m_original 에 담겼다. 이것을 ImageView 객체인 m_camView 에 넣어주는데, ImageView는 다음과 같이 만들어져 있다.
class ImageView: public wxWindow
{
public:
ImageView(wxWindow* parent);
virtual ~ImageView();
void SetImage(Mat& mat);
protected:
wxImage* m_image;
};
{
public:
ImageView(wxWindow* parent);
virtual ~ImageView();
void SetImage(Mat& mat);
protected:
wxImage* m_image;
};
void ImageView::SetImage(Mat& mat)
{
if (mat.channels() == 3) {
memcpy(m_image->GetData(), mat.data, (mat.rows * mat.cols * mat.channels()));
}
else if (mat.channels() == 1) {
int size = (mat.rows * mat.cols);
unsigned char* data = m_image->GetData();
for (int i = 0; i < size; ++i) {
data[i * 3] = data[i * 3 + 1] = data[i * 3 + 2] = mat.data[i];
}
}
}
{
if (mat.channels() == 3) {
memcpy(m_image->GetData(), mat.data, (mat.rows * mat.cols * mat.channels()));
}
else if (mat.channels() == 1) {
int size = (mat.rows * mat.cols);
unsigned char* data = m_image->GetData();
for (int i = 0; i < size; ++i) {
data[i * 3] = data[i * 3 + 1] = data[i * 3 + 2] = mat.data[i];
}
}
}
Mat 는 OpenCV에서 이미지 나타내는 클래스이다. (C API에서는 IplImage 를 사용하는데, C++ API에서는 모두 Mat 를 사용한다.) 이것을 wxWidgets에서 사용하는 wxImage로 변환하는데, 컬러 수가 같으면 단순히 데이터를 복사하고, Mat의 컬러 수가 1개라면 wxImage에는 흑백 이미지로 복사하도록 했다.
이제 화면에 wxImage의 내용을 표시하면, 웹캠의 영상을 화면으로 볼 수 있다. 이 부분은 VideoCapture와 관련이 없으므로 생략~
(첨부된 소스코드에서는 이보다 더 많은 작업을 하기 때문에, 문서에 있는 코드와는 약간 차이가 있다. 하지만 VideoCapture를 이용해 웹캠의 영상을 가져오는 부분은 동일하다.)