Home [데브코스] 5주차 - OpenCV video processing
Post
Cancel
Preview Image

[데브코스] 5주차 - OpenCV video processing


카메라와 동영상 처리

VideoCapture 클래스

OpenCV에서는 카메라와 동영상으로부터 프레임(frame)을 받아오는 작업을 VideoCapture 클래스 하나로 처리한다. 일단 카메라와 동영상을 여는 작업이 수행되면, 이후에는 매 프레임을 받아오는 공통의 작업을 수행한다.

캠과 파일을 open()으로 열게 되면 read()를 통해 영상파일로부터 프레임을 받아온다.

추가적으로

  • isOpened() : 현재 장치나 비디오 파일이 정상적으로 오픈되어 있는지 확인
  • grab()/retrieve() : read()가 이 두개의 조합으로 구성되는데, grab()은 카메라에게 캡쳐를 시작하라는 명령, retrieve()는 캡쳐된 영상을 프로그램으로 전송받는 명령
  • get()/set() : get()은 속성을 받아옴, set()은 속성 정보를 설정
  • release() : 사용이 끝나면 끝났음을 알려주는 것

VideoCapture 클래스의 정의는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class VideoCapture
{
    public:
        /* 생성자 */
        VideoCapture();
        VideoCapture(const String& filename, int apiPreference = CAP_ANY);
        VideoCapture(int index, int apiPreference = CAP_ANY);
        virtual ~VideoCapture();    // 소멸자

        virtual bool open(const String& filename, int apiPreference = CAP_ANY); // 문자를 받을 때, 동영상 파일을 오픈할 때 사용
        virtual bool open(int index, int apiPreference = CAP_ANY); // 정수를 입력으로 받을 때, 카메라 장치를 오픈할 때 사용
        virtual void release(); // 현재 사용이 끝날 때 호출

        virtual VideoCapture& operator >> (Mat& image); // 프레임 받아오기
        virtual bool VideoCapture::read(OutputArray image); // 프레임 받아오기

        virtual bool set(int propId, double value); // 속성 설정
        virtual double get(int propId) const;   // 속성 참조
        ...
}

생성자와 open이 동일하게 인자를 받을 수 있기 때문에 생성과 동시에 오픈을 할 수가 있다.


카메라 열기

1
2
VideoCapture::VideoCapture(int index, int apiPreference = CAP_ANY);
bool VideoCapture::open(int index, int apiPreference = CAP_ANY);
  • index : 사용할 캡쳐 장치의 ID, 기본적으로 시스템은 0번부터 시작하기 때문에, 0으로 지정하면 첫번째, 여러 대의 카메라가 연결되어 있으면 0, 1, 순서로 지정하면 된다.
  • apiPreference : 선호하는 카메라 처리 방법을 지정, 대부분 Opencv에서 알아서 지정해준다.
    • e.g. cv::CAP_DSHOW, cv::CAP_MSMF, cv::CAP_V4L, etc
  • 반환값 : VideoCapture 생성자는 VideoCapture 객체를 반환한다. VideoCapture::open() 함수는 작업이 성공하면 true, 실패하면 false


동영상 파일 열기

1
2
VideoCapture::VideoCapture(const String& filename, int apiPreference = CAP_ANY);
bool VideoCapture::open(const String& filename, int apiPreference = CAP_ANY);
  • filename : 동영상 파일 이름, 정지 영상 시퀀스(여러 장의 연속 이미지), 비디오 스트림 URL 등
    • 비디오 파일 - ‘video.avi’, ‘./data/video.avi’
    • 정지 영상 시퀀스 - ‘img_%02d.jpg’
    • 비디오 스트림 URL - “https://… mp4”
  • apiPreference : 위와 동일
  • 반환값 : 위와 동일


현재 프레임 받아오기

1
2
bool VideoCapture::read(OutputArray image);
VideoCapture& VideoCapture::operator >> (Mat& image);
  • image : 현재 프레임, 만약 현재 프레임을 받아오지 못하면 비어 있는 영상으로 설정
  • 반환값 : VideoCapture::read()함수는 작업이 성공하면 true, 실패하면 false

OutputArray 형태이므로 Mat frame 변수에 데이터를 받아 read()안에 넣어도 된다.

참고사항 - ‘»’ 연산자 오버로딩은 내부에서 read()함수를 재호출하는 래퍼(wrapper)함수다. 그래서 ‘»’ 대신 read()함수를 사용해도 된다.


  • 카메라 열어 프레임 출력

