4.1 카메라와 동영상 파일 다루기
4.1.1 VideoCapture 클래스
- 동영상: 일련의 정지 영상을 압축하여 파일로 저장한 형태
- 이때 동영상에 저장되어 있는 일련의 정지 영상을 프레임(frame)이라고 한다.
- 동영상 처리 작업: 동영상에서 프레임 추출 -> 각각의 프레임에 영상 처리 기법을 적용하는 형태
- 아래 코드는 VideoCapture 클래스 정의를 간략화한 것.
class VideoCapture
{
public:
// VideoCapture 클래스의 생성자와 소멸자
VideoCapture();
VideoCapture(const String& filename, int apiPreference = CAP_ANY);
virtual ~VideoCapt
// 동영상 파일 또는 카메라 장치를 열거나 닫는 작업과 관련된 멤버 함수
virtual bool open(const String& filename, int apiPreference = CAP_ANY);
virtual bool open(int index, int apiPreference = CAP_ANY);
virtual bool isOpened() const;
virtual void release();
// 동영상 파일 또는 카메라 장치로부터 한 프레임을 받아 오는 기능의 멤버 함수
virtual bool grab();
virtual bool retrieve(OutputArray image, int flag = 0);
virtual VideoCapture& operator >> (Mat& value);
virtual bool read(OutputArray image);
// 현재 열려 있는 동영상 파일 또는 카메라 장치로부터 정보를 가져오거나 설정하는 긴으을 담당하는 멤버 함수
virtual bool set(int propId, double value);
virtual double get(int propId) const;
};
VideoCapture::VideoCapture(const String& filename, int apiPreference = CAP_ANY);
bool VideoCapture::open(const String& filename, int apiPreference = CAP_ANY);
- filename: 동영상 파일 이름
- apiPreference: 사용할 비디오 캡쳐 API 백엔드
- 반환값: (VideoCapture::open() 함수) 열기가 성공하면 true, 실패하면 false
filename 인자에 비디오 스트림 URL을 지정하여 인터넷 동영상을 사용할 수도 있다.
VideoCapture::VideoCapture(int index, int apiPreference = CAP_ANY);
bool VideoCapture::open(int index, int apiPreference = CAP_ANY);
- index: 카메라와 장치 사용 방식 지정 번호
- apiPreference : 사용할 카메라 캡쳐 API 백엔드
- 반환값: (VideoCapture::open() 함수) 열기가 성공하면 true, 실패하면 false
카메라에 장치를 사용하려고 할때 함수에 전달하는 정수값 index는 다음과 같은 형태로 구성된다.
index = camera_id + domain_offset_id
bool VideoCapture::isOpened() const; // 열기 작업이 성공적으로 수행되었는지 확인
virtual void VideoCapture::release(); // 사용하고 있던 자원을 모두 해제하는 코드
VideoCapture& VideoCapture::operator >> (Mat& image);
bool VideoCapture::read(OutputArray image);
- image: 다음 비디오 프레임. 만약 더 가져올 프레임이 없다면 비어 있는 행렬로 설정된다.
- 반환값: 프레임을 받아 올 수 없으면 false 반환
double VideoCapture::get(int propId) const;
- 현재 열려 있는 카메라 장치 또는 동영상 파일로부터 여러 가지 정보를 받아오기 위해서 사용
- propId: 속성 ID, VideoCaptrueProperties 열거형 중 하나를 지정
- 반환값: 지정한 속성 값. 만약 지정한 속성 값을 얻을 수 없으면 0을 반환한다.
bool VideoCapture::set(int propId, double value);
- propId: 속성 ID, VideoCaptureProperties 열겨형 중 하나를 지정한다.
- value: 지정할 속성 값
- 반환값: 속성 지정이 가능하면 true, 아니면 false
4.1.2 카메라 입력 처리하기
- VideoCapture 클래스를 사용하여 컴퓨터에 연결된 카메라부터 프레임을 받아와서 처리하는 예제 프로그램
int main(void)
{
camera_in();
}
void camera_in()
{
VideoCapture cap(0);
if (!cap.isOpened()) {
cerr << "Camera open failed!" << endl;
return;
}
cout << "Frame width: " << cvRound(cap.get(CAP_PROP_FRAME_WIDTH)) << endl;
cout << "Frame height: " << cvRound(cap.get(CAP_PROP_FRAME_HEIGHT)) << endl;
Mat frame, inversed;
while (true) {
cap >> frame;
if (frame.empty())
break;
inversed = ~frame;
imshow("frame", frame);
imshow("inversed", inversed);
if (waitKey(10) == 27) // ESC key
break;
}
destroyAllWindows();
}
4.1.3 동영상 파일 처리하기
- 아래 코드는 동영상 파일을 풀러와서 처리하는 예제 프로그램 소스 코드
void video_in()
{
VideoCapture cap("stopwatch.avi");
if (!cap.isOpened()) {
cerr << "Video open failed!" << endl;
return;
}
cout << "Frame width: " << cvRound(cap.get(CAP_PROP_FRAME_WIDTH)) << endl;
cout << "Frame height: " << cvRound(cap.get(CAP_PROP_FRAME_HEIGHT)) << endl;
cout << "Frame count: " << cvRound(cap.get(CAP_PROP_FRAME_COUNT)) << endl;
double fps = cap.get(CAP_PROP_FPS);
cout << "FPS: " << fps << endl;
int delay = cvRound(1000 / fps);
Mat frame, inversed;
while (true) {
cap >> frame;
if (frame.empty())
break;
inversed = ~frame;
imshow("frame", frame);
imshow("inversed", inversed);
if (waitKey(delay) == 27) // ESC key
break;
}
destroyAllWindows();
}
- stopwatch.avi 동영상 파일로부터 매 프레임을 받아 와서 원본 프레임과 반전된 프레임을 각각 화면에 출력하는 코드.
콘솔창에는 프레임의 가로 크기와 세로 크기, 전체 프레임 수, FPS 값이 출력됨. 전체 프레임이 모두 출력된 후에는 자동 종료된다.
4.1.4 동영상 파일 저장하기
class VideoWriter
{
public:
// VideoWriter 클래스의 생성자와 소멸자
VideoWriter();
VideoWriter(const String& filename, int fourcc, double fps, Size frameSize, bool isColor = true);
virtual ~VideoWriter();
// 동영상 파일을 열거나 닫는 작업을 수행하는 멤버 함수
virtual bool open(const String& filename, int fourcc, double fps, Size frameSize, bool isColor = true);
virtual bool isOpened() const;
virtual void release();
// 동영상 파일에 프레임을 추가하는 기능의 멤버 함수
virtual VideoWriter& operator << (const Mat& image);
virtual void write(const Mat& image);
// 현재 열린 동영상 파일로부터 정보를 가져오거나 설정하는 기능을 담당하는 함수
virtual bool set(int propId, double value);
virtual double get(int propId) const;
// VideoWriter::fourcc() 멤버함수는 fourcc 코드를 생성하는 정적 멤버 함수
static int fourcc(char c1, char c2, char c3, char c4);
};
VideoWriter::VideoWriter(const String& filename, int fourcc, double fps, Size frameSize, bool isColor = true);
bool VideoWriter::open(const String& filename, int fourcc, double fps, Size frameSize, bool isColor = true);
- filename: 저장할 동영상 파일 이름
- fourcc: 동영상 압축 코덱을 표현하는 4-문자 코드
- fps: 저장할 동영상의 초당 프레임 수
- frameSize: 동영상 프레임의 가로 및 세로 크기
- isColor : 이 값이 true이면 컬러 동영상으로 저장하고, false 이면 그레이스케일 동영상으로 저장.
이 플래그는 Windows 운영 체제에서만 지원됨 - 반환값: (VideoWriter::open() 함수) 열기가 성공하면 true, 실패하면 false
void camera_in_video_out()
{
// 시스템 기본 카메라를 사용
VideoCapture cap(0);
if (!cap.isOpened()) {
cerr << "Camera open failed!" << endl;
return;
}
//카메라 프레임의 가로, 세로 크기, 카메라의 fps 값을 받아온다
int w = cvRound(cap.get(CAP_PROP_FRAME_WIDTH));
int h = cvRound(cap.get(CAP_PROP_FRAME_HEIGHT));
double fps = cap.get(CAP_PROP_FPS);
// DivX MPEG-4 코덱에 해당하는 fourcc 코드를 생성
int fourcc = VideoWriter::fourcc('D', 'I', 'V', 'X');
// FPS 값으로부터 매 프레임 사이의 시간 간격을 밀리초 단위로 계산
int delay = cvRound(1000 / fps);
// 저장할 동영상 파일을 생성. 동영상 파일의 이름은 output.avi
VideoWriter outputVideo("output.avi", fourcc, fps, Size(w, h));
if (!outputVideo.isOpened()) {
cout << "File open failed!" << endl;
return;
}
Mat frame, inversed;
while (true) {
cap >> frame; // 카메라로부터 한 프레임을 받아 와 frame 에 저장
if (frame.empty())
break;
// 카메라 프레임을 반전하여 inversed에 저장
inversed = ~frame;
// 반전된 카메라 프레임 영상 inversed를 출력 동영상에 추가
outputVideo << inversed;
imshow("frame", frame);
imshow("inversed", inversed);
if (waitKey(delay) == 27)
break;
}
destroyAllWindows();
}
- 실제로 동영상 파일을 생성하는 예제 소스 코드. 함수가 종료된 후 Windows 파일 탐색기를 이용해 실행 폴더로 이동하면 output.avi이 생성되어 있다.
4.2 다양한 그리기 함수
4.2.1 직선 그리기
void drawLines()
{
// 400x400 크기의 3채널 컬러 영상을 생성하고, 모든 픽셀을 흰색으로 초기화한다
Mat img(400, 400, CV_8UC3, Scalar(255, 255, 255));
// 수평 방향의 직선 세 개를 서로 다른 색상과 두께로 그린다
line(img, Point(50, 50), Point(200, 50), Scalar(0, 0, 255));
line(img, Point(50, 100), Point(200, 100), Scalar(255, 0, 255), 3);
line(img, Point(50, 150), Point(200, 150), Scalar(255, 0, 0), 10);
// 대각선 방향의 직선 세 개를 서로 다른 색상과 직전 타입으로 그린다
line(img, Point(250, 50), Point(350, 100), Scalar(0, 0, 255), 1, LINE_4);
line(img, Point(250, 70), Point(350, 120), Scalar(255, 0, 255), 1, LINE_8);
line(img, Point(250, 90), Point(350, 140), Scalar(255, 0, 0), 1, LINE_AA);
// 수평 방향의 화살표 세 개를 서로 다른 색상, 길이, 화살표 길이로 그린다.
arrowedLine(img, Point(50, 200), Point(150, 200), Scalar(0, 0, 255), 1);
arrowedLine(img, Point(50, 250), Point(350, 250), Scalar(255, 0, 255), 1);
arrowedLine(img, Point(50, 300), Point(350, 300), Scalar(255, 0, 0), 1, LINE_8, 0, 0.05);
// 다양한 모양의 마커를 그린다.
drawMarker(img, Point(50, 350), Scalar(0, 0, 255), MARKER_CROSS);
drawMarker(img, Point(100, 350), Scalar(0, 0, 255), MARKER_TILTED_CROSS);
drawMarker(img, Point(150, 350), Scalar(0, 0, 255), MARKER_STAR);
drawMarker(img, Point(200, 350), Scalar(0, 0, 255), MARKER_DIAMOND);
drawMarker(img, Point(250, 350), Scalar(0, 0, 255), MARKER_SQUARE);
drawMarker(img, Point(300, 350), Scalar(0, 0, 255), MARKER_TRIANGLE_UP);
drawMarker(img, Point(350, 350), Scalar(0, 0, 255), MARKER_TRIANGLE_DOWN);
imshow("img", img);
waitKey();
destroyAllWindows();
}
- 실행 이미지
4.2.2 도형 그리기
void drawPolys()
{
Mat img(400, 400, CV_8UC3, Scalar(255, 255, 255));
// 사각형을 두께 2인 선으로 그린다 (빨간색)
rectangle(img, Rect(50, 50, 100, 50), Scalar(0, 0, 255), 2);
// 사각형을 내부를 채워서 그린다 (갈색)
rectangle(img, Rect(50, 150, 100, 50), Scalar(0, 0, 128), -1);
// 반지름 30인 원을 내부를 채워서 그린다 (하늘색)
circle(img, Point(300, 120), 30, Scalar(255, 255, 0), -1, LINE_AA);
// 반지름 60인 원을 두께 3인 선으로 그린다 (파란색)
circle(img, Point(300, 120), 60, Scalar(255, 0, 0), 3, LINE_AA);
// 타원을 0부터 270도까지 내부를 채워서 그린다 (하늘색)
ellipse(img, Point(120, 300), Size(60, 30), 20, 0, 270, Scalar(255, 255, 0), FILLED, LINE_AA);
// 타원을 두께 2인 선으로 그린다 (녹색)
ellipse(img, Point(120, 300), Size(100, 50), 20, 0, 360, Scalar(0, 255, 0), 2, LINE_AA);
// 계단 모양의 다각형을 두께 2인 선으로 그린다 (보라색)
vector<Point> pts;
pts.push_back(Point(250, 250)); pts.push_back(Point(300, 250));
pts.push_back(Point(300, 300)); pts.push_back(Point(350, 300));
pts.push_back(Point(350, 350)); pts.push_back(Point(250, 350));
polylines(img, pts, true, Scalar(255, 0, 255), 2);
imshow("img", img);
waitKey();
destroyAllWindows();
}
- 실행 결과
4.2.3 문자열 출력하기
void drawText1()
{
Mat img(500, 800, CV_8UC3, Scalar(255, 255, 255));
// 다양한 폰트를 이용하여 문자열을 출력
putText(img, "FONT_HERSHEY_SIMPLEX", Point(20, 50), FONT_HERSHEY_SIMPLEX, 1, Scalar(0, 0, 255));
putText(img, "FONT_HERSHEY_PLAIN", Point(20, 100), FONT_HERSHEY_PLAIN, 1, Scalar(0, 0, 255));
putText(img, "FONT_HERSHEY_DUPLEX", Point(20, 150), FONT_HERSHEY_DUPLEX, 1, Scalar(0, 0, 255));
putText(img, "FONT_HERSHEY_COMPLEX", Point(20, 200), FONT_HERSHEY_COMPLEX, 1, Scalar(255, 0, 0));
putText(img, "FONT_HERSHEY_TRIPLEX", Point(20, 250), FONT_HERSHEY_TRIPLEX, 1, Scalar(255, 0, 0));
putText(img, "FONT_HERSHEY_COMPLEX_SMALL", Point(20, 300), FONT_HERSHEY_COMPLEX_SMALL, 1, Scalar(255, 0, 0));
putText(img, "FONT_HERSHEY_SCRIPT_SIMPLEX", Point(20, 350), FONT_HERSHEY_SCRIPT_SIMPLEX, 1, Scalar(255, 0, 255));
putText(img, "FONT_HERSHEY_SCRIPT_COMPLEX", Point(20, 400), FONT_HERSHEY_SCRIPT_COMPLEX, 1, Scalar(255, 0, 255));
// FONT_HERSHEY_COMPLEX 폰트와 FONT_ITALIC 상수를 함께 사용하여 이탤릭체로 문자열을 출력한다.
putText(img, "FONT_HERSHEY_COMPLEX | FONT_ITALIC", Point(20, 450), FONT_HERSHEY_COMPLEX | FONT_ITALIC, 1, Scalar(255, 0, 0));
imshow("img", img);
waitKey();
destroyAllWindows();
}
- 실행 결과
void drawText2()
{
Mat img(200, 640, CV_8UC3, Scalar(255, 255, 255));
// 출력할 문자열과 폰트 종류, 크기 비율, 선 두께를 지정한다
const String text = "Hello, OpenCV";
int fontFace = FONT_HERSHEY_TRIPLEX;
double fontScale = 2.0;
int thickness = 1;
// 출력할 문자열이 차지할 사각형 영역의 크기를 구하여 sizeText 변수에 저장한다
Size sizeText = getTextSize(text, fontFace, fontScale, thickness, 0);
// 출력할 대상 영상의 크기를 sizeImg 변수에 저장한다
Size sizeImg = img.size();
// sizeText와 sizeImg 크기 정보를 이용하여 문자열을 출력할 좌표를 계산한다
Point org((sizeImg.width - sizeText.width) / 2, (sizeImg.height + sizeText.height) / 2);
// 실제 문자열을 출력하고 문자열을 감싸는 사각형 영역을 그린다.
putText(img, text, org, fontFace, fontScale, Scalar(255, 0, 0), thickness);
rectangle(img, org, org + Point(sizeText.width, -sizeText.height), Scalar(0, 255, 0), 1);
imshow("img", img);
waitKey();
destroyAllWindows();
}
- 실행 결과
4.3 이벤트 처리
4.3.1 키보드 이벤트 처리
int waitKey(int delay = 0);
- delay: 키 입력을 기다릴 시간(밀리초 단위). delay <= 0 이면 무한히 기다린다.
- 반환값: 눌러진 키 값. 지정한 시간 동안 키가 눌리지 않았으면 -1 을 반환한다.
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace std;
using namespace cv;
int main(void)
{
// lenna.bmp 파일을 그레이스케일 영상 형태로 불러와서 img 변수에 저장
Mat img = imread("lenna.bmp");
if (img.empty()) {
cerr << "Image load failed!" << endl;
return -1;
}
namedWindow("img");
imshow("img", img);
// while 블록을 무한히 반복
while (true) {
// waitKey() 함수의 반환값을 keycode 변수에 저장
int keycode = waitKey();
// 키보드의 i 또는 I를 누르면 img 영상을 반전하여 "img" 창에 나타낸다
if (keycode == 'i' || keycode == 'I') {
img = ~img;
imshow("img", img);
}
// 키보드의 esc 또는 q 또는 Q 를 누르면 while 반복문을 빠져나온다
else if (keycode == 27 || keycode == 'q' || keycode == 'Q') {
break;
}
}
return 0;
}
4.3.2 마우스 이벤트 처리
void setMouseCallback(const String& winname, MouseCallback onMouse, void* userdata = 0);
- winname: 마우스 이벤트 처리를 할 창의 이름
- onMouse: 마우스 이벤트 처리를 위한 콜백 함수 이름
- userdata: 콜백 함수에 전달할 사용자 데이터의 포인터
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace cv;
using namespace std;
//main() 함수와 on_mouse()함수에서 함께 사용할 영상 img를 전역 변수로 선언
Mat img;
// on_mouse() 함수에서 이전 마우스 이벤트 발생 위치를 저장하기 위한 용도로 pt0ld변수를 전역 변수 형태로 선언
Point ptOld;
void on_mouse(int event, int x, int y, int flags, void*);
int main(void)
{
img = imread("lenna.bmp");
if (img.empty()) {
cerr << "Image load failed!" << endl;
return -1;
}
//setMouseCallback() 함수를 사용하기 전에 마우스 이벤트를 받을 창이 미리 생성되어 있어야 함.
namedWindow("img");
setMouseCallback("img", on_mouse);
imshow("img", img);
waitKey();
return 0;
}
void on_mouse(int event, int x, int y, int flags, void*)
{
switch (event) {
case EVENT_LBUTTONDOWN:
// 마우스 왼쪽이 눌린 좌표를 전역 변수 pt0ld에 저장. pt0ld는 마우스가 움직인 궤적을 그릴 때 사용
ptOld = Point(x, y);
// 마우스 왼쪽 버튼이 눌린 좌표를 콘솔 창에 출력
cout << "EVENT_LBUTTONDOWN: " << x << ", " << y << endl;
break;
case EVENT_LBUTTONUP:
//마우스 왼쪽 버튼이 떼어진 좌표를 콘솔 창에 출력
cout << "EVENT_LBUTTONUP: " << x << ", " << y << endl;
break;
// 마우스가 움직이는 경우, 마우스 왼쪽 버튼이 눌려 있는 상태라면 img 영상 위에 노란색 직선을 이어그린다.
// 직선은 pt0ld 좌표부터 현재 마우스 이벤트 발생 좌표까지 그리며, 직선을 그리고 난 후에는 현재 마우스 이벤트 발생 좌표를 ptOld에 저장
case EVENT_MOUSEMOVE:
if (flags & EVENT_FLAG_LBUTTON) {
line(img, ptOld, Point(x, y), Scalar(0, 255, 255), 2);
imshow("img", img);
ptOld = Point(x, y);
}
break;
default:
break;
}
}
4.3.3 트랙바 사용하기
int createTrackbar(const String& trackbarname, const String& winname, int* value, int count, TrackbarCallback onChange = 0, void* userdata = 0);
- trackbarname = 트랙바 이름
- winname: 트랙바를 생성할 창 이름
- value : 트랙바 위치를 받을 정수형 변수의 주소
- count: 트랙바 최대 위치
- onChange: 트랙바 위치가 변결될 때마다 호출되게 만들 콜백 함수 이름(함수의 포인터)
만약 NULL을 지정하면 콜백 함수는 호출되지 않고 value로 지정한 변수 값만 갱신된다. - userdata: 트랙바 콜백 함수에 전달할 사용자 데이터의 포인터
- 반환값: 정상 동작하면 1을, 실패하면 0을 반환한다.
#include "opencv2/opencv.hpp"
#include <iostream>
using namespace cv;
using namespace std;
// 트랙바 콜백 함수를 선언. 이 트랙바 콜백 함수의 정의는 22~28행에 있음
void on_level_change(int pos, void* userdata);
int main(void)
{
// 픽셀 값이 0으로 초기화된 400x400 크기의 영상 img를 생성
Mat img = Mat::zeros(400, 400, CV_8UC1);
// 트랙바를 부착할 image 창을 미리 생성
namedWindow("image");
// 트랙바를 생성하고 트랙바 콜백 함수를 등록. 트랙바 이름은 level이고, 최대 위치는 16으로 지정.
//트랙바 콜백 함수 이름은 on_level_change이고, img 객체의 주소를 사용자 데이터로 지정
createTrackbar("level", "image", 0, 16, on_level_change, (void*)&img);
// img 영상을 image 창에 출력하고, 키 입력이 있을 때까지 창을 닫지 않고 유지
imshow("image", img);
waitKey();
return 0;
}
void on_level_change(int pos, void* userdata)
{
// void* 타입의 인자 userdata를 Mat* 타입으로 형변환한 후 img 변수로 참조
Mat img = *(Mat*)userdata;
// 트랙바 위치에 pos에 16을 곱한 결과를 img 영상의 전체 픽셀 값으로 설정. 만약 pos*16결과가 255보다 크면 포화 연산이 적용
img.setTo(pos * 16);
// 픽셀 값이 설정된 img 영상을 image 창에 출력. 앞서 23행 에서 waitKey() 함수가 사용되었기 때문에 여기서는 waitKey()를 따로 호출하지 않아도 된다.
imshow("image", img);
}
트랙바를 생성한 후, 트랙바의 현재 위치를 알고 싶다면 getTrackbarPos() 함수를 사용할 수 있음.
int getTrackbarPos(const String& trackbarname, const String& winname);
- trackbarname: 트랙바 이름
- winname: 트랙바가 부착되어있는 창 이름
- 반환값: 지정한 트랙바의 현재 위치
void setTrackbarPos(const String& trackbarname, const String& winname, int pos);
- trackbarname: 트랙바 이름
- winname: 트랙바가 부착되어 있는 창 이름
- pos: 트랙바를 이동할 위치
'3학년 > OpenCV' 카테고리의 다른 글
[OpenCV] 8장 영상의 기하학적 변환 (0) | 2023.07.01 |
---|---|
[OpenCV] 7장 필터 (0) | 2023.06.30 |
[OpenCV] 6장 영상의 산술 및 논리 연산 (0) | 2023.05.28 |
[OpenCV] 5장 영상의 밝기와 명암비 조절 (0) | 2023.05.27 |
[OpenCV] 3장 OpenCV 주요 클래스 (0) | 2023.05.22 |