그렇다면 시스템에 내장되어 있는 카메라를 불러오는 코드를 작성해보았다. 카메라가 오픈되지 않았다면 failed를 출력할 것이다. 카메라를 오픈했다면 아무 메시지도 뜨지 않는다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int main()
{
	VideoCapture cap;
	cap.open(0);

	if (!cap.isOpened()) {
		cerr << "Camera open failed" << endl;
		return -1;
	}
}

정상적으로 작동한다면 다음 진행을 작성한다. 먼저 프레임마다 불러올 것이기 때문에 while문을 사용하고, 프레임을 frame 변수에 저장할 것이기에 frame 변수를 선언해서 cap.read(frame)를 사용해서 읽어온다. 그리고 안전성을 위해 불러오지 못한 경우에는 빠져나가도록 하고, imshow를 하여 윈도우를 생성해주면서 창에 이미지를 표현한다. 이 때, waitKey()를 하지 않는다면 이미지가 나오지 않을 것이다. 그래서 waitKey를 설정해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int main()
{
	VideoCapture cap;
	cap.open(0);
    /* 이 두개를 합쳐서 
    VideoCapture cap(0); 
    으로 해도 된다. */

	if (!cap.isOpened()) {
		cerr << "Camera open failed" << endl;
		return -1;
	}

	Mat frame, edge;      // frame이란 한장의 정지 영상
	while (true) {
		cap >> frame; // == cap.read(frame);

		if (frame.empty()) {
			cerr << "Frame empty!" << endl;
			break;
		}

        Canny(frame, edge, 50, 150); // frame에서 외각을 따서 만드는 것

		imshow("frame", frame);
		imshow("edge", edge);
		if (waitKey(1) == 27) //ESC
			break;
	}

	cap.release(); // 카메라 종료
	destroyAllWindows(); // 생성했던 창을 닫아줌
}

여기서 주의할 점은 waitKey()로 아무 인자없이 실행하면 움직이지 않다가 키를 입력해야 움직이는 것을 볼 수 있다. 그래서 무제한으로 기다리는 것이 아니라 1ms만 기다리고 다음 프레임을 받도록 설정해야 계속 움직인다.

여기서 1ms만 기다리라 해서 1ms만 기다리고 다음 프레임을 받아오는 것이 아니라 카메라마다의 FPS를 통해서 만약 30fps라면 33msec 마다 1장씩 날아오므로 loop문 처음으로 가서 프레임이 올 때까지, 즉 32ms를 더 기다리고 받아서 시작할 것이다.


  • 동영상 파일 프레임 출력
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int main()
{   
    /* 카메라
    VideoCapture cap(0); */
    VideoCapture cap("test_video.mp4");

	if (!cap.isOpened()) {
		cerr << "Camera open failed" << endl;
		return -1;
	}

	Mat frame, edge;      // frame이란 한장의 정지 영상
	while (true) {
		cap >> frame;     // == cap.read(frame);

		if (frame.empty()) {
			cerr << "Frame empty!" << endl; // 동영상 맨 마지막으로 가도 이것이 실행되고 종료 될 것이다.
			break;
		}

        Canny(frame, edge, 50, 150); // frame에서 외각을 따서 만드는 것

		imshow("frame", frame);
		imshow("edge", edge);
		if (waitKey(1) == 27) // ESC
			break;
	}

	cap.release();       // 비디오 종료
	destroyAllWindows(); // 생성했던 창을 닫아줌
}


카메라와 동영상 속성 값 참조 및 설정

1
2
double VideoCapture::get(int propId) const;
bool VideoCapture::set(int propId, double value);
  • probId : 속성 플래스
    • CAP_PROP_POS_MSEC : 비디오 파일에서 현재 위치를 msec단위로 표기
    • CAP_PROP_FRAME_WIDTH : 프레임 가로 크기
    • CAP_PROP_FRAME_HEIGHT : 프레임 세로 크기
    • CAP_PROP_FPS : 초당 프레임 수
    • CAP_PROP_FRAME_COUNT : 비디오 파일의 총 프레임 수
    • CAP_PROP_POS_FRAMES : 현재 프레임 번호
    • CAP_PROP_BRIGHTNESS : 밝기
    • CAP_PROP_EXPOSURE : 노출
  • value : 속성값, 필요한 경우 정수형으로 형변환하여 사용
  • 반환값 :
    • get : 세로나 가로는 정수지만, 통일화하기 위해 double로 추출
    • set : 성공하면 true, 실패하면 false


  • 현재 카메라 크기 출력
1
2
3
4
int w = cvRound(cap.get(CAP_PROP_FRAME_WIDTH)); // == (int)(cap.get(...)
int h = cvRound(cap.get(CAP_PROP_FRAME_HEIGHT)); // == (int)(cap.get(...)

cout << "width: " << w << ", Height: " << h << endl;

이를 실행하면 다음과 같이 출력된다.

  • 크기 조정
1
2
3
4
5
6
7
cap.set(CAP_PROP_FRAME_WIDTH, 640);
cap.set(CAP_PROP_FRAME_HEIGHT, 480);

int w = cvRound(cap.get(CAP_PROP_FRAME_WIDTH)); 
int h = cvRound(cap.get(CAP_PROP_FRAME_HEIGHT)); 

cout << "width: " << w << ", Height: " << h << endl;

이렇게 하면 다음과 같이 변화한 것을 볼 수 있다.

이는 카메라만 가능하고, 동영상의 경우 이미 정해져있기 때문에 동작하지 않을 것이다.


  • fps 출력하기
1
2
double fps = cap.get(CAP_PROP_FPS);
cout << "fps: " << fps << endl;

현재 가지고 있는 동영상의 fps는 50이라는 것을 알 수 있다. 이것을 카메라로 돌리면 0으로 나올 것이다.



VideoWriter 클래스

OpenCV에서는 일련의 정지 영상을 동영상 파일로 저장할 수 있는 VideoWriter 클래스를 제공한다.

VideoWriter의 정의는 다음과 같다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class VideoWriter
{
	public:
		/* 저장할 동영상 파일 열기 */
		VideoWriter();
		VideoWriter (const String& filename, int fourcc, double fps, Size frameSize, bool isColor = true);
		virtual ~VideoCapture();

		virtual bool open (const String& filename, int fourcc, double fps, Size frameSize, bool isColor = true);
		virtual void release();

		virtual VideoCapture& operator << (const Mat& image); // 프레임 저장

		/* 정보 설정하기(set) & 가져오기(get) */
		virtual bool set(int propId, double value);
		virtual double get(int porpId) const;
		...
}



1
2
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개의 문자, VideoWriter::fourcc() 함수로 생성하여 인자로 넣으면 된다.
    • VideoWriter::fourcc(‘D’,’I’,’V’,’X’) : DIVX MPEG-4 코덱
    • VideoWriter::fourcc(‘X’,’V’,’I’,’D’) : XVID MPEG-4 코덱
    • VideoWriter::fourcc(‘X’,’2’,’6’,’4’) : H.264/AVC 코덱
    • VideoWriter::fourcc(‘M’,’J’,’P’,’G’) : Motion-JPEG 코덱
  • fps : 초당 프레임 수
  • frameSize : 비디오 크기
  • isColor : 컬러 동영상 플래그, false로 지정하면 그레이스케일 동영상



카메라 동영상으로 저장하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int main()
{
	VideoCapture cap(0);

	if (!cap.isOpened()) {
		cerr << "Camera open failed!" << endl;
		return -1;
	}

	int  fourcc = VideoWriter::fourcc('X', 'V', 'I', 'D');
	double fps = 15; // 자신의 카메라에 맞게 설정
	Size sz((int)cap.get(CAP_PROP_FRAME_WIDTH), (int)cap.get(CAP_PROP_FRAME_HEIGHT)); // Size sz = Size((int)cap.get(CAP_PROP_FRAME_WIDTH), (int)cap.get(CAP_PROP_FRAME_HEIGHT));

	cout << "FPS = " << fps << endl;
	cout << "Size = " << sz << endl;

	VideoWriter output("output.avi", fourcc, fps, sz);

	if (!output.isOpened()) {
		cerr << "output.avi open failed!" << endl;
		return -1;
	}

	int delay = cvRound(1000 / fps); // 프레임의 시간 간격을 구한 것, ms로 넣어야 하는데, fps란 frame per second이므로 이를 단위 변환
	Mat frame, edge;
	while (true) {
		cap >> frame;
		if (frame.empty())
			break;

		Canny(frame, edge, 50, 150);
		cvtColor(edge, edge, COLOR_GRAY2BGR); // edge는 grayscale이므로 변환해줘야 한다. 

		output << edge;	// 기록을 시작하겠다는 redirection, 즉 edge를 output으로 저장하겠다.

		imshow("frame", frame);
		imshow("edge", edge);

		if (waitKey(delay) == 27)
			break;
	}

	cout << "output.avi file is created!!!" << endl;
	
	output.release();
	cap.release();
	destroyAllWindows();
}



OpenCV 그리기 함수

그리기 방식세부 그리기 방식함수 이름
선 그리기직선 그리기line()
 화삺표 그리기arrowedline()
 마커 그리기drawMarker()
도형 그리기사각형 그리기rectangle()
 원 그리기circle()
 타원 그리기ellipse()
 다각형 그리기polylines(), fillPoly()
문자열 출력하기문자열 출력하기putText()
 출력 문자열 크기 계산getTextSize()

fillPoly()는 다각형을 채워서 그린다.


직선 그리기

1
void line(InputArray img, Point pt1, Point pt2, const Scalar& color, int thickness=1, int lineType = LINE_8, int shift = 0);
  • img : 입출력 영상
  • pt1, pt2 : 시작점, 끝점 좌표
  • color : 선 색상 또는 밝기, Scalar(0,0,255)
  • thickness : 선 두께
  • lineType : 선 타입, LINE_4, LINE_8, LINE_AA 중 하나를 지정, 이는 직선 그리기 방식이 따로 있다.
  • shift : 그리기 좌표 값의 축소 비율

이것을 진행하면 픽셀값이 달라지기 때문에, img를 나중에 다시 사용해야 한다면 복사해놔야 한다.


사각형 그리기

1
void rectangle(InputArray img, Rect rec, const Scalar& color, int thickness=1, int lineType = LINE_8, int shift = 0);
  • img : 입출력 영상
  • rec : 사각형 위치 정보
  • color : 선 색상 또는 밝기, Scalar(0,0,255)
  • thickness : 선 두께, 음수 (-1)를 지정하면 내부를 채움
  • lineType : 선 타입, LINE_4, LINE_8, LINE_AA 중 하나를 지정
  • shift : 그리기 좌표 값의 축소 비율


원 그리기

1
void circle(InputArray img, Point center, int radius, const Scalar& color, int thickness=1, int lineType = LINE_8, int shift = 0);
  • img : 입출력 영상
  • center : 원 중심 좌표
  • radius : 원 반지름
  • color : 선 색상 또는 밝기, Scalar(0,0,255)
  • thickness : 선 두께, 음수 (-1)를 지정하면 내부를 채움
  • lineType : 선 타입, LINE_4, LINE_8, LINE_AA 중 하나를 지정
  • shift : 그리기 좌표 값의 축소 비율


다각형 그리기

1
void polylines(InputArray img, InputArrayOfArrays pts, bool isClosed, const Scalar& color, int thickness=1, int lineType = LINE_8, int shift = 0);
  • img : 입출력 영상
  • pts : 다각형 꼭짓점(외각선) 점들의 집합 , vector<Point>
  • isClosed : true이면 시작점과 끝점을 서로 이음 (폐곡선)
  • color : 선 색상 또는 밝기, Scalar(0,0,255)
  • thickness : 선 두께, 음수 (-1)를 지정하면 내부를 채움
  • lineType : 선 타입, LINE_4, LINE_8, LINE_AA 중 하나를 지정
  • shift : 그리기 좌표 값의 축소 비율


문자열 출력하기

1
void putText(InputArray img, const String& text, Point org, int fontFace, double fontScale, Scalar color, int thickness = 1, int lineType = LINE_8, bottomLeftOrigin = false);
  • img : 입출력 영상
  • text : 출력할 문자열
  • org : 문자열이 출력될 좌측 하단 시작 좌표
  • fontFace : 폰트 종류, cv::HersheyFonts 로 시작하는 폰트로 지정해야 함
  • fontScale : 폰트 크기 지정, 기본 폰트 크기의 배수를 지정
  • color : 문자열 색상
  • thickness : 폰트 두께
  • lineType : 선 타입, LINE_4, LINE_8, LINE_AA 중 하나를 지정



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include "opencv2/opencv.hpp"

using namespace std;
using namespace cv;

int main()
{
	VideoCapture cap("test_video.mp4");

	if (!cap.isOpened()) {
		cerr << "Video open failed!" << endl;
		return -1;
	}

	Mat frame;
	while (true) {
		cap >> frame;

		if (frame.empty()) {
			cerr << "Empty frame!" << endl;
			break;
		}

		line(frame, Point(570, 280), Point(0, 560), Scalar(255, 0, 0), 2);	 // 임의의 점을 잡아 차선 인식 해놓음
		line(frame, Point(570, 280), Point(1024, 720), Scalar(255, 0, 0), 2);

		int pos = cvRound(cap.get(CAP_PROP_POS_FRAMES));
		String text = format("frame number: %d", pos);
		putText(frame, text, Point(20, 50), FONT_HERSHEY_SIMPLEX, 0.7, Scalar(0, 0, 255), 1, LINE_AA); // frame number 화면에 출력

		imshow("frame", frame);

		if (waitKey(10) == 27)
			break;
	}

	cap.release();
	destroyAllWindows();
}
This post is licensed under CC BY 4.0 by the author